# DEPRECATED — Legacy Website CMS architecture

> Retired in Phase 13. This document remains only as a historical data and migration reference. New Website work uses the Sites platform.

# Website CMS architecture — Phase 8B1

## Data model and relationships

`TakafulOperator` has one `OperatorWebsite`; the website owns one `OperatorWebsiteTheme` and many `WebsitePage` records. A page owns ordered `WebsiteSection` records, and each section owns localized `WebsiteSectionRevision` history.

Deletion cascades only through this CMS ownership graph: website → theme/pages → sections → revisions. The operator foreign key cascades to its website. This makes an intentional operator deletion clean up orphaned CMS data, but Phase 8B1 adds no application delete action.

## Pages and sections

`WebsitePageType` is the authoritative enum: `home`, `hibah`, `medicalcard`, `quotation`, `contact`. The database prevents duplicate `(operator_website_id, page_type)` and duplicate `(website_page_id, section_key)` records.

`config/website_cms.php` is the server-side typed registry. It contains only sections audited in the reference templates: Home has hero/intro/agent profile; Hibah and Medical Card have their audited hero, importance, fact/cost, solution, plan, steps and final CTA structures; Quotation has hero, quotation intro and quotation form; Contact has hero and agent-contact card. It defines field shapes, not production copy.

## Locale and revision model

The website default locale is `ms`; revisions carry their own `locale`, so `en` can be added later without a schema change. Actual seeded/default English content is intentionally absent.

Each revision has a unique `(website_section_id, locale, revision_no)` tuple. Revision numbers must be allocated transactionally by future server-side draft/publish services—never Vue. Status is centralized in `WebsiteSectionRevisionStatus`: `draft`, `published`, `archived`.

The future write service must lock the section/locale revision set and enforce at most one current draft and one published revision. A portable partial unique index is not used because the application is tested with SQLite and intended for MySQL; the ordinary lookup index supports that transactional invariant without a database-specific generated-column trick.

## Theme separation

`OperatorWebsiteTheme` holds logo, color values and `theme_config`, one row per website. It has no content/revision fields, so visual identity remains stable and independent of editable localized copy.

## Quotation boundary

Quotation has `quotation_intro` for the audited surrounding title/content and `quotation_form` as `section_type=system`, `is_system=true`. The registry gives it no editable fields, and `WebsiteSection::acceptsContentRevisions()` returns false. The future renderer will mount the existing quotation system here; CMS must not alter fields, validation, operator selection, lead screening, submission or tracking.

## Future editor/publish flow

An editor will load the schema registry and a draft revision, validate structured JSON against the registered field types, allocate the next revision number in a transaction, and save a draft. Publishing will archive the existing published revision and promote the selected validated revision within the same transaction. Public rendering will query only the published revision. None of that UI, mutation service, seeding, or rendering is part of Phase 8B1.

## Default bootstrap seed — Phase 8B2

`Database\\Seeders\\Data\\WebsiteCmsDefaults` contains the audited, structured Malay defaults and exact theme palette. `WebsiteCmsDefaultSeeder`, called after operator seeding, creates/updates one website, theme, five pages, audited sections, and one published `ms` revision 1 for every editable section. It uses `updateOrCreate`, so re-running it preserves record counts and deterministically restores canonical revision-1 data.

Mapping to the audit source is `aia→aia`, `pru→pru`, `get→ge`, `et→etiqa`, `ti→ikhlas`, `hlmt→hlmt`, `zt→zurich`, `fwd→fwd`, `slt→sl`. TakafulHub stores the data internally and has no runtime reference-project dependency.

Asset fields use canonical future TakafulHub paths under `/assets/website-cms/` and `/assets/operators/`. The source files are deliberately not copied in this phase; `theme_config.asset_source_pending_copy` records that later asset-port work is required. No public renderer consumes these values yet.

## Public read/render flow — Phase 8B3

`PublicOperatorWebsiteService` resolves only an active operator, active operator website, enabled page, enabled ordered sections and `published` revisions for the website default locale. It returns a reduced render model with public operator identity, theme and section payloads only; draft/history/author metadata is excluded. Missing published content for a non-system section is skipped rather than exposing a draft.

`PublicOperatorWebsiteController` renders one shared `Public/OperatorCmsPage` for Home, Hibah, Medical Card and Contact. The existing quotation route renders the same CMS hero/intro data around the fixed `#quotation-form` mount, while its available operators, fields, validation and submission endpoint remain owned by `PublicQuotationController` and `QuotationSubmissionService`.

The seeded asset paths are not physically present yet. Image elements remove themselves on load error, leaving the content panel usable; a later asset-port phase must copy/replace the referenced files. No public render path mutates CMS data.

