Some checks failed
- Add Development URL Quick Reference section to url-routing overview with all login URLs, entry points, and full examples - Replace /shop/ path segments with /storefront/ across 50 docs files - Update file references: shop_pages.py → storefront_pages.py, templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/ - Preserve domain references (orion.shop) and /store/ staff dashboard paths - Archive docs left unchanged (historical) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
415 lines
11 KiB
Markdown
415 lines
11 KiB
Markdown
# 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!
|