# ALZAR FZE — CRM-Managed Website CMS: Build Brief

**Read `MIGRATION_BRIEF.md` first.** That covers de-Manus-ing the website and getting
it onto Railway + TiDB. THIS brief covers the next layer: letting employees manage
website content *from the CRM*, gated by owner approval, with SEO and (later) social.

Architecture chosen by the owner: **shared-database, separate apps.** Employees create
content in the CRM panel (same login they already use). The website is a pure renderer
that only ever displays owner-approved, published content. The approval gate is enforced
at the DATA layer, so unapproved content physically cannot appear on the public site.

---

## 1. The shared content model (lives in TiDB)

Content tables live in a schema both apps can reach. Recommended: a dedicated
`alzar_content` schema on the existing TiDB cluster (the CRM writes, the website reads).
Confirm both Railway services can connect to it.

### Content types to build (launch set)
1. **Articles / News** — title, slug, body (rich text), hero image, excerpt, tags,
   author, published date.
2. **Products** — name, slug, category, description, spec fields, images, related
   principal, datasheet/catalog file.
3. **Principals / Brands** — the partners ALZAR represents (e.g. Schäffer, Refmon).
   Name, slug, logo, description, website link, associated industry, associated products.
4. **Job postings** — title, slug, location, department, employment type, description,
   requirements, how-to-apply, posted date, closing date, open/closed status.

Every content type shares a common set of fields (below) so the workflow and SEO logic
are uniform.

