# Multi-Platform CMS Architecture ## Overview The Multi-Platform CMS enables Orion to serve multiple business offerings (OMS, Loyalty, Site Builder) from a single codebase, each with its own marketing site and store 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, store_id=NULL │ │ NOT inherited by stores │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ TIER 2: Store Defaults │ │ Default pages all stores inherit (about, terms, privacy) │ │ is_platform_page=FALSE, store_id=NULL │ │ Inherited by ALL stores on the platform │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ TIER 3: Store Overrides │ │ Custom pages created by individual stores │ │ is_platform_page=FALSE, store_id= │ │ Overrides store defaults for specific store │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Content Resolution Flow When a customer visits a store page (e.g., `/stores/shopname/about`): ``` Customer visits: /platforms/oms/storefront/shopname/about │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 1: Check Store Override │ │ SELECT * FROM content_pages │ │ WHERE platform_id=:platform_id AND store_id=123 AND slug='about' │ │ Found? → Return store's custom "About" page │ └─────────────────────────────────────────────────────────────────────┘ │ Not found ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Step 2: Check Store Default │ │ SELECT * FROM content_pages │ │ WHERE platform_id=:platform_id AND store_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 ```sql 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.orion.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() ); ``` ### store_platforms (Junction Table) ```sql CREATE TABLE store_platforms ( store_id INTEGER REFERENCES stores(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 (store_id, platform_id) ); ``` ### content_pages (Extended) ```sql 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, store_id=NULL -- Store defaults: is_platform_page=FALSE, store_id=NULL -- Store overrides: is_platform_page=FALSE, store_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: `orion.lu/` → `main` platform - Platform sites: `omsflow.lu/`, `rewardflow.lu/` → specific platform ### Request Processing ``` Request: GET /platforms/oms/stores/shopname/about │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ PlatformContextMiddleware │ │ - Detects platform from /platforms/{code}/ prefix or domain │ │ - Rewrites path: /platforms/oms/stores/shopname/about │ │ → /stores/shopname/about │ │ - Sets request.state.platform = Platform(code='oms') │ │ - Sets request.state.platform_clean_path = /stores/shopname/about │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ StoreContextMiddleware │ │ - Uses rewritten path for store detection │ │ - Sets request.state.store = Store(subdomain='shopname') │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Route Handler (shop_pages.py) │ │ - Gets platform_id from request.state.platform │ │ - Calls content_page_service.get_page_for_store( │ │ platform_id=platform.id, store_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 MAIN_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 store count, marketing pages, store 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) - **Store Defaults**: Inherited by stores (is_platform_page=FALSE, store_id=NULL) - **Store Overrides**: Store-specific (store_id set) - Color-coded tier badges: - Blue: Platform Marketing - Teal: Store Default - Purple: Store 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/store` | Create store page | ## Key Files ### Models - `models/database/platform.py` - Platform model - `models/database/store_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` - Store 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: ```sql INSERT INTO platforms (code, name, description, domain, path_prefix) VALUES ('loyalty', 'Loyalty Program', 'Customer loyalty and rewards', 'rewardflow.lu', 'loyalty'); ``` 2. Create platform-specific content pages: ```sql INSERT INTO content_pages (platform_id, slug, title, content, is_platform_page) VALUES (2, 'home', 'Loyalty Program', '

Welcome

', TRUE); ``` 3. Configure routing: - **Development:** Access at `localhost:9999/platforms/loyalty/` - **Production:** Access at `rewardflow.lu/` - Platform detected automatically by `PlatformContextMiddleware` - No additional route configuration needed 4. Assign stores to platform: ```sql INSERT INTO store_platforms (store_id, platform_id) VALUES (1, 2); ``` ### Platform URL Summary | Platform | Code | Dev URL | Prod URL | |----------|------|---------|----------| | Main Marketing | `main` | `localhost:9999/` | `orion.lu/` | | OMS | `oms` | `localhost:9999/platforms/oms/` | `omsflow.lu/` | | Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `rewardflow.lu/` |