Skip to main content

theme-config-service

Bounded Context: Theming & Configuration (Supporting) · Owner: Frontend Platform squad · Phase: 0 (foundational; ships with the first tenant) · Storage: Cloud SQL Postgres (shared schema + RLS) + Memorystore (published-theme cache) + Cloud CDN (token bundle distribution) + transactional outbox · Bundle: services/theme-config-service/

theme-config-service is the single platform source of truth for what each tenant looks and feels like on every consumer-facing surface of Ghasi Melmastoon — the multi-tenant hotel SaaS whose backoffice is an Electron offline-first desktop and whose cloud is GCP. One shared codebase serves the consumer meta layer, the tenant-branded booking site (web + mobile), and the meta-search hotel-detail page; this service is what makes that single codebase look uniquely each tenant's at runtime via versioned design tokens, layout presets, content blocks, navigation menus, per-locale copy, booking-flow toggles, and email theme overrides.

The service does not store the raw bytes for logos, hero photos, or videos — those live in file-storage-service and are referenced by URL. It does not translate copy at runtime — translations are authored per locale (with optional AI-drafted suggestions through ai-orchestrator-service, gated by HITL) and pinned at publish time. It does not decide which tenant a request belongs to — bff-consumer-service and bff-tenant-booking-service resolve the tenant from the host/path and ask us for the published theme.

Purpose

  • Be the single platform-wide theming surface for the consumer meta layer, tenant-branded booking experience, meta-search hotel-detail page, and the email theme that notification-service consumes.
  • Enforce a versioned draft → preview → published lifecycle with rollback, OCC, and audit so design changes are safe to ship in production.
  • Resolve design tokens — semantic colors (primary, secondary, surface, text-on-surface, accent, error, warning, success), typography pairs, spacing/radius/shadow/motion scales — into a deterministic, RTL/LTR-aware bundle the BFFs can serve to a static page.
  • Maintain per-locale content (Pashto / Dari / Arabic RTL; English / French / Urdu / Persian LTR; extensible) with a fallback chain so a missing translation never breaks the page.
  • Hold booking-flow configuration (steps shown, fields collected, optional add-ons like passport capture, transport request, gift-booking) so each tenant can shape its funnel without a code deploy.
  • Ship and bust CDN cache on every publish so the global edge re-fetches the new bundle within seconds.

Key responsibilities

  1. Theme CRUD and lifecycle — create / clone / edit / preview / publish / rollback per tenant, optionally per property (Phase 2 chain overrides).
  2. Design tokens resolution — flat semantic-token model with derived RTL variants (margin / padding / text-align / icon flip), contrast checks against WCAG AA (foreground vs background), and tenant-overridable scales.
  3. Layout preset selection — register canonical presets (hero-with-search, mosaic-grid, list-with-map, simple-promo); tenant chooses one per page (home, listing, detail, booking, post-stay).
  4. Content block CRUDabout-us, amenities-grid, gallery, testimonials, faq, contact, policies, location-map; ordered per page; per-locale rich-text body validated against an allow-listed Markdown / sanitized-HTML schema.
  5. Navigation menus — tenant-defined header / footer / mobile-drawer structures; link targets resolved against canonical route ids; per-locale labels.
  6. Per-locale content management — per-tenant enabled locales, default locale, fallback chain (e.g., ps-AF → fa-AF → ar-SA → en); add/remove locales emits events.
  7. RTL-aware token application — direction auto resolves to rtl for ar, ps, fa, ur, he; logical-property derivation for spacing and text alignment; explicit RTL variants required for asymmetric assets (e.g., chevron icons).
  8. Preview environment — short-lived preview tokens that grant rendering of an unpublished ThemeVersion against a special preview hostname; staff can share a preview URL with tenant stakeholders for review.
  9. Publishing workflow with rollback — atomic flip of the active ThemePublication pointer; rollback is a publish of a previous version (no destructive history); OCC against the live theme protects against concurrent publishes.
  10. CDN cache invalidation on publish — write the new bundle to GCS, swap the published-theme cache key in Memorystore, issue a Cloud CDN invalidation by tag, emit theme.cdn_cache_invalidated.v1.
  11. Per-tenant booking-flow configuration — toggles like captureGuestPassport, requestAirportTransport, allowGiftBooking, requireGuestSignature, showCancellationPolicySummary; consumed by bff-tenant-booking-service.
  12. Email theme overrides — per-tenant override block fed to notification-service so transactional emails (booking confirmation, invoice, dunning) use the tenant's logo/color palette.
  13. Concurrent edit handling — OCC on every mutating endpoint (If-Match against version); 409 with the conflicting fields surfaced so the editor can three-way-merge.
  14. Audit projection — every publish, rollback, token change, locale add/remove appended to the audit projection consumed by audit-service.

