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>
605 lines
17 KiB
Markdown
605 lines
17 KiB
Markdown
# Content Management System (CMS)
|
|
|
|
## Overview
|
|
|
|
The Content Management System allows platform administrators and stores to manage static content pages like About, FAQ, Contact, Shipping, Returns, Privacy Policy, Terms of Service, etc.
|
|
|
|
**Key Features:**
|
|
- ✅ Platform-level default content
|
|
- ✅ Store-specific overrides
|
|
- ✅ Fallback system (store → platform default)
|
|
- ✅ Rich text content (HTML/Markdown)
|
|
- ✅ SEO metadata
|
|
- ✅ Published/Draft status
|
|
- ✅ Navigation management (footer/header)
|
|
- ✅ Display order control
|
|
|
|
## Architecture
|
|
|
|
### Two-Tier Content System
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ CONTENT LOOKUP FLOW │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
Request: /about
|
|
|
|
1. Check for store-specific override
|
|
↓
|
|
SELECT * FROM content_pages
|
|
WHERE store_id = 123 AND slug = 'about' AND is_published = true
|
|
↓
|
|
Found? ✅ Return store content
|
|
❌ Continue to step 2
|
|
|
|
2. Check for platform default
|
|
↓
|
|
SELECT * FROM content_pages
|
|
WHERE store_id IS NULL AND slug = 'about' AND is_published = true
|
|
↓
|
|
Found? ✅ Return platform content
|
|
❌ Return 404 or default template
|
|
```
|
|
|
|
### Database Schema
|
|
|
|
```sql
|
|
CREATE TABLE content_pages (
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
-- Store association (NULL = platform default)
|
|
store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE,
|
|
|
|
-- Page identification
|
|
slug VARCHAR(100) NOT NULL, -- about, faq, contact, shipping, returns
|
|
title VARCHAR(200) NOT NULL,
|
|
|
|
-- Content
|
|
content TEXT NOT NULL, -- HTML or Markdown
|
|
content_format VARCHAR(20) DEFAULT 'html', -- html, markdown
|
|
|
|
-- SEO
|
|
meta_description VARCHAR(300),
|
|
meta_keywords VARCHAR(300),
|
|
|
|
-- Publishing
|
|
is_published BOOLEAN DEFAULT FALSE NOT NULL,
|
|
published_at TIMESTAMP WITH TIME ZONE,
|
|
|
|
-- Navigation placement
|
|
display_order INTEGER DEFAULT 0,
|
|
show_in_footer BOOLEAN DEFAULT TRUE, -- Quick Links column
|
|
show_in_header BOOLEAN DEFAULT FALSE, -- Top navigation
|
|
show_in_legal BOOLEAN DEFAULT FALSE, -- Bottom bar with copyright
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
|
|
|
-- Author tracking
|
|
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
updated_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Constraints
|
|
CONSTRAINT uq_store_slug UNIQUE (store_id, slug)
|
|
);
|
|
|
|
CREATE INDEX idx_store_published ON content_pages (store_id, is_published);
|
|
CREATE INDEX idx_slug_published ON content_pages (slug, is_published);
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Platform Administrator Workflow
|
|
|
|
**1. Create Platform Default Pages**
|
|
|
|
Platform admins create default content that all stores inherit:
|
|
|
|
```bash
|
|
POST /api/v1/admin/content-pages/platform
|
|
{
|
|
"slug": "about",
|
|
"title": "About Us",
|
|
"content": "<h1>About Us</h1><p>We are a marketplace...</p>",
|
|
"content_format": "html",
|
|
"meta_description": "Learn more about our marketplace",
|
|
"is_published": true,
|
|
"show_in_header": true,
|
|
"show_in_footer": true,
|
|
"show_in_legal": false,
|
|
"display_order": 1
|
|
}
|
|
```
|
|
|
|
**Common Platform Defaults:**
|
|
- `about` - About Us
|
|
- `contact` - Contact Us
|
|
- `faq` - Frequently Asked Questions
|
|
- `shipping` - Shipping Information
|
|
- `returns` - Return Policy
|
|
- `privacy` - Privacy Policy
|
|
- `terms` - Terms of Service
|
|
- `help` - Help Center
|
|
|
|
**2. View All Content Pages**
|
|
|
|
```bash
|
|
GET /api/v1/admin/content-pages/
|
|
GET /api/v1/admin/content-pages/?store_id=123 # Filter by store
|
|
GET /api/v1/admin/content-pages/platform # Only platform defaults
|
|
```
|
|
|
|
**3. Update Platform Default**
|
|
|
|
```bash
|
|
PUT /api/v1/admin/content-pages/1
|
|
{
|
|
"title": "Updated About Us",
|
|
"content": "<h1>About Our Platform</h1>...",
|
|
"is_published": true
|
|
}
|
|
```
|
|
|
|
### Store Workflow
|
|
|
|
**1. View Available Pages**
|
|
|
|
Stores see their overrides + platform defaults:
|
|
|
|
```bash
|
|
GET /api/v1/store/{code}/content-pages/
|
|
```
|
|
|
|
Response:
|
|
```json
|
|
[
|
|
{
|
|
"id": 15,
|
|
"slug": "about",
|
|
"title": "About Orion", // Store override
|
|
"is_store_override": true,
|
|
"is_platform_page": false
|
|
},
|
|
{
|
|
"id": 2,
|
|
"slug": "shipping",
|
|
"title": "Shipping Information", // Platform default
|
|
"is_store_override": false,
|
|
"is_platform_page": true
|
|
}
|
|
]
|
|
```
|
|
|
|
**2. Create Store Override**
|
|
|
|
Store creates custom "About" page:
|
|
|
|
```bash
|
|
POST /api/v1/store/{code}/content-pages/
|
|
{
|
|
"slug": "about",
|
|
"title": "About Orion",
|
|
"content": "<h1>About Orion</h1><p>We specialize in...</p>",
|
|
"is_published": true
|
|
}
|
|
```
|
|
|
|
This overrides the platform default for this store only.
|
|
|
|
**3. View Only Store Overrides**
|
|
|
|
```bash
|
|
GET /api/v1/store/{code}/content-pages/overrides
|
|
```
|
|
|
|
Shows what the store has customized (excludes platform defaults).
|
|
|
|
**4. Delete Override (Revert to Platform Default)**
|
|
|
|
```bash
|
|
DELETE /api/v1/store/{code}/content-pages/15
|
|
```
|
|
|
|
After deletion, platform default will be shown again.
|
|
|
|
### Shop Frontend (Public)
|
|
|
|
**1. Get Page Content**
|
|
|
|
```bash
|
|
GET /api/v1/shop/content-pages/about
|
|
```
|
|
|
|
Automatically uses store context from middleware:
|
|
- Returns store override if exists
|
|
- Falls back to platform default
|
|
- Returns 404 if neither exists
|
|
|
|
**2. Get Navigation Links**
|
|
|
|
```bash
|
|
# Get all navigation pages
|
|
GET /api/v1/shop/content-pages/navigation
|
|
|
|
# Filter by placement
|
|
GET /api/v1/shop/content-pages/navigation?header_only=true
|
|
GET /api/v1/shop/content-pages/navigation?footer_only=true
|
|
GET /api/v1/shop/content-pages/navigation?legal_only=true
|
|
```
|
|
|
|
Returns published pages filtered by navigation placement.
|
|
|
|
## File Structure
|
|
|
|
```
|
|
app/
|
|
├── models/database/
|
|
│ └── content_page.py ← Database model
|
|
│
|
|
├── services/
|
|
│ └── content_page_service.py ← Business logic
|
|
│
|
|
├── api/v1/
|
|
│ ├── admin/
|
|
│ │ └── content_pages.py ← Admin API endpoints
|
|
│ ├── store/
|
|
│ │ └── content_pages.py ← Store API endpoints
|
|
│ └── shop/
|
|
│ └── content_pages.py ← Public API endpoints
|
|
│
|
|
└── templates/shop/
|
|
├── about.html ← Content page template
|
|
├── faq.html
|
|
├── contact.html
|
|
└── ...
|
|
```
|
|
|
|
## Template Integration
|
|
|
|
### Generic Content Page Template
|
|
|
|
Create a reusable template for all content pages:
|
|
|
|
```jinja2
|
|
{# app/templates/shop/content-page.html #}
|
|
{% extends "shop/base.html" %}
|
|
|
|
{% block title %}{{ page.title }}{% endblock %}
|
|
|
|
{% block meta_description %}
|
|
{{ page.meta_description or page.title }}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
|
|
{# Breadcrumbs #}
|
|
<nav class="mb-6">
|
|
<a href="{{ base_url }}" class="text-primary hover:underline">Home</a>
|
|
<span class="mx-2">/</span>
|
|
<span class="text-gray-600">{{ page.title }}</span>
|
|
</nav>
|
|
|
|
{# Page Title #}
|
|
<h1 class="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-8">
|
|
{{ page.title }}
|
|
</h1>
|
|
|
|
{# Content #}
|
|
<div class="prose dark:prose-invert max-w-none">
|
|
{% if page.content_format == 'markdown' %}
|
|
{{ page.content | markdown }}
|
|
{% else %}
|
|
{{ page.content | safe }}
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# Last updated #}
|
|
{% if page.updated_at %}
|
|
<div class="mt-12 pt-6 border-t text-sm text-gray-500">
|
|
Last updated: {{ page.updated_at }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
### Route Handler
|
|
|
|
```python
|
|
# app/routes/shop_pages.py
|
|
|
|
from app.services.content_page_service import content_page_service
|
|
|
|
@router.get("/{slug}", response_class=HTMLResponse)
|
|
async def content_page(
|
|
slug: str,
|
|
request: Request,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Generic content page handler.
|
|
|
|
Loads content from database with store override support.
|
|
"""
|
|
store = getattr(request.state, 'store', None)
|
|
store_id = store.id if store else None
|
|
|
|
page = content_page_service.get_page_for_store(
|
|
db,
|
|
slug=slug,
|
|
store_id=store_id,
|
|
include_unpublished=False
|
|
)
|
|
|
|
if not page:
|
|
raise HTTPException(status_code=404, detail=f"Page not found: {slug}")
|
|
|
|
return templates.TemplateResponse(
|
|
"shop/content-page.html",
|
|
get_shop_context(request, page=page)
|
|
)
|
|
```
|
|
|
|
### Dynamic Footer Navigation
|
|
|
|
Update footer to load links from database:
|
|
|
|
```jinja2
|
|
{# app/templates/shop/base.html #}
|
|
|
|
<footer>
|
|
<div class="grid grid-cols-3">
|
|
|
|
<div>
|
|
<h4>Quick Links</h4>
|
|
<ul>
|
|
{% for page in footer_pages %}
|
|
<li>
|
|
<a href="{{ base_url }}{{ page.slug }}">
|
|
{{ page.title }}
|
|
</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
</div>
|
|
</footer>
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Content Formatting
|
|
|
|
**HTML Content:**
|
|
```html
|
|
<h1>About Us</h1>
|
|
<p>We are a <strong>leading marketplace</strong> for...</p>
|
|
<ul>
|
|
<li>Quality products</li>
|
|
<li>Fast shipping</li>
|
|
<li>Great support</li>
|
|
</ul>
|
|
```
|
|
|
|
**Markdown Content:**
|
|
```markdown
|
|
# About Us
|
|
|
|
We are a **leading marketplace** for...
|
|
|
|
- Quality products
|
|
- Fast shipping
|
|
- Great support
|
|
```
|
|
|
|
### 2. SEO Optimization
|
|
|
|
Always provide meta descriptions:
|
|
|
|
```json
|
|
{
|
|
"meta_description": "Learn about our marketplace, mission, and values. We connect stores with customers worldwide.",
|
|
"meta_keywords": "about us, marketplace, e-commerce, mission"
|
|
}
|
|
```
|
|
|
|
### 3. Draft → Published Workflow
|
|
|
|
1. Create page with `is_published: false`
|
|
2. Preview using `include_unpublished=true` parameter
|
|
3. Review and edit
|
|
4. Publish with `is_published: true`
|
|
|
|
### 4. Navigation Management
|
|
|
|
The CMS supports three navigation placement categories:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ HEADER (show_in_header=true) │
|
|
│ [Logo] About Us Contact [Login] [Sign Up] │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ PAGE CONTENT │
|
|
│ │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ FOOTER (show_in_footer=true) │
|
|
│ ┌──────────────┬──────────────┬────────────┬──────────────┐ │
|
|
│ │ Quick Links │ Platform │ Contact │ Social │ │
|
|
│ │ • About │ • Admin │ • Email │ • Twitter │ │
|
|
│ │ • FAQ │ • Store │ • Phone │ • LinkedIn │ │
|
|
│ │ • Contact │ │ │ │ │
|
|
│ │ • Shipping │ │ │ │ │
|
|
│ └──────────────┴──────────────┴────────────┴──────────────┘ │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ LEGAL BAR (show_in_legal=true) │
|
|
│ © 2025 Orion Privacy Policy │ Terms │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Navigation Categories:**
|
|
|
|
| Category | Field | Location | Typical Pages |
|
|
|----------|-------|----------|---------------|
|
|
| Header | `show_in_header` | Top navigation bar | About, Contact |
|
|
| Footer | `show_in_footer` | Quick Links column | FAQ, Shipping, Returns |
|
|
| Legal | `show_in_legal` | Bottom bar with © | Privacy, Terms |
|
|
|
|
**Use `display_order` to control link ordering within each category:**
|
|
|
|
```python
|
|
# Platform defaults with navigation placement
|
|
"about": display_order=1, show_in_header=True, show_in_footer=True
|
|
"contact": display_order=2, show_in_header=True, show_in_footer=True
|
|
"faq": display_order=3, show_in_footer=True
|
|
"shipping": display_order=4, show_in_footer=True
|
|
"returns": display_order=5, show_in_footer=True
|
|
"privacy": display_order=6, show_in_legal=True
|
|
"terms": display_order=7, show_in_legal=True
|
|
```
|
|
|
|
### 5. Content Reversion
|
|
|
|
To revert store override back to platform default:
|
|
|
|
```bash
|
|
# Store deletes their custom page
|
|
DELETE /api/v1/store/{code}/content-pages/15
|
|
|
|
# Platform default will now be shown automatically
|
|
```
|
|
|
|
## Common Page Slugs
|
|
|
|
Standard slugs to implement:
|
|
|
|
| Slug | Title | Header | Footer | Legal | Order |
|
|
|------|-------|--------|--------|-------|-------|
|
|
| `about` | About Us | ✅ | ✅ | ❌ | 1 |
|
|
| `contact` | Contact Us | ✅ | ✅ | ❌ | 2 |
|
|
| `faq` | FAQ | ❌ | ✅ | ❌ | 3 |
|
|
| `shipping` | Shipping Info | ❌ | ✅ | ❌ | 4 |
|
|
| `returns` | Returns | ❌ | ✅ | ❌ | 5 |
|
|
| `privacy` | Privacy Policy | ❌ | ❌ | ✅ | 6 |
|
|
| `terms` | Terms of Service | ❌ | ❌ | ✅ | 7 |
|
|
| `help` | Help Center | ❌ | ✅ | ❌ | 8 |
|
|
| `size-guide` | Size Guide | ❌ | ❌ | ❌ | - |
|
|
| `careers` | Careers | ❌ | ❌ | ❌ | - |
|
|
| `cookies` | Cookie Policy | ❌ | ❌ | ✅ | 8 |
|
|
|
|
## Security Considerations
|
|
|
|
1. **HTML Sanitization**: If using HTML format, sanitize user input to prevent XSS
|
|
2. **Authorization**: Stores can only edit their own pages
|
|
3. **Published Status**: Only published pages visible to public
|
|
4. **Store Isolation**: Stores cannot see/edit other store's content
|
|
|
|
## Migration Strategy
|
|
|
|
### Initial Setup
|
|
|
|
1. **Create Platform Defaults**:
|
|
```bash
|
|
python scripts/seed/create_default_content_pages.py
|
|
```
|
|
|
|
2. **Migrate Existing Static Templates**:
|
|
- Convert existing HTML templates to database content
|
|
- Preserve existing URLs and SEO
|
|
|
|
3. **Update Routes**:
|
|
- Add generic content page route handler
|
|
- Remove individual route handlers for each page
|
|
|
|
## Future Enhancements
|
|
|
|
Possible improvements:
|
|
|
|
- **Version History**: Track content changes over time
|
|
- **Rich Text Editor**: WYSIWYG editor in admin/store panel
|
|
- **Image Management**: Upload and insert images
|
|
- **Templates**: Pre-built page templates for common pages
|
|
- **Localization**: Multi-language content support
|
|
- **Scheduled Publishing**: Publish pages at specific times
|
|
- **Content Approval**: Admin review before store pages go live
|
|
|
|
## API Reference Summary
|
|
|
|
### Admin Endpoints
|
|
|
|
```
|
|
GET /api/v1/admin/content-pages/ # List all pages
|
|
GET /api/v1/admin/content-pages/platform # List platform defaults
|
|
POST /api/v1/admin/content-pages/platform # Create platform default
|
|
GET /api/v1/admin/content-pages/{id} # Get specific page
|
|
PUT /api/v1/admin/content-pages/{id} # Update page
|
|
DELETE /api/v1/admin/content-pages/{id} # Delete page
|
|
```
|
|
|
|
### Store Endpoints
|
|
|
|
```
|
|
GET /api/v1/store/{code}/content-pages/ # List all (store + platform)
|
|
GET /api/v1/store/{code}/content-pages/overrides # List store overrides only
|
|
GET /api/v1/store/{code}/content-pages/{slug} # Get specific page
|
|
POST /api/v1/store/{code}/content-pages/ # Create store override
|
|
PUT /api/v1/store/{code}/content-pages/{id} # Update store page
|
|
DELETE /api/v1/store/{code}/content-pages/{id} # Delete store page
|
|
```
|
|
|
|
### Shop (Public) Endpoints
|
|
|
|
```
|
|
GET /api/v1/shop/content-pages/navigation # Get navigation links
|
|
GET /api/v1/shop/content-pages/{slug} # Get page content
|
|
```
|
|
|
|
## Example: Complete Workflow
|
|
|
|
**1. Platform Admin Creates Defaults:**
|
|
```bash
|
|
# Create "About" page
|
|
curl -X POST /api/v1/admin/content-pages/platform \
|
|
-H "Authorization: Bearer <admin_token>" \
|
|
-d '{
|
|
"slug": "about",
|
|
"title": "About Our Marketplace",
|
|
"content": "<h1>About</h1><p>Default content...</p>",
|
|
"is_published": true
|
|
}'
|
|
```
|
|
|
|
**2. All Stores See Platform Default:**
|
|
- Store A visits: `store-a.com/about` → Shows platform default
|
|
- Store B visits: `store-b.com/about` → Shows platform default
|
|
|
|
**3. Store A Creates Override:**
|
|
```bash
|
|
curl -X POST /api/v1/store/store-a/content-pages/ \
|
|
-H "Authorization: Bearer <store_token>" \
|
|
-d '{
|
|
"slug": "about",
|
|
"title": "About Store A",
|
|
"content": "<h1>About Store A</h1><p>Custom content...</p>",
|
|
"is_published": true
|
|
}'
|
|
```
|
|
|
|
**4. Now:**
|
|
- Store A visits: `store-a.com/about` → Shows Store A custom content
|
|
- Store B visits: `store-b.com/about` → Still shows platform default
|
|
|
|
**5. Store A Reverts to Default:**
|
|
```bash
|
|
curl -X DELETE /api/v1/store/store-a/content-pages/15 \
|
|
-H "Authorization: Bearer <store_token>"
|
|
```
|
|
|
|
**6. Result:**
|
|
- Store A visits: `store-a.com/about` → Shows platform default again
|