Files
orion/app/modules/cms/routes/api/shop.py
Samir Boulahtit ec4ec045fc feat: complete CMS as fully autonomous self-contained module
Transform CMS from a thin wrapper into a fully self-contained module with
all code living within app/modules/cms/:

Module Structure:
- models/: ContentPage model (canonical location with dynamic discovery)
- schemas/: Pydantic schemas for API validation
- services/: ContentPageService business logic
- exceptions/: Module-specific exceptions
- routes/api/: REST API endpoints (admin, vendor, shop)
- routes/pages/: HTML page routes (admin, vendor)
- templates/cms/: Jinja2 templates (namespaced)
- static/: JavaScript files (admin/vendor)
- locales/: i18n translations (en, fr, de, lb)

Key Changes:
- Move ContentPage model to module with dynamic model discovery
- Create Pydantic schemas package for request/response validation
- Extract API routes from app/api/v1/*/ to module
- Extract page routes from admin_pages.py/vendor_pages.py to module
- Move static JS files to module with dedicated mount point
- Update templates to use cms_static for module assets
- Add module static file mounting in main.py
- Delete old scattered files (no shims - hard errors on old imports)

This establishes the pattern for migrating other modules to be
fully autonomous and independently deployable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 22:42:46 +01:00

85 lines
2.5 KiB
Python

# app/modules/cms/routes/api/shop.py
"""
Shop Content Pages API (Public)
Public endpoints for retrieving content pages in shop frontend.
No authentication required.
"""
import logging
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.modules.cms.schemas import (
PublicContentPageResponse,
ContentPageListItem,
)
from app.modules.cms.services import content_page_service
router = APIRouter()
logger = logging.getLogger(__name__)
# ============================================================================
# PUBLIC ENDPOINTS
# ============================================================================
@router.get("/navigation", response_model=list[ContentPageListItem])
def get_navigation_pages(request: Request, db: Session = Depends(get_db)):
"""
Get list of content pages for navigation (footer/header).
Uses vendor from request.state (set by middleware).
Returns vendor overrides + platform defaults.
"""
vendor = getattr(request.state, "vendor", None)
vendor_id = vendor.id if vendor else None
# Get all published pages for this vendor
pages = content_page_service.list_pages_for_vendor(
db, vendor_id=vendor_id, include_unpublished=False
)
return [
{
"slug": page.slug,
"title": page.title,
"show_in_footer": page.show_in_footer,
"show_in_header": page.show_in_header,
"display_order": page.display_order,
}
for page in pages
]
@router.get("/{slug}", response_model=PublicContentPageResponse)
def get_content_page(slug: str, request: Request, db: Session = Depends(get_db)):
"""
Get a specific content page by slug.
Uses vendor from request.state (set by middleware).
Returns vendor override if exists, otherwise platform default.
"""
vendor = getattr(request.state, "vendor", None)
vendor_id = vendor.id if vendor else None
page = content_page_service.get_page_for_vendor_or_raise(
db,
slug=slug,
vendor_id=vendor_id,
include_unpublished=False, # Only show published pages
)
return {
"slug": page.slug,
"title": page.title,
"content": page.content,
"content_format": page.content_format,
"meta_description": page.meta_description,
"meta_keywords": page.meta_keywords,
"published_at": page.published_at.isoformat() if page.published_at else None,
}