Files
orion/docs/archive/multi-platform-cms-architecture.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

24 KiB

Multi-Platform CMS Architecture

Session Date: 2026-01-18 Status: Initial Analysis - Requirements Captured Related: Loyalty Program Analysis


Executive Summary

The platform is evolving from a single OMS product to a multi-platform business where each platform represents a distinct business offering (OMS, Loyalty Program, Website Builder, etc.). Each platform requires its own independent CMS with a three-tier content hierarchy:

  1. Platform Pages - Marketing site for the platform itself
  2. Store Default Pages - Fallback content for store storefronts
  3. Store Override/Custom Pages - Store-specific content

Current State Analysis

Problems Identified

Issue Description
Conflated page types Platform pages and store defaults share store_id = NULL, making them indistinguishable
Hardcoded homepage Platform homepage uses homepage-orion.html directly, ignoring CMS
Non-functional admin UI /admin/platform-homepage saves to CMS but route doesn't use it
Single platform assumption Architecture assumes one platform, can't scale to multiple offerings
No platform isolation No way to have separate About/FAQ/Pricing pages per platform

Current Architecture (Broken)

ContentPage (store_id = NULL)
    ↓ used by both (conflated)
    ├── Platform Homepage (/about, /pricing)     ← Should be Platform A specific
    └── Store Default Fallback                  ← Should be generic storefront pages

Proposed Architecture

Multi-Platform Hierarchy

┌─────────────────────────────────────────────────────────────────────────────┐
│                           PLATFORM LEVEL                                     │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐           │
│  │ Platform A       │  │ Platform B       │  │ Platform C       │           │
│  │ (Orion OMS)   │  │ (Loyalty+)       │  │ (Site Builder)   │           │
│  │                  │  │                  │  │                  │           │
│  │ • Homepage       │  │ • Homepage       │  │ • Homepage       │           │
│  │ • About          │  │ • About          │  │ • About          │           │
│  │ • Pricing        │  │ • Pricing        │  │ • Pricing        │           │
│  │ • FAQ            │  │ • FAQ            │  │ • FAQ            │           │
│  │ • Contact        │  │ • Contact        │  │ • Contact        │           │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘           │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                      STORE DEFAULT LEVEL (per platform)                     │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ Platform A Defaults                                                   │   │
│  │ • About Us (generic store template)                                   │   │
│  │ • Shipping Policy                                                     │   │
│  │ • Return Policy                                                       │   │
│  │ • Privacy Policy                                                      │   │
│  │ • Terms of Service                                                    │   │
│  │ • FAQ (e-commerce focused)                                            │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         STORE LEVEL (isolated)                              │
│  ┌─────────────────────────┐  ┌─────────────────────────┐                   │
│  │ Store 1 (Orion)     │  │ Store 2 (TechStore)    │                   │
│  │ Platform A, Tier: Pro   │  │ Platform A, Tier: Basic │                   │
│  │                         │  │                         │                   │
│  │ Override Pages:         │  │ Override Pages:         │                   │
│  │ • About (custom)        │  │ • (none - uses defaults)│                   │
│  │ • Shipping (custom)     │  │                         │                   │
│  │                         │  │ Custom Pages:           │                   │
│  │ Custom Pages:           │  │ • Size Guide            │                   │
│  │ • Our Story             │  │                         │                   │
│  │ • Store Locations       │  │                         │                   │
│  └─────────────────────────┘  └─────────────────────────┘                   │
└─────────────────────────────────────────────────────────────────────────────┘

Content Resolution Flow

When a customer visits store1.example.com/about:

1. Identify store context (Store 1)
2. Identify platform context (Platform A)
3. Check: Does Store 1 have custom "about" page?
   ├── YES → Return store's custom page
   └── NO  → Check: Does Platform A have default "about" page?
             ├── YES → Return platform default
             └── NO  → Return 404

Data Model Changes

New: Platform Model

class Platform(Base):
    """
    Represents a business offering/product line.
    Examples: Orion OMS, Loyalty+, Site Builder
    """
    __tablename__ = "platforms"

    id = Column(Integer, primary_key=True)
    code = Column(String(50), unique=True, nullable=False)  # "oms", "loyalty", "sites"
    name = Column(String(100), nullable=False)              # "Orion OMS"
    domain = Column(String(255), nullable=True)             # "orion.lu"

    # Branding
    logo = Column(String(500), nullable=True)
    theme_config = Column(JSON, nullable=True)              # Colors, fonts, etc.

    # Status
    is_active = Column(Boolean, default=True)

    # Timestamps
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime, default=func.now(), onupdate=func.now())

    # Relationships
    content_pages = relationship("ContentPage", back_populates="platform")
    subscription_tiers = relationship("SubscriptionTier", back_populates="platform")
    stores = relationship("Store", back_populates="platform")