Hotel-specific shape

  • Multi-language first. Every Pashto, Dari, Arabic, Urdu tenant must work out of the box; the default content scaffolds carry baseline copy in ps-AF, fa-AF, ar-SA, en, fr, ur-PK, tr — tenants override and add as needed.
  • RTL is not optional. Every layout preset registers a paired RTL variant; every token derivation knows about padding-inline-start / padding-inline-end rather than physical padding-left / padding-right. CI fails any preset that uses physical properties.
  • Hero photo + brand colors. The minimum viable tenant theme is: logo, primary colour, hero image, and a 1-paragraph "about". The default layout preset (hero-with-search) already wires these; an empty tenant ships looking presentable.
  • Locale-aware date / currency / phone formatting. The published bundle carries dateFormatPattern, currencyDisplay, phoneFormat per locale so the booking page renders 2026-04-25 as ۱۴۰۵/۲/۵ for Persian users without app code knowing.
  • Email theme co-resolution. When notification-service renders a booking-confirmation email, it reads the active email-theme variant from us; our header / footer block + brand colors keep emails visually consistent with the booking site.
  • Chain branding (Phase 2). Hotel chains can publish a chain theme at the tenant level and override per-property; the property's effective theme is merge(chain, propertyOverrides). Phase 0 ships tenant-only; chain support is a tracked feature flag.
  • Low-bandwidth. The published-token bundle is < 40 KB gzipped; layout-preset scripts and CSS are pre-built into the consumer apps; no runtime CSS-in-JS hydration penalty.

Aggregates owned

AggregateCardinalityPurposeIdentity prefix
Theme1 per (tenant, optional property)Logical theme; carries the active ThemePublication pointer and the version historythm_
ThemeVersion1..N per ThemeImmutable snapshot of tokens + layout selections + content + navigation + booking-flow + email overrides; states draft → preview_ready → published → archivedthv_
ThemePublicationexactly 1 active per ThemeThe version currently served to consumers; rollback creates a new publication pointing at an older versionthp_
LayoutPresetplatform-global registryCanonical layout presets registered by Frontend Platform; tenants select but do not authorlpr_
ContentBlock1..N per ThemeVersionPage-attached block (about/policy/faq/etc.) with per-locale bodycnb_
NavigationConfig1 per (ThemeVersion, surface ∈ {header, footer, mobile_drawer})Ordered tree of menu items with per-locale labelsnvc_
BookingFlowConfig1 per ThemeVersionToggles + ordered step list for the booking flowbfc_
EmailTheme1 per ThemeVersionOverride block consumed by notification-service for transactional emailsemt_
LocalePack1..N per ThemeVersionPer-locale string bundle for keyed copy (CTAs, errors, badges)(composite)
PreviewTokenshort-lived, 1 per shared preview linkSigned token granting read of one unpublished ThemeVersion to anonymous viewers on the preview hostpvt_

Theme.tenantId is mandatory; Theme.propertyId is null for tenant-default themes (Phase 0) and non-null for per-property overrides (Phase 2). Resolution is "property-override wins; otherwise fall back to tenant-default".

Key APIs (REST, /api/v1/themes, /api/v1/theme-versions, /api/v1/theme-publications, /api/v1/layout-presets, /api/v1/content-blocks, /api/v1/navigation-configs, /api/v1/booking-flow-configs, /api/v1/email-themes, /api/v1/locales, /api/v1/preview)

