- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter) - Add comprehensive pyproject.toml configuration - Simplify Makefile code quality targets - Configure exclusions for venv/.venv in pyproject.toml - Auto-fix 1,359 linting issues across codebase Benefits: - Much faster builds (Ruff is written in Rust) - Single tool replaces multiple tools - More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q) - All configuration centralized in pyproject.toml - Better import sorting and formatting consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
4.6 KiB
Python
149 lines
4.6 KiB
Python
# 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 fastapi import Request
|
|
from sqlalchemy.orm import Session
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
|
|
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(
|
|
"[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())
|