Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. 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`
|
|
✅ **Shop API**: `app/api/v1/shop/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"]
|
|
)
|
|
```
|
|
|
|
**Shop Router** (`app/api/v1/shop/__init__.py` or create if doesn't exist):
|
|
```python
|
|
from app.api.v1.shop import content_pages
|
|
|
|
api_router.include_router(
|
|
content_pages.router,
|
|
prefix="/content-pages",
|
|
tags=["shop-content-pages"]
|
|
)
|
|
```
|
|
|
|
### 4. Update Shop Routes to Use CMS
|
|
|
|
Edit `app/routes/shop_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(
|
|
"shop/content-page.html",
|
|
get_shop_context(request, page=page)
|
|
)
|
|
```
|
|
|
|
### 5. Create Generic Content Page Template
|
|
|
|
Create `app/templates/shop/content-page.html`:
|
|
|
|
```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 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/shop/base.html` to load navigation from database.
|
|
|
|
First, update the context helper to include footer pages:
|
|
|
|
```python
|
|
# app/routes/shop_pages.py
|
|
|
|
def get_shop_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/shop/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 shop
|
|
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 shop (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 shop (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!
|