# middleware/theme_context.py """ Theme Context Middleware (Class-Based) Injects vendor-specific theme into request context. Class-based middleware provides: - Better state management - Easier testing - Standard ASGI pattern """ import logging from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request from sqlalchemy.orm import Session from app.core.database import get_db from models.database.vendor_theme import VendorTheme logger = logging.getLogger(__name__) class ThemeContextManager: """Manages theme context for vendor shops.""" @staticmethod def get_vendor_theme(db: Session, vendor_id: int) -> dict: """ Get theme configuration for vendor. Returns default theme if no custom theme is configured. """ theme = db.query(VendorTheme).filter( VendorTheme.vendor_id == vendor_id, VendorTheme.is_active == True ).first() if theme: return theme.to_dict() # Return default theme return ThemeContextManager.get_default_theme() @staticmethod def get_default_theme() -> dict: """Default theme configuration""" return { "theme_name": "default", "colors": { "primary": "#6366f1", "secondary": "#8b5cf6", "accent": "#ec4899", "background": "#ffffff", "text": "#1f2937", "border": "#e5e7eb" }, "fonts": { "heading": "Inter, sans-serif", "body": "Inter, sans-serif" }, "branding": { "logo": None, "logo_dark": None, "favicon": None, "banner": None }, "layout": { "style": "grid", "header": "fixed", "product_card": "modern" }, "social_links": {}, "custom_css": None, "css_variables": { "--color-primary": "#6366f1", "--color-secondary": "#8b5cf6", "--color-accent": "#ec4899", "--color-background": "#ffffff", "--color-text": "#1f2937", "--color-border": "#e5e7eb", "--font-heading": "Inter, sans-serif", "--font-body": "Inter, sans-serif", } } class ThemeContextMiddleware(BaseHTTPMiddleware): """ Middleware to inject theme context into request state. Class-based middleware provides: - Better state management - Easier testing - Standard ASGI pattern Runs LAST in middleware chain (after vendor_context_middleware and context_middleware). Depends on: request.state.vendor (set by vendor_context_middleware) Sets: request.state.theme: Theme dictionary """ async def dispatch(self, request: Request, call_next): """ Load and inject theme context. """ # Only inject theme for shop pages (not admin or API) if hasattr(request.state, 'vendor') and request.state.vendor: vendor = request.state.vendor # Get database session db_gen = get_db() db = next(db_gen) try: # Get vendor theme theme = ThemeContextManager.get_vendor_theme(db, vendor.id) request.state.theme = theme logger.debug( f"[THEME] Theme loaded for vendor", extra={ "vendor_id": vendor.id, "vendor_name": vendor.name, "theme_name": theme.get('theme_name', 'default'), } ) except Exception as e: logger.error( f"[THEME] Failed to load theme for vendor {vendor.id}: {e}", exc_info=True ) # Fallback to default theme request.state.theme = ThemeContextManager.get_default_theme() finally: db.close() else: # No vendor context, use default theme request.state.theme = ThemeContextManager.get_default_theme() logger.debug( "[THEME] No vendor context, using default theme", extra={"has_vendor": False} ) # Continue processing response = await call_next(request) return response def get_current_theme(request: Request) -> dict: """Helper function to get current theme from request state.""" return getattr(request.state, "theme", ThemeContextManager.get_default_theme())