Updated: ContentPage Model

class ContentPage(Base):
    """
    CMS content page with three-tier hierarchy:
    1. Platform pages (platform_id set, store_id NULL, is_platform_page=True)
    2. Store defaults (platform_id set, store_id NULL, is_platform_page=False)
    3. Store overrides (platform_id set, store_id set)
    """
    __tablename__ = "content_pages"

    id = Column(Integer, primary_key=True)

    # NEW: Platform association (required)
    platform_id = Column(Integer, ForeignKey("platforms.id"), nullable=False)

    # Existing: Store association (NULL for platform pages and defaults)
    store_id = Column(Integer, ForeignKey("stores.id"), nullable=True)

    # NEW: Distinguish platform marketing pages from store defaults
    is_platform_page = Column(Boolean, default=False, nullable=False)
    # True  = Platform's own page (homepage, pricing, platform about)
    # False = Store default template (when store_id is NULL)
    # N/A   = Store override (when store_id is set)

    # Existing fields...
    slug = Column(String(100), nullable=False)
    title = Column(String(200), nullable=False)
    content = Column(Text, nullable=False)
    content_format = Column(String(20), default="html")
    template = Column(String(50), default="default")

    # SEO
    meta_description = Column(String(300), nullable=True)
    meta_keywords = Column(String(300), nullable=True)

    # Publishing
    is_published = Column(Boolean, default=False)
    published_at = Column(DateTime, nullable=True)

    # Navigation
    display_order = Column(Integer, default=0)
    show_in_header = Column(Boolean, default=False)
    show_in_footer = Column(Boolean, default=True)
    show_in_legal = Column(Boolean, default=False)

    # Timestamps & audit
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
    created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
    updated_by = Column(Integer, ForeignKey("users.id"), nullable=True)

    # Constraints
    __table_args__ = (
        UniqueConstraint("platform_id", "store_id", "slug", name="uq_platform_store_slug"),
        Index("idx_platform_store_published", "platform_id", "store_id", "is_published"),
    )

Updated: Store Model

class Store(Base):
    # Existing fields...

    # NEW: Platform association
    platform_id = Column(Integer, ForeignKey("platforms.id"), nullable=False)
    platform = relationship("Platform", back_populates="stores")

Page Type Matrix

Page Type platform_id store_id is_platform_page Example
Platform Marketing Page NULL TRUE Platform A's homepage, pricing
Store Default Page NULL FALSE Generic "About Our Store" template
Store Override Page FALSE Orion's custom About page
Store Custom Page FALSE Orion's "Store Locations" page

User Journeys

Journey 1: Platform Admin Sets Up New Platform

Actor: Super Admin Goal: Create a new business platform with its marketing pages

1. Admin navigates to /admin/platforms
2. Admin clicks "Create Platform"
3. Admin fills in:
   - Code: "loyalty"
   - Name: "Loyalty+"
   - Domain: "loyalty.orion.lu"
   - Logo, theme colors
4. Admin saves platform
5. System creates platform record
6. Admin navigates to /admin/platforms/loyalty/pages
7. Admin creates platform pages:
   - Homepage (is_platform_page=True)
   - About Us (is_platform_page=True)
   - Pricing (is_platform_page=True)
   - FAQ (is_platform_page=True)
   - Contact (is_platform_page=True)
8. Each page can use different templates (modern, minimal, etc.)
9. Admin publishes pages
10. Platform marketing site is now live at loyalty.orion.lu

Journey 2: Platform Admin Creates Store Defaults

Actor: Platform Admin Goal: Set up default storefront pages for all stores on Platform A

1. Admin navigates to /admin/platforms/oms/store-defaults
2. Admin sees list of store default pages
3. Admin creates default pages:
   - About Us (generic store template)
     Content: "Welcome to our store. We're dedicated to..."
   - Shipping Policy
     Content: "We offer fast and reliable shipping..."
   - Return Policy
     Content: "30-day return policy on all items..."
   - Privacy Policy
     Content: "Your privacy is important to us..."
   - Terms of Service
     Content: "By using our store, you agree to..."
4. All pages have is_platform_page=False, store_id=NULL
5. These pages are now available to ALL stores on Platform A
6. Stores who don't customize will see these defaults

Journey 3: Store Subscribes and Views Default Pages

Actor: New Store (TechStore) Goal: Start using the platform and see what pages are available

