Files
orion/docs/architecture/multi-platform-cms.md
Samir Boulahtit 4f55fe31c8 docs: update documentation for /platforms/ URL routing strategy
- Add multi-platform URL routing section to url-routing/overview.md
- Update multi-platform-cms.md with new request flow diagrams
- Add PlatformContextMiddleware documentation to middleware.md
- Update middleware execution order diagram
- Add Phase 7 (Platform URL Routing Strategy) to implementation plan
- Update platform URL summary tables across all docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 18:32:03 +01:00

14 KiB

Multi-Platform CMS Architecture

Overview

The Multi-Platform CMS enables Wizamart to serve multiple business offerings (OMS, Loyalty, Site Builder) from a single codebase, each with its own marketing site and vendor ecosystem.

Three-Tier Content Hierarchy

Content pages follow a three-tier inheritance model:

┌─────────────────────────────────────────────────────────────────────┐
│                     TIER 1: Platform Marketing                       │
│  Public pages for the platform (homepage, pricing, features)         │
│  is_platform_page=TRUE, vendor_id=NULL                               │
│  NOT inherited by vendors                                            │
└─────────────────────────────────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     TIER 2: Vendor Defaults                          │
│  Default pages all vendors inherit (about, terms, privacy)           │
│  is_platform_page=FALSE, vendor_id=NULL                              │
│  Inherited by ALL vendors on the platform                            │
└─────────────────────────────────────────────────────────────────────┘
                                   │
                                   ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     TIER 3: Vendor Overrides                         │
│  Custom pages created by individual vendors                          │
│  is_platform_page=FALSE, vendor_id=<vendor_id>                       │
│  Overrides vendor defaults for specific vendor                       │
└─────────────────────────────────────────────────────────────────────┘

Content Resolution Flow

When a customer visits a vendor page (e.g., /vendors/shopname/about):

Customer visits: /vendors/shopname/about
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: Check Vendor Override                                        │
│ SELECT * FROM content_pages                                          │
│ WHERE platform_id=1 AND vendor_id=123 AND slug='about'               │
│ Found? → Return vendor's custom "About" page                         │
└─────────────────────────────────────────────────────────────────────┘
                         │ Not found
                         ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 2: Check Vendor Default                                         │
│ SELECT * FROM content_pages                                          │
│ WHERE platform_id=1 AND vendor_id IS NULL                            │
│   AND is_platform_page=FALSE AND slug='about'                        │
│ Found? → Return platform's default "About" template                  │
└─────────────────────────────────────────────────────────────────────┘
                         │ Not found
                         ▼
                    Return 404

Database Schema

platforms

