# app/modules/cms/routes/pages/storefront.py """ CMS Storefront Page Routes (HTML rendering). Storefront (customer shop) pages for CMS content: - Generic content pages (/{slug} catch-all) - Debug context endpoint """ import logging from fastapi import APIRouter, Depends, HTTPException, Path, Request from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session from app.api.deps import get_db from app.modules.cms.services import content_page_service from app.modules.core.utils.page_context import get_storefront_context from app.templates_config import templates logger = logging.getLogger(__name__) router = APIRouter() # Route configuration - high priority so catch-all is registered last ROUTE_CONFIG = { "priority": 100, } # ============================================================================ # STOREFRONT HOMEPAGE # ============================================================================ @router.get("/", response_class=HTMLResponse, include_in_schema=False) async def storefront_homepage( request: Request, db: Session = Depends(get_db), ): """ Storefront homepage handler. Looks for a CMS page with slug="home" (store override → store default), and renders the appropriate landing template. Falls back to the default landing template when no CMS homepage exists. """ store = getattr(request.state, "store", None) platform = getattr(request.state, "platform", None) store_id = store.id if store else None if not platform: raise HTTPException(status_code=400, detail="Platform context required") # Try to load a homepage from CMS (store override → store default) page = content_page_service.get_page_for_store( db, platform_id=platform.id, slug="home", store_id=store_id, include_unpublished=False, ) # Resolve placeholders for store default pages (title + content) page_content = None page_title = None if page: page_content = page.content page_title = page.title if page.is_store_default and store: page_content = content_page_service.resolve_placeholders( page.content, store ) page_title = content_page_service.resolve_placeholders( page.title, store ) context = get_storefront_context(request, db=db, page=page) if page_content: context["page_content"] = page_content if page_title: context["page_title"] = page_title # Select template based on page.template field (or default) template_map = { "full": "cms/storefront/landing-full.html", "modern": "cms/storefront/landing-modern.html", "minimal": "cms/storefront/landing-minimal.html", } template_name = "cms/storefront/landing-default.html" if page and page.template: template_name = template_map.get(page.template, template_name) return templates.TemplateResponse(template_name, context) # ============================================================================ # DYNAMIC CONTENT PAGES (CMS) # ============================================================================ @router.get("/{slug}", response_class=HTMLResponse, include_in_schema=False) async def generic_content_page( request: Request, slug: str = Path(..., description="Content page slug"), db: Session = Depends(get_db), ): """ Generic content page handler (CMS). Handles dynamic content pages like: - /about, /faq, /contact, /shipping, /returns, /privacy, /terms, etc. Features: - Two-tier system: Store overrides take priority, fallback to platform defaults - Only shows published pages - Returns 404 if page not found This route MUST be defined last in the router to avoid conflicts with specific routes (like /products, /cart, /account, etc.) """ logger.debug( "[CMS_STOREFRONT] generic_content_page REACHED", extra={ "path": request.url.path, "slug": slug, "store": getattr(request.state, "store", "NOT SET"), "context": getattr(request.state, "context_type", "NOT SET"), }, ) store = getattr(request.state, "store", None) platform = getattr(request.state, "platform", None) store_id = store.id if store else None if not platform: raise HTTPException(status_code=400, detail="Platform context required") platform_id = platform.id # Load content page from database (store override -> store default) page = content_page_service.get_page_for_store( db, platform_id=platform_id, slug=slug, store_id=store_id, include_unpublished=False, ) if not page: logger.warning( "[CMS_STOREFRONT] Content page not found", extra={ "slug": slug, "store_id": store_id, "store_name": store.name if store else None, }, ) raise HTTPException(status_code=404, detail=f"Page not found: {slug}") logger.info( "[CMS_STOREFRONT] Content page found", extra={ "slug": slug, "page_id": page.id, "page_title": page.title, "is_store_override": page.store_id is not None, "store_id": store_id, }, ) # Resolve placeholders in store default pages ({{store_name}}, etc.) page_content = page.content page_title = page.title if page.is_store_default and store: page_content = content_page_service.resolve_placeholders(page.content, store) page_title = content_page_service.resolve_placeholders(page.title, store) context = get_storefront_context(request, db=db, page=page) context["page_title"] = page_title context["page_content"] = page_content # Select template based on page.template field template_map = { "full": "cms/storefront/landing-full.html", "modern": "cms/storefront/landing-modern.html", "minimal": "cms/storefront/landing-minimal.html", } template_name = template_map.get(page.template, "cms/storefront/content-page.html") return templates.TemplateResponse( template_name, context, ) # ============================================================================ # DEBUG ENDPOINTS - For troubleshooting context issues # ============================================================================ @router.get("/debug/context", response_class=HTMLResponse, include_in_schema=False) async def debug_context(request: Request): """ DEBUG ENDPOINT: Display request context. Shows what's available in request.state. Useful for troubleshooting template variable issues. URL: /storefront/debug/context """ import json store = getattr(request.state, "store", None) theme = getattr(request.state, "theme", None) debug_info = { "path": request.url.path, "host": request.headers.get("host", ""), "store": { "found": store is not None, "id": store.id if store else None, "name": store.name if store else None, "subdomain": store.subdomain if store else None, "is_active": store.is_active if store else None, }, "theme": { "found": theme is not None, "name": theme.get("theme_name") if theme else None, }, "clean_path": getattr(request.state, "clean_path", "NOT SET"), "context_type": str(getattr(request.state, "context_type", "NOT SET")), } html_content = f"""
{json.dumps(debug_info, indent=2)}
Store: {"Found" if store else "Not Found"}
Theme: {"Found" if theme else "Not Found"}
Context Type: {str(getattr(request.state, "context_type", "NOT SET"))}
""" return HTMLResponse(content=html_content)