1. Store signs up for Platform A (OMS), selects "Basic" tier
2. Store completes onboarding
3. Store logs into dashboard
4. Store navigates to "Content Pages" section
5. Store sees list of pages:
   ┌─────────────────────────────────────────────────────────┐
   │ Content Pages                                           │
   ├─────────────────────────────────────────────────────────┤
   │ Page          │ Source           │ Status    │ Actions │
   ├───────────────┼──────────────────┼───────────┼─────────┤
   │ About Us      │ Platform Default │ Published │ Override│
   │ Shipping      │ Platform Default │ Published │ Override│
   │ Returns       │ Platform Default │ Published │ Override│
   │ Privacy       │ Platform Default │ Published │ Override│
   │ Terms         │ Platform Default │ Published │ Override│
   └─────────────────────────────────────────────────────────┘

   [+ Create Custom Page]

6. Store previews storefront at techstore.example.com/about
7. Sees platform default "About Us" content

Journey 4: Store Overrides a Default Page

Actor: Store (Orion) Goal: Customize the About page with store-specific content

1. Store logs into dashboard
2. Navigates to Content Pages
3. Sees "About Us" with source "Platform Default"
4. Clicks "Override" button
5. System creates a copy with store_id set
6. Store edits content:
   - Title: "About Orion"
   - Content: "Orion was founded in 2020 in Luxembourg..."
   - Adds store images
7. Store saves and publishes
8. Page list now shows:
   ┌─────────────────────────────────────────────────────────┐
   │ About Us      │ Custom Override  │ Published │ Edit    │
   └─────────────────────────────────────────────────────────┘
9. Customer visits orion.example.com/about
10. Sees Orion's custom About page

Journey 5: Store Creates a Custom Page

Actor: Store (Orion) Goal: Add a new page that doesn't exist in defaults

1. Store logs into dashboard
2. Navigates to Content Pages
3. Clicks "+ Create Custom Page"
4. Fills in:
   - Slug: "store-locations"
   - Title: "Our Store Locations"
   - Content: Map and addresses of physical stores
   - Show in footer: Yes
5. Store saves and publishes
6. Page appears in storefront footer navigation
7. Accessible at orion.example.com/store-locations

Journey 6: Store Reverts Override to Default

Actor: Store (Orion) Goal: Remove customization and use platform default again

1. Store navigates to Content Pages
2. Sees "Shipping" with source "Custom Override"
3. Clicks "Revert to Default"
4. System shows confirmation:
   "This will delete your custom Shipping page and show
    the platform default instead. This cannot be undone."
5. Store confirms
6. System deletes store's custom page
7. Storefront now shows platform default Shipping page

Journey 7: Customer Browses Store Storefront

Actor: Customer Goal: Read store policies before purchasing

1. Customer visits orion.example.com
2. Browses products, adds to cart
3. Wants to check return policy
4. Clicks "Returns" in footer
5. System resolves page:
   - Check: Orion override? NO
   - Check: Platform A default? YES
   - Serve: Platform A default "Return Policy" page
6. Customer reads return policy
7. Customer clicks "About Us" in footer
8. System resolves page:
   - Check: Orion override? YES
   - Serve: Orion's custom "About Orion" page
9. Customer sees store-specific About page

Workflow Diagrams

Content Resolution Algorithm

resolve_page(store, slug):
    │
    ├─► Get store's platform_id
    │
    ├─► Query: ContentPage WHERE
    │         platform_id = store.platform_id
    │         AND store_id = store.id
    │         AND slug = slug
    │         AND is_published = True
    │
    ├─► Found? ──YES──► Return store's page
    │     │
    │     NO
    │     ▼
    ├─► Query: ContentPage WHERE
    │         platform_id = store.platform_id
    │         AND store_id IS NULL
    │         AND is_platform_page = False  ← Important: exclude platform pages
    │         AND slug = slug
    │         AND is_published = True
    │
    ├─► Found? ──YES──► Return platform default
    │     │
    │     NO
    │     ▼
    └─► Return 404

Platform Page Resolution (Marketing Site)

resolve_platform_page(platform, slug):
    │
    ├─► Query: ContentPage WHERE
    │         platform_id = platform.id
    │         AND store_id IS NULL
    │         AND is_platform_page = True  ← Only platform marketing pages
    │         AND slug = slug
    │         AND is_published = True
    │
    ├─► Found? ──YES──► Return platform page
    │     │
    │     NO
    │     ▼
    └─► Return 404

API Endpoints

Platform Admin API

# Platform Management
GET    /api/v1/admin/platforms                     # List all platforms
POST   /api/v1/admin/platforms                     # Create platform
GET    /api/v1/admin/platforms/{code}              # Get platform
PUT    /api/v1/admin/platforms/{code}              # Update platform
DELETE /api/v1/admin/platforms/{code}              # Delete platform

