Update 8 documentation files to reflect new URL scheme:
- Dev: /platforms/{code}/storefront/{store_code}/
- Prod: subdomain.platform.lu/ (root path = storefront)
- Rename DEFAULT_PLATFORM_CODE to MAIN_PLATFORM_CODE
- Replace hardcoded platform_id=1 with dynamic values
- Update route examples, middleware descriptions, code samples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
290 lines
14 KiB
Markdown
290 lines
14 KiB
Markdown
# 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=<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=<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', '<h1>Welcome</h1>', 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/` |
|