CREATE TABLE platforms (
    id SERIAL PRIMARY KEY,
    code VARCHAR(50) UNIQUE NOT NULL,     -- 'oms', 'loyalty', 'sitebuilder'
    name VARCHAR(100) NOT NULL,            -- 'Order Management System'
    description TEXT,
    domain VARCHAR(255),                   -- 'oms.wizamart.lu'
    path_prefix VARCHAR(50),               -- '/oms'
    logo VARCHAR(255),
    logo_dark VARCHAR(255),
    favicon VARCHAR(255),
    theme_config JSONB DEFAULT '{}',
    default_language VARCHAR(10) DEFAULT 'fr',
    supported_languages JSONB DEFAULT '["fr", "de", "en"]',
    is_active BOOLEAN DEFAULT TRUE,
    is_public BOOLEAN DEFAULT TRUE,
    settings JSONB DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

vendor_platforms (Junction Table)

CREATE TABLE vendor_platforms (
    vendor_id INTEGER REFERENCES vendors(id) ON DELETE CASCADE,
    platform_id INTEGER REFERENCES platforms(id) ON DELETE CASCADE,
    joined_at TIMESTAMP DEFAULT NOW(),
    is_active BOOLEAN DEFAULT TRUE,
    settings JSONB DEFAULT '{}',
    PRIMARY KEY (vendor_id, platform_id)
);

content_pages (Extended)

ALTER TABLE content_pages ADD COLUMN platform_id INTEGER REFERENCES platforms(id);
ALTER TABLE content_pages ADD COLUMN is_platform_page BOOLEAN DEFAULT FALSE;

-- Platform marketing pages: is_platform_page=TRUE, vendor_id=NULL
-- Vendor defaults: is_platform_page=FALSE, vendor_id=NULL
-- Vendor overrides: is_platform_page=FALSE, vendor_id=<id>

Request Flow

URL Routing Structure

The system uses different URL patterns for development vs production:

Development (localhost):

  • Main marketing site: localhost:9999/ (no prefix) → main platform
  • Platform sites: localhost:9999/platforms/{code}/ → specific platform

Production (custom domains):

  • Main marketing site: wizamart.lu/main platform
  • Platform sites: oms.lu/, loyalty.lu/ → specific platform

Request Processing

Request: GET /platforms/oms/vendors/shopname/about
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PlatformContextMiddleware                                            │
│ - Detects platform from /platforms/{code}/ prefix or domain          │
│ - Rewrites path: /platforms/oms/vendors/shopname/about               │
│                  → /vendors/shopname/about                           │
│ - Sets request.state.platform = Platform(code='oms')                 │
│ - Sets request.state.platform_clean_path = /vendors/shopname/about   │
└─────────────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ VendorContextMiddleware                                              │
│ - Uses rewritten path for vendor detection                           │
│ - Sets request.state.vendor = Vendor(subdomain='shopname')           │
└─────────────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Route Handler (shop_pages.py)                                        │
│ - Gets platform_id from request.state.platform                       │
│ - Calls content_page_service.get_page_for_vendor(                    │
│       platform_id=1, vendor_id=123, slug='about'                     │
│   )                                                                  │
│ - Service handles three-tier resolution                              │
└─────────────────────────────────────────────────────────────────────┘

Main Marketing Site (No Platform Prefix)

For requests without the /platforms/ prefix (e.g., localhost:9999/about):

Request: GET /about
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PlatformContextMiddleware                                            │
│ - No /platforms/ prefix detected                                     │
│ - Uses DEFAULT_PLATFORM_CODE = 'main'                                │
│ - Sets request.state.platform = Platform(code='main')                │
│ - Path unchanged: /about                                             │
└─────────────────────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Route Handler (platform_pages.py)                                    │
│ - Gets platform_id from request.state.platform (main)                │
│ - Loads CMS content for main marketing site                          │
└─────────────────────────────────────────────────────────────────────┘

Admin Interface

Platform Management (/admin/platforms)

  • Lists all platforms with statistics
  • Shows vendor count, marketing pages, vendor defaults
  • Links to platform detail and edit pages

Content Pages (/admin/content-pages)

  • Platform filter dropdown
  • Four-tab view:
    • All Pages: Complete list
    • Platform Marketing: Public platform pages (is_platform_page=TRUE)
    • Vendor Defaults: Inherited by vendors (is_platform_page=FALSE, vendor_id=NULL)
    • Vendor Overrides: Vendor-specific (vendor_id set)
  • Color-coded tier badges:
    • Blue: Platform Marketing
    • Teal: Vendor Default
    • Purple: Vendor Override

API Endpoints

Platform Management

Method Endpoint Description
GET /api/v1/admin/platforms List all platforms
GET /api/v1/admin/platforms/{code} Get platform details
PUT /api/v1/admin/platforms/{code} Update platform
GET /api/v1/admin/platforms/{code}/stats Platform statistics

Content Pages

Method Endpoint Description
GET /api/v1/admin/content-pages/ List all pages (supports platform filter)
GET /api/v1/admin/content-pages/platform Platform default pages only
POST /api/v1/admin/content-pages/platform Create platform page
POST /api/v1/admin/content-pages/vendor Create vendor page

Key Files

Models

  • models/database/platform.py - Platform model
  • models/database/vendor_platform.py - Junction table
  • models/database/content_page.py - Extended with platform_id

Middleware

  • middleware/platform_context.py - Platform detection and context

Services

  • app/services/content_page_service.py - Three-tier content resolution

Routes

  • app/routes/platform_pages.py - Platform marketing pages
  • app/routes/shop_pages.py - Vendor shop pages with inheritance

Admin

  • app/api/v1/admin/platforms.py - Platform management API
  • app/templates/admin/platforms.html - Platform admin UI
  • static/admin/js/platforms.js - Alpine.js component

CMS Tier Limits (Subscription-Based)

Tier Total Pages Custom Pages
Essential 3 0
Professional 10 5
Business 30 20
Enterprise Unlimited Unlimited

Adding a New Platform

  1. Insert platform record:

    INSERT INTO platforms (code, name, description, domain, path_prefix)
    VALUES ('loyalty', 'Loyalty Program', 'Customer loyalty and rewards', 'loyalty.lu', 'loyalty');
    
  2. Create platform-specific content pages:

    INSERT INTO content_pages (platform_id, slug, title, content, is_platform_page)
    VALUES (2, 'home', 'Loyalty Program', '<h1>Welcome</h1>', TRUE);
    
  3. Configure routing:

    • Development: Access at localhost:9999/platforms/loyalty/
    • Production: Access at loyalty.lu/
    • Platform detected automatically by PlatformContextMiddleware
    • No additional route configuration needed
  4. Assign vendors to platform:

    INSERT INTO vendor_platforms (vendor_id, platform_id)
    VALUES (1, 2);
    

Platform URL Summary

Platform Code Dev URL Prod URL
Main Marketing main localhost:9999/ wizamart.lu/
OMS oms localhost:9999/platforms/oms/ oms.lu/
Loyalty loyalty localhost:9999/platforms/loyalty/ loyalty.lu/