# Platform Pages (Marketing)
GET    /api/v1/admin/platforms/{code}/pages        # List platform pages
POST   /api/v1/admin/platforms/{code}/pages        # Create platform page
PUT    /api/v1/admin/platforms/{code}/pages/{id}   # Update platform page
DELETE /api/v1/admin/platforms/{code}/pages/{id}   # Delete platform page

# Store Defaults
GET    /api/v1/admin/platforms/{code}/defaults     # List store defaults
POST   /api/v1/admin/platforms/{code}/defaults     # Create store default
PUT    /api/v1/admin/platforms/{code}/defaults/{id}# Update store default
DELETE /api/v1/admin/platforms/{code}/defaults/{id}# Delete store default

Store API

# View All Pages (defaults + overrides + custom)
GET    /api/v1/store/{code}/content-pages         # List all pages

# Override/Custom Page Management
POST   /api/v1/store/{code}/content-pages         # Create override/custom
PUT    /api/v1/store/{code}/content-pages/{id}    # Update page
DELETE /api/v1/store/{code}/content-pages/{id}    # Delete/revert page

# Page Preview
GET    /api/v1/store/{code}/content-pages/{slug}/preview  # Preview with fallback

Public API

# Storefront Pages (with fallback resolution)
GET    /api/v1/shop/content-pages/{slug}           # Get page (store context from middleware)
GET    /api/v1/shop/content-pages/navigation       # Get nav pages

# Platform Marketing Pages
GET    /api/v1/platform/{code}/pages/{slug}        # Get platform page
GET    /api/v1/platform/{code}/pages/navigation    # Get platform nav

Implementation Phases

Phase 1: Database & Model Updates

  • Create Platform model
  • Add platform_id to ContentPage
  • Add is_platform_page to ContentPage
  • Add platform_id to Store
  • Create migration scripts
  • Create default "oms" platform for existing data

Phase 2: Service Layer

  • Update ContentPageService with three-tier resolution
  • Add PlatformService for platform CRUD
  • Update page listing to show source (Platform/Default/Override)

Phase 3: Admin Interface

  • Platform management UI (/admin/platforms)
  • Platform pages editor (/admin/platforms/{code}/pages)
  • Store defaults editor (/admin/platforms/{code}/defaults)
  • Fix platform homepage to use CMS

Phase 4: Store Dashboard

  • Update content pages list to show page source
  • Add "Override" action for default pages
  • Add "Revert to Default" action for overrides
  • Visual indicator for inherited vs custom pages

Phase 5: Public Routes

  • Update shop routes with three-tier resolution
  • Update platform routes to use CMS
  • Ensure proper navigation loading per context

Migration Strategy

Existing Data Handling

-- 1. Create default platform
INSERT INTO platforms (code, name, domain, is_active)
VALUES ('oms', 'Orion OMS', 'localhost:8000', true);

-- 2. Update all existing content_pages
UPDATE content_pages
SET platform_id = (SELECT id FROM platforms WHERE code = 'oms');

-- 3. Mark platform marketing pages
UPDATE content_pages
SET is_platform_page = true
WHERE store_id IS NULL
  AND slug IN ('platform_homepage', 'pricing');

-- 4. Remaining NULL store pages become store defaults
UPDATE content_pages
SET is_platform_page = false
WHERE store_id IS NULL
  AND is_platform_page IS NULL;

-- 5. Update all stores
UPDATE stores
SET platform_id = (SELECT id FROM platforms WHERE code = 'oms');

Open Questions

  1. Domain routing: How to route requests to correct platform?

    • Option A: Separate domains (oms.orion.lu, loyalty.orion.lu)
    • Option B: Path-based (/oms/, /loyalty/)
    • Option C: Subdomain detection
  2. Shared stores: Can a store belong to multiple platforms?

    • Current assumption: NO, one store per platform
    • If YES: Need junction table
  3. Tier restrictions: Can page creation be restricted by tier?

    • e.g., Basic tier: max 5 custom pages
    • e.g., Pro tier: unlimited pages
  4. Template inheritance: Should store defaults have template selection?

    • Or always use a standard template?

Session Notes

2026-01-18

  • Analyzed current CMS architecture issues
  • Identified homepage is hardcoded (not using CMS)
  • Confirmed admin platform-homepage UI is non-functional
  • Designed three-tier content hierarchy:
    1. Platform pages (marketing)
    2. Store defaults (fallback)
    3. Store overrides/custom
  • Documented user journeys for all actors
  • Outlined implementation phases
  • Next: Review proposal, clarify open questions, begin implementation

Document created for session continuity. Update as discussions progress.