### Shared fields on EVERY content item
- `id`, `type`, `slug` (unique per type, URL-safe)
- `status` enum: `draft` | `pending_review` | `approved` | `published` | `rejected`
- `created_by` (CRM user id), `created_at`, `updated_at`
- `reviewed_by`, `reviewed_at`, `review_note` (owner's approve/reject comment)
- `published_at`
- SEO fields: `meta_title`, `meta_description`, `canonical_url`, `og_image`
- Body content stored as sanitized rich text / structured JSON (NOT raw HTML from
  users — sanitize server-side to prevent XSS on the public site).

---

## 2. The approval workflow (the core requirement)

State machine:

```
draft ──submit──> pending_review ──approve──> approved ──publish──> published
                        │                                              
                        └──reject──> rejected ──(edit)──> draft        
```

- **Employees** (CRM role) can create/edit items and move them draft → pending_review.
  They CANNOT approve or publish. They cannot edit an item once it's published without
  it going back through review (create a new revision in pending_review).
- **Owner (approver role)** sees a **review queue** of all `pending_review` items and can:
  1. **Approve / reject with a note** (writes `review_note`, `reviewed_by`, `reviewed_at`).
  2. **Edit before publishing** — modify any field, then approve.
  3. **Preview the live page** — render the item exactly as it will appear on the public
     site, from a secure preview URL, before approving. (Preview route requires approver
     auth; it renders unpublished content for that one item only.)
- Approve → `approved`. A separate publish action (or auto-publish-on-approve, owner's
  choice — make it a setting) flips `approved` → `published` and sets `published_at`.

### Permissions
- Approver capability is a **role/permission flag**, NOT hardcoded to one user. Owner is
  the only approver at launch; adding senior-staff approvers later = grant the flag. No
  rebuild.
- Reuse the CRM's existing auth/roles. Add a `can_approve_content` permission.

### The hard guarantee
The website's PUBLIC queries select `WHERE status = 'published'` ONLY. There is no code
path on the public site that reads draft/pending/approved-but-unpublished content
(except the authenticated preview route). Unapproved content cannot leak.

---

## 3. Audit trail (added for owner protection)

Every state transition writes an immutable log row: `content_id`, `action`
(created/submitted/approved/rejected/published/edited), `actor` (user id), `timestamp`,
optional `note`. Surface this as a per-item history in the CRM. Cheap to build alongside
the workflow; protects everyone when "who published this / when" comes up later.

---

## 4. SEO — concrete deliverables (not decoration)

Build these into the website renderer and the content model:

- **Per-item meta** — `meta_title` + `meta_description` editable per item, with sensible
  auto-fallbacks (e.g. meta_title = title, meta_description = excerpt).
- **Clean slugs** — human-readable URLs: `/articles/refractory-selection-guide`,
  `/products/silica-bricks`, `/principals/refmon`, `/careers/sales-engineer-dubai`.
- **schema.org JSON-LD structured data** per type:
  - Articles → `Article` / `NewsArticle`
  - Products → `Product`
  - Principals → `Organization` / `Brand`
  - Job postings → `JobPosting`  ← this gets jobs into Google Jobs. High value.
  - Site-wide → `Organization` with ALZAR's details (address, JAFZA, contact).
- **Auto-generated `sitemap.xml`** that regenerates when content is published (include
  all published items with lastmod dates). Wire it to the existing sitemap.
- **`robots.txt`** kept correct; preview/admin routes disallowed.
- **Open Graph + Twitter Card tags** per item (title, description, `og_image`) — also
  makes social shares render correctly, which feeds section 5.
- **Canonical URLs** per item to avoid duplicate-content penalties.
- **Performance** — the site is already Vite/React; keep images optimized and lazy-loaded,
  and ensure server-rendered or pre-rendered meta tags so crawlers see them (SPA meta
  injected only client-side is a common SEO failure — verify crawlers get the tags).

> NOTE on crawlability: the current site is a client-rendered React SPA. Confirm that
> per-page meta/JSON-LD is visible to crawlers without JS execution. If not, add
> server-side rendering of `<head>` metadata (or a prerender step) for public content
> pages. This is important — flag to owner if it requires meaningful rework.

---

## 5. Social auto-posting — PHASE 2 (spec now, build after launch)

Owner wants approved content optionally pushed to Instagram, LinkedIn, Facebook.
This is real work per platform and MUST NOT block site launch. Build the CMS +
approval + SEO first, ship the site, then add this.

Reality per platform (verify current requirements at build time):
- **LinkedIn** — official API posting requires a LinkedIn developer app + OAuth +
  "Share on LinkedIn"/Community Management product approval. Company-page posting needs
  page admin auth.
- **Facebook (Pages)** — Graph API can post to a Facebook Page via an app with the right
  permissions; requires app review / business verification. Can take days–weeks.
- **Instagram** — API posting works ONLY for Instagram **Business/Creator** accounts
  linked to a Facebook Page, via the same Facebook Graph API. Personal accounts cannot
  be automated. ← Owner action: ensure the IG account is a Business account linked to
  the FB Page before this phase.

Design so it slots in cleanly:
- Add an optional per-item "share to: [ ] LinkedIn [ ] Facebook [ ] Instagram" choice,
  actioned only on/after publish, and only by the approver.
- Build a small dispatch layer with one adapter per platform behind a common interface,
  so platforms can be enabled one at a time as their app approvals land.
- Store per-platform post status + returned post URL on the content item for the audit
  trail. Handle failures gracefully (a failed social post never un-publishes the page).

---

## 6. Build order (so nothing blocks launch)

1. Finish `MIGRATION_BRIEF.md` — site live on Railway + TiDB, de-Manus'd.
2. Content schema in `alzar_content` on TiDB. Both services connected.
3. Approval workflow + roles/permissions + audit trail, surfaced in the CRM panel.
4. Website renderer reads `status='published'` only; preview route for approver.
5. SEO layer (meta, JSON-LD, sitemap, OG, canonical) + crawlability fix if needed.
6. Launch content types: Articles, Products, Principals, Job postings.
7. **Then** Phase 2: social auto-posting, one platform at a time.

---

## 7. Definition of done (this phase)

- [ ] Employees create/edit all 4 content types from the CRM panel, using their existing login.
- [ ] Nothing appears on the public site until owner approves AND it's published.
- [ ] Owner review queue supports approve/reject-with-note, edit-before-publish, and live preview.
- [ ] Approver is a permission flag (owner only now; extensible to senior staff).
- [ ] Public site queries published content only; unapproved content provably cannot leak.
- [ ] Audit trail records every transition (who/what/when).
- [ ] Every content type emits correct meta + JSON-LD; sitemap auto-updates; crawlers see the tags.
- [ ] Job postings emit valid JobPosting schema (eligible for Google Jobs).
- [ ] Social posting is specced and stubbed behind a clean adapter interface for Phase 2.
