# 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": "
About Us We are a marketplace...
",
"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": "About Our Platform ...",
"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 Wizamart", // Store override
"is_store_override": true,
"is_platform_default": false
},
{
"id": 2,
"slug": "shipping",
"title": "Shipping Information", // Platform default
"is_store_override": false,
"is_platform_default": true
}
]
```
**2. Create Store Override**
Store creates custom "About" page:
```bash
POST /api/v1/store/{code}/content-pages/
{
"slug": "about",
"title": "About Wizamart",
"content": "About Wizamart We specialize in...
",
"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 %}
{# Breadcrumbs #}
Home
/
{{ page.title }}
{# Page Title #}
{{ page.title }}
{# Content #}
{% if page.content_format == 'markdown' %}
{{ page.content | markdown }}
{% else %}
{{ page.content | safe }}
{% endif %}
{# Last updated #}
{% if page.updated_at %}
Last updated: {{ page.updated_at }}
{% endif %}
{% 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 #}
```
## Best Practices
### 1. Content Formatting
**HTML Content:**
```html
About Us
We are a leading marketplace for...
Quality products
Fast shipping
Great support
```
**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 Wizamart 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 " \
-d '{
"slug": "about",
"title": "About Our Marketplace",
"content": "About Default content...
",
"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 " \
-d '{
"slug": "about",
"title": "About Store A",
"content": "About Store A Custom content...
",
"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 "
```
**6. Result:**
- Store A visits: `store-a.com/about` → Shows platform default again