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

290 lines
14 KiB
Markdown

# 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
```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.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)
```sql
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)
```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, 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:
```sql
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:
```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 `loyalty.lu/`
- Platform detected automatically by `PlatformContextMiddleware`
- No additional route configuration needed
4. Assign vendors to platform:
```sql
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/` |