# 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 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": "

About Orion

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. ### Storefront (Public) **1. Get Page Content** ```bash GET /api/v1/storefront/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/storefront/content-pages/navigation # Filter by placement GET /api/v1/storefront/content-pages/navigation?header_only=true GET /api/v1/storefront/content-pages/navigation?footer_only=true GET /api/v1/storefront/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 │ └── storefront/ │ └── content_pages.py ← Public API endpoints │ └── templates/storefront/ ├── 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/storefront/content-page.html #} {% extends "storefront/base.html" %} {% block title %}{{ page.title }}{% endblock %} {% block meta_description %} {{ page.meta_description or page.title }} {% endblock %} {% block content %}
{# Breadcrumbs #} {# 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/storefront_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( "storefront/content-page.html", get_storefront_context(request, page=page) ) ``` ### Dynamic Footer Navigation Update footer to load links from database: ```jinja2 {# app/templates/storefront/base.html #} ``` ## Best Practices ### 1. Content Formatting **HTML Content:** ```html

About Us

We are a leading marketplace for...

``` **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 ``` ### Storefront (Public) Endpoints ``` GET /api/v1/storefront/content-pages/navigation # Get navigation links GET /api/v1/storefront/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