MethodPathPurpose
GET/api/v1/themesList the tenant's themes (default + per-property overrides)
POST/api/v1/themesCreate a new theme (Phase 0: only auto-created on tenant provisioning; admin endpoint for chain Phase 2)
GET/api/v1/themes/:idRead theme with active version pointer
POST/api/v1/themes/:id/versionsCreate a new draft ThemeVersion (clone from active or empty)
PATCH/api/v1/theme-versions/:idEdit draft (tokens, content, navigation, booking flow, email overrides) — OCC If-Match
POST/api/v1/theme-versions/:id/previewMint a preview token + URL; emits theme.preview_generated.v1
POST/api/v1/theme-versions/:id/publishAtomic flip; emits theme.published.v1 + CDN invalidation
POST/api/v1/themes/:id/rollbackPublish a prior version; emits theme.rolled_back.v1
GET/api/v1/themes/:id/publishedPublic read (cacheable): the active token bundle + content + navigation for a tenant/property
GET/api/v1/layout-presetsCatalog of registered presets (platform-global)
POST/api/v1/content-blocksAdd a content block to a draft version
PATCH/api/v1/content-blocks/:idEdit a content block (per-locale body)
DELETE/api/v1/content-blocks/:idRemove from draft
PATCH/api/v1/navigation-configs/:idReplace navigation tree on a draft
PATCH/api/v1/booking-flow-configs/:idToggle booking-flow steps + fields
PATCH/api/v1/email-themes/:idUpdate email theme overrides
POST/api/v1/localesAdd a locale to the tenant's enabled set
DELETE/api/v1/locales/:codeRemove a locale (with safeguards for default + in-use)
POST/api/v1/themes/:id/ai-suggest-paletteRequest AI-suggested palette from primary brand color (orchestrator-routed; HITL on apply)
POST/api/v1/themes/:id/ai-translate-contentRequest AI-drafted translations into the missing locales (orchestrator-routed; HITL on apply)
GET/api/v1/preview/:token/bundlePublic preview bundle read (no auth, signed token)

Consumed by bff-backoffice-service (theme authoring console), bff-consumer-service (meta-layer detail page), bff-tenant-booking-service (full booking surface), and notification-service (email theme reads at render time).

Key events published

EventTrigger
melmastoon.theme.draft_created.v1A new ThemeVersion enters draft
melmastoon.theme.draft_updated.v1A draft mutated (tokens / content / navigation / etc.)
melmastoon.theme.preview_generated.v1Preview token minted
melmastoon.theme.published.v1A ThemeVersion becomes the active publication
melmastoon.theme.rolled_back.v1A prior version re-published as the active one
melmastoon.theme.tokens_changed.v1Token diff between two consecutive published versions
melmastoon.theme.content_block.created.v1Block added
melmastoon.theme.content_block.updated.v1Block edited
melmastoon.theme.content_block.deleted.v1Block removed
melmastoon.theme.locale_added.v1Locale added to enabled set
melmastoon.theme.locale_removed.v1Locale removed
melmastoon.theme.layout_preset_changed.v1Layout preset selection changed for a page
melmastoon.theme.booking_flow_config_updated.v1Booking flow toggles or step order changed
melmastoon.theme.email_theme_updated.v1Email theme override changed
melmastoon.theme.cdn_cache_invalidated.v1CDN purge issued for the published bundle

Key events consumed

EventEffect
melmastoon.tenant.created.v1Provision a default Theme + first ThemeVersion from the platform scaffold; immediately publish so consumer surfaces work day-zero
melmastoon.tenant.deleted.v1Soft-delete all themes + content blocks for the tenant; schedule hard purge per retention policy
melmastoon.tenant.config_updated.v1Refresh fallback locale + currency display rules in the published bundle if changed
melmastoon.property.created.v1 (Phase 2)If chain branding is enabled, scaffold per-property override theme
melmastoon.media.deleted.v1 (from file-storage-service)Mark referenced asset URLs as broken in the relevant published versions; emit a warning event for the tenant admin

Upstream / downstream

Upstream (we consume): tenant-service, property-service (Phase 2), file-storage-service (asset deletion notifications), ai-orchestrator-service (HITL-gated palette / translation drafts).

Downstream (we publish for): bff-consumer-service (meta-layer hotel-detail), bff-tenant-booking-service (booking funnel), bff-backoffice-service (theme console + preview), notification-service (email theme overrides), analytics-service (publish events), audit-service (publish + rollback events).

Non-functional requirements

NFRTarget
Published-bundle read p95 (Memorystore hit)< 15 ms
Published-bundle read p95 (cold, GCS fetch)< 250 ms
Published-bundle gzipped size budget≤ 40 KB for tokens + structure; assets referenced by URL
Publish → CDN-edge availability p95< 10 s
Preview-token mint p95< 80 ms
Draft mutation API p95< 200 ms
API availability99.95% monthly
Tenant isolationRLS-enforced; tenant-isolation.spec.ts mandatory in CI
Token contrast pass rate (WCAG AA on foreground/background pairs)100% at publish time (block on fail)
RTL parity (every layout preset has a passing RTL snapshot)100% in CI
ReplicasMin 3 Cloud Run instances (API), 2 publish-worker, 1 CDN-invalidation worker

Where to go next