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>
14 KiB
14 KiB
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: /stores/shopname/about
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: Check Store Override │
│ SELECT * FROM content_pages │
│ WHERE platform_id=1 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=1 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
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)
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)
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) →mainplatform - Platform sites:
localhost:9999/platforms/{code}/→ specific platform
Production (custom domains):
- Main marketing site:
orion.lu/→mainplatform - Platform sites:
oms.lu/,loyalty.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=1, 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 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 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 modelmodels/database/store_platform.py- Junction tablemodels/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 pagesapp/routes/shop_pages.py- Store shop pages with inheritance
Admin
app/api/v1/admin/platforms.py- Platform management APIapp/templates/admin/platforms.html- Platform admin UIstatic/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
-
Insert platform record:
INSERT INTO platforms (code, name, description, domain, path_prefix) VALUES ('loyalty', 'Loyalty Program', 'Customer loyalty and rewards', 'loyalty.lu', 'loyalty'); -
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); -
Configure routing:
- Development: Access at
localhost:9999/platforms/loyalty/ - Production: Access at
loyalty.lu/ - Platform detected automatically by
PlatformContextMiddleware - No additional route configuration needed
- Development: Access at
-
Assign stores to platform:
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/ |
oms.lu/ |
| Loyalty | loyalty |
localhost:9999/platforms/loyalty/ |
loyalty.lu/ |