Files
orion/app/api/v1/shop/content_pages.py
Samir Boulahtit 8a367077e1 refactor: migrate vendor APIs to token-based context and consolidate architecture
## Vendor-in-Token Architecture (Complete Migration)
- Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id
- Update permission dependencies to extract vendor from JWT token
- Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException,
  InsufficientVendorPermissionsException
- Shop endpoints retain require_vendor_context() for URL-based detection
- Add AUTH-004 architecture rule enforcing vendor context patterns
- Fix marketplace router missing /marketplace prefix

## Exception Pattern Fixes (API-003/API-004)
- Services raise domain exceptions, endpoints let them bubble up
- Add code_quality and content_page exception modules
- Move business logic from endpoints to services (admin, auth, content_page)
- Fix exception handling in admin, shop, and vendor endpoints

## Tailwind CSS Consolidation
- Consolidate CSS to per-area files (admin, vendor, shop, platform)
- Remove shared/cdn-fallback.html and shared/css/tailwind.min.css
- Update all templates to use area-specific Tailwind output files
- Remove Node.js config (package.json, postcss.config.js, tailwind.config.js)

## Documentation & Cleanup
- Update vendor-in-token-architecture.md with completed migration status
- Update architecture-rules.md with new rules
- Move migration docs to docs/development/migration/
- Remove duplicate/obsolete documentation files
- Merge pytest.ini settings into pyproject.toml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 22:24:45 +01:00

109 lines
3.0 KiB
Python

# app/api/v1/shop/content_pages.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 pydantic import BaseModel
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.services.content_page_service import content_page_service
router = APIRouter()
logger = logging.getLogger(__name__)
# ============================================================================
# RESPONSE SCHEMAS
# ============================================================================
class PublicContentPageResponse(BaseModel):
"""Public content page response (no internal IDs)."""
slug: str
title: str
content: str
content_format: str
meta_description: str | None
meta_keywords: str | None
published_at: str | None
class ContentPageListItem(BaseModel):
"""Content page list item for navigation."""
slug: str
title: str
show_in_footer: bool
show_in_header: bool
display_order: int
# ============================================================================
# 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,
}