## Admin Website Setting read model — Phase 8B4

`AdminWebsiteSettingService` powers the protected `/admin/website-settings` workspace. It lists operator websites, resolves a selected website/page, displays the theme, and returns ordered sections with only their current published localized revision. Content is interpreted as structured fields in the workspace rather than raw JSON; system quotation sections are clearly marked non-editable and contain no revision payload.

The workspace has no mutation routes or controls. It provides a scoped live URL for the currently selected operator/page, while drafts, revision history, creator data, authentication data, payment data, and unrelated application data remain outside its read model.

## Section drafts — Phase 8B5A

An admin may edit an enabled non-system section through `WebsiteSectionDraftService`. The first validated save locks the section revisions and creates draft revision `max(revision_no) + 1` from the published baseline; later saves update that same draft row. Published revisions are never updated by this workflow, and the public renderer continues to select `published` only. System sections such as `quotation_form` are rejected by both editor and save endpoints.

The editor uses the configured schema field definitions and accepts only the exact declared content keys. Content is plain structured data; no HTML/WYSIWYG input, publishing, preview, theme editing, uploads, reordering, or visibility mutation is provided.

## Draft preview — Phase 8B5B

The admin-only preview route resolves the selected operator website and page through `WebsiteDraftPreviewService`. For every enabled editable section it chooses the active draft in the website default locale when one exists, otherwise its published revision; system sections retain their application-controlled runtime behavior. The normal public routes still use `PublicOperatorWebsiteService`, which reads published revisions only.

Preview reuses the public page renderers and provides an explicit draft banner with workspace and published-page links. Quotation preview continues to use the production form renderer and its application-provided operator/options data, while its submit action is disabled in preview mode. Preview reads do not create, update, publish, or otherwise mutate revisions. There are no publish, rollback, theme editing, uploads, reordering, visibility controls, or other CMS mutations in this phase.

## Section publish — Phase 8B6A

`WebsiteSectionPublishService` publishes one editable section atomically. It locks the section and all revisions in the website default locale, archives the current published revision, and promotes the existing draft with a `published_at` timestamp. The draft row itself becomes published; publishing does not create a new revision or change its creator. A second request sees no draft and returns a controlled conflict.

The lifecycle is `Published #1 → Draft #2 → Preview → Archived #1 + Published #2`; the next edit then creates `Draft #3`. Publishing is section-scoped, so other drafts on the same page remain drafts and keep page preview available. System sections, including `quotation_form`, cannot be published. Rollback, revision-history UI, bulk/page-wide publish, theme editing, uploads, visibility controls, reordering, and locale switching remain out of scope.

## Revision history and rollback — Phase 8B6B

Editable sections expose a read-only, newest-first history for the current locale with revision status, timestamps, creator display name, and schema-shaped content. Only Archived revisions are rollback candidates. `WebsiteSectionRollbackService` locks the section and revisions and copies an Archived revision into the active Draft; if no Draft exists, it creates the next revision number. If one already exists, it updates that same Draft row rather than creating another.

For example, `Published #3 + Archived #1/#2 → restore #1 → Draft #4 → preview → optional publish → Archived #3 + Published #4`. Restoring never changes the live page; only the existing section publish flow can do that. There is no direct-live, page-wide, bulk, theme, asset, diff, locale, reorder, or visibility rollback capability.

## Theme editing — Phase 8B7A

Each operator website continues to have one current `OperatorWebsiteTheme` record. Admins may directly save the five existing colour fields (`primary_color`, `secondary_color`, `accent_color`, `background_color`, and `text_color`) as strict, normalized `#RRGGBB` values. These updates are immediate-live for both public pages and draft preview; content revisions remain a separate versioned workflow.

`logo_path` and seeded `theme_config` stay read-only. This phase adds no uploads, remote assets, arbitrary CSS, theme draft/publish, theme history, or theme rollback. The initial seeder values remain defaults only, and updates apply only to the selected operator website theme record.

## CMS asset uploads — Phase 8B7B

CMS image uploads use the `public` disk with server-generated names under `website-cms/{operator-slug}/{approved-context}`. The accepted contexts map only to audited top-level image placements and `theme_logo`; paths returned to the client are canonical relative public paths beginning `/storage/website-cms/`. Uploads accept verified JPG, PNG, and WebP images up to 5 MB. SVG, arbitrary documents, remote URLs, traversal paths, and user-controlled storage directories are not accepted.

A `theme_logo` upload immediately updates the selected operator's current theme logo path. Section-image uploads only return a path: the editor still saves that path into a section Draft, then preview and publish retain the normal content lifecycle. An upload that is never saved or published may remain orphaned; cleanup is deliberately deferred.
