# 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
