docs: migrate module documentation to single source of truth
Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
414
app/modules/cms/docs/implementation.md
Normal file
414
app/modules/cms/docs/implementation.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# CMS Implementation Guide
|
||||
|
||||
## Quick Start
|
||||
|
||||
This guide shows you how to implement the Content Management System for static pages.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
✅ **Database Model**: `models/database/content_page.py`
|
||||
✅ **Service Layer**: `app/services/content_page_service.py`
|
||||
✅ **Admin API**: `app/api/v1/admin/content_pages.py`
|
||||
✅ **Store API**: `app/api/v1/store/content_pages.py`
|
||||
✅ **Storefront API**: `app/api/v1/storefront/content_pages.py`
|
||||
✅ **Documentation**: Full CMS documentation in `docs/features/content-management-system.md`
|
||||
|
||||
## Next Steps to Activate
|
||||
|
||||
### 1. Create Database Migration
|
||||
|
||||
```bash
|
||||
# Create Alembic migration
|
||||
alembic revision --autogenerate -m "Add content_pages table"
|
||||
|
||||
# Review the generated migration in alembic/versions/
|
||||
|
||||
# Run migration
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### 2. Add Relationship to Store Model
|
||||
|
||||
Edit `models/database/store.py` and add this relationship:
|
||||
|
||||
```python
|
||||
# Add this import
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
# Add this relationship to Store class
|
||||
content_pages = relationship("ContentPage", back_populates="store", cascade="all, delete-orphan")
|
||||
```
|
||||
|
||||
### 3. Register API Routers
|
||||
|
||||
Edit the appropriate router files to include the new endpoints:
|
||||
|
||||
**Admin Router** (`app/api/v1/admin/__init__.py`):
|
||||
```python
|
||||
from app.api.v1.admin import content_pages
|
||||
|
||||
api_router.include_router(
|
||||
content_pages.router,
|
||||
prefix="/content-pages",
|
||||
tags=["admin-content-pages"]
|
||||
)
|
||||
```
|
||||
|
||||
**Store Router** (`app/api/v1/store/__init__.py`):
|
||||
```python
|
||||
from app.api.v1.store import content_pages
|
||||
|
||||
api_router.include_router(
|
||||
content_pages.router,
|
||||
prefix="/{store_code}/content-pages",
|
||||
tags=["store-content-pages"]
|
||||
)
|
||||
```
|
||||
|
||||
**Storefront Router** (`app/api/v1/storefront/__init__.py` or create if doesn't exist):
|
||||
```python
|
||||
from app.api.v1.storefront import content_pages
|
||||
|
||||
api_router.include_router(
|
||||
content_pages.router,
|
||||
prefix="/content-pages",
|
||||
tags=["storefront-content-pages"]
|
||||
)
|
||||
```
|
||||
|
||||
### 4. Update Storefront Routes to Use CMS
|
||||
|
||||
Edit `app/routes/storefront_pages.py` to add a generic content page handler:
|
||||
|
||||
```python
|
||||
from app.services.content_page_service import content_page_service
|
||||
|
||||
@router.get("/{slug}", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def generic_content_page(
|
||||
slug: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Generic content page handler.
|
||||
Handles: /about, /faq, /contact, /shipping, /returns, /privacy, /terms, etc.
|
||||
"""
|
||||
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)
|
||||
)
|
||||
```
|
||||
|
||||
### 5. Create Generic Content Page Template
|
||||
|
||||
Create `app/templates/storefront/content-page.html`:
|
||||
|
||||
```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 %}
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
|
||||
{# Breadcrumbs #}
|
||||
<nav class="mb-6 text-sm">
|
||||
<a href="{{ base_url }}" class="text-primary hover:underline">Home</a>
|
||||
<span class="mx-2 text-gray-400">/</span>
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ 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 prose-lg dark:prose-invert max-w-none">
|
||||
{{ page.content | safe }}
|
||||
</div>
|
||||
|
||||
{# Last updated #}
|
||||
{% if page.updated_at %}
|
||||
<div class="mt-12 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400">
|
||||
Last updated: {{ page.updated_at.strftime('%B %d, %Y') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### 6. Update Footer to Load Navigation Dynamically
|
||||
|
||||
Edit `app/templates/storefront/base.html` to load navigation from database.
|
||||
|
||||
First, update the context helper to include footer pages:
|
||||
|
||||
```python
|
||||
# app/routes/storefront_pages.py
|
||||
|
||||
def get_storefront_context(request: Request, **extra_context) -> dict:
|
||||
# ... existing code ...
|
||||
|
||||
# Load footer navigation pages
|
||||
db = next(get_db())
|
||||
try:
|
||||
footer_pages = content_page_service.list_pages_for_store(
|
||||
db,
|
||||
store_id=store.id if store else None,
|
||||
include_unpublished=False,
|
||||
footer_only=True
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
context = {
|
||||
"request": request,
|
||||
"store": store,
|
||||
"theme": theme,
|
||||
"clean_path": clean_path,
|
||||
"access_method": access_method,
|
||||
"base_url": base_url,
|
||||
"footer_pages": footer_pages, # Add this
|
||||
**extra_context
|
||||
}
|
||||
|
||||
return context
|
||||
```
|
||||
|
||||
Then update the footer template:
|
||||
|
||||
```jinja2
|
||||
{# app/templates/storefront/base.html - Footer section #}
|
||||
|
||||
<div>
|
||||
<h4 class="font-semibold mb-4">Quick Links</h4>
|
||||
<ul class="space-y-2">
|
||||
{% for page in footer_pages %}
|
||||
<li>
|
||||
<a href="{{ base_url }}{{ page.slug }}"
|
||||
class="text-gray-600 hover:text-primary dark:text-gray-400">
|
||||
{{ page.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 7. Create Default Platform Pages (Script)
|
||||
|
||||
Create `scripts/seed/create_default_content_pages.py`:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Create default platform content pages."""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.database import SessionLocal
|
||||
from app.services.content_page_service import content_page_service
|
||||
|
||||
def create_defaults():
|
||||
db: Session = SessionLocal()
|
||||
|
||||
try:
|
||||
# About Us
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="about",
|
||||
title="About Us",
|
||||
content="""
|
||||
<h2>Welcome to Our Marketplace</h2>
|
||||
<p>We connect quality stores with customers worldwide.</p>
|
||||
<p>Our mission is to provide a seamless shopping experience...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=1
|
||||
)
|
||||
|
||||
# Shipping Information
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="shipping",
|
||||
title="Shipping Information",
|
||||
content="""
|
||||
<h2>Shipping Policy</h2>
|
||||
<p>We offer fast and reliable shipping...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=2
|
||||
)
|
||||
|
||||
# Returns
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="returns",
|
||||
title="Returns & Refunds",
|
||||
content="""
|
||||
<h2>Return Policy</h2>
|
||||
<p>30-day return policy on all items...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=3
|
||||
)
|
||||
|
||||
# Privacy Policy
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="privacy",
|
||||
title="Privacy Policy",
|
||||
content="""
|
||||
<h2>Privacy Policy</h2>
|
||||
<p>Your privacy is important to us...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=4
|
||||
)
|
||||
|
||||
# Terms of Service
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="terms",
|
||||
title="Terms of Service",
|
||||
content="""
|
||||
<h2>Terms of Service</h2>
|
||||
<p>By using our platform, you agree to...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=5
|
||||
)
|
||||
|
||||
# Contact
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="contact",
|
||||
title="Contact Us",
|
||||
content="""
|
||||
<h2>Get in Touch</h2>
|
||||
<p>Have questions? We'd love to hear from you!</p>
|
||||
<p>Email: support@example.com</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=6
|
||||
)
|
||||
|
||||
# FAQ
|
||||
content_page_service.create_page(
|
||||
db,
|
||||
slug="faq",
|
||||
title="Frequently Asked Questions",
|
||||
content="""
|
||||
<h2>FAQ</h2>
|
||||
<h3>How do I place an order?</h3>
|
||||
<p>Simply browse our products...</p>
|
||||
""",
|
||||
is_published=True,
|
||||
show_in_footer=True,
|
||||
display_order=7
|
||||
)
|
||||
|
||||
print("✅ Created default content pages successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_defaults()
|
||||
```
|
||||
|
||||
Run it:
|
||||
```bash
|
||||
python scripts/seed/create_default_content_pages.py
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### 1. Test Platform Defaults
|
||||
|
||||
```bash
|
||||
# Create platform default
|
||||
curl -X POST http://localhost:8000/api/v1/admin/content-pages/platform \
|
||||
-H "Authorization: Bearer <admin_token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"slug": "about",
|
||||
"title": "About Our Marketplace",
|
||||
"content": "<h1>About</h1><p>Platform default content</p>",
|
||||
"is_published": true,
|
||||
"show_in_footer": true
|
||||
}'
|
||||
|
||||
# View in storefront
|
||||
curl http://localhost:8000/store/orion/about
|
||||
```
|
||||
|
||||
### 2. Test Store Override
|
||||
|
||||
```bash
|
||||
# Create store override
|
||||
curl -X POST http://localhost:8000/api/v1/store/orion/content-pages/ \
|
||||
-H "Authorization: Bearer <store_token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"slug": "about",
|
||||
"title": "About Orion",
|
||||
"content": "<h1>About Orion</h1><p>Custom store content</p>",
|
||||
"is_published": true
|
||||
}'
|
||||
|
||||
# View in storefront (should show store content)
|
||||
curl http://localhost:8000/store/orion/about
|
||||
```
|
||||
|
||||
### 3. Test Fallback
|
||||
|
||||
```bash
|
||||
# Delete store override
|
||||
curl -X DELETE http://localhost:8000/api/v1/store/orion/content-pages/{id} \
|
||||
-H "Authorization: Bearer <store_token>"
|
||||
|
||||
# View in storefront (should fall back to platform default)
|
||||
curl http://localhost:8000/store/orion/about
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
You now have a complete CMS system that allows:
|
||||
|
||||
1. **Platform admins** to create default content for all stores
|
||||
2. **Stores** to override specific pages with custom content
|
||||
3. **Automatic fallback** to platform defaults when store hasn't customized
|
||||
4. **Dynamic navigation** loading from database
|
||||
5. **SEO optimization** with meta tags
|
||||
6. **Draft/Published workflow** for content management
|
||||
|
||||
All pages are accessible via their slug: `/about`, `/faq`, `/contact`, etc. with proper store context and routing support!
|
||||
Reference in New Issue
Block a user