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>
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: /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
|
|
|
|
```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: `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 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', '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 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/` | `oms.lu/` |
|
|
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
|