Files
orion/app/api/v1/admin/vendor_themes.py
Samir Boulahtit 9920430b9e fix: correct tojson|safe usage in templates and update validator
- Remove |safe from |tojson in HTML attributes (x-data) - quotes must
  become " for browsers to parse correctly
- Update LANG-002 and LANG-003 architecture rules to document correct
  |tojson usage patterns:
  - HTML attributes: |tojson (no |safe)
  - Script blocks: |tojson|safe
- Fix validator to warn when |tojson|safe is used in x-data (breaks
  HTML attribute parsing)
- Improve code quality across services, APIs, and tests

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:59:51 +01:00

235 lines
7.7 KiB
Python

# app/api/v1/admin/vendor_themes.py
"""
Vendor theme management endpoints for admin.
These endpoints allow admins to:
- View vendor themes
- Apply theme presets
- Customize theme settings
- Reset themes to default
All operations use the service layer for business logic.
All exceptions are handled by the global exception handler.
"""
import logging
from fastapi import APIRouter, Depends, Path
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api, get_db
from app.services.vendor_theme_service import vendor_theme_service
from models.database.user import User
from models.schema.vendor_theme import (
ThemeDeleteResponse,
ThemePresetListResponse,
ThemePresetResponse,
VendorThemeResponse,
VendorThemeUpdate,
)
router = APIRouter(prefix="/vendor-themes")
logger = logging.getLogger(__name__)
# ============================================================================
# PRESET ENDPOINTS
# ============================================================================
@router.get("/presets", response_model=ThemePresetListResponse)
async def get_theme_presets(current_admin: User = Depends(get_current_admin_api)):
"""
Get all available theme presets with preview information.
Returns list of presets that can be applied to vendor themes.
Each preset includes color palette, fonts, and layout configuration.
**Permissions:** Admin only
**Returns:**
- List of available theme presets with preview data
"""
logger.info("Getting theme presets")
presets = vendor_theme_service.get_available_presets()
return ThemePresetListResponse(presets=presets)
# ============================================================================
# THEME RETRIEVAL
# ============================================================================
@router.get("/{vendor_code}", response_model=VendorThemeResponse)
async def get_vendor_theme(
vendor_code: str = Path(..., description="Vendor code"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Get theme configuration for a vendor.
Returns the vendor's custom theme if exists, otherwise returns default theme.
**Path Parameters:**
- `vendor_code`: Vendor code (e.g., VENDOR001)
**Permissions:** Admin only
**Returns:**
- Complete theme configuration including colors, fonts, layout, and branding
**Errors:**
- `404`: Vendor not found (VendorNotFoundException)
"""
logger.info(f"Getting theme for vendor: {vendor_code}")
# Service raises VendorNotFoundException if vendor not found
# Global exception handler converts it to HTTP 404
theme = vendor_theme_service.get_theme(db, vendor_code)
return theme
# ============================================================================
# THEME UPDATE
# ============================================================================
@router.put("/{vendor_code}", response_model=VendorThemeResponse)
async def update_vendor_theme(
vendor_code: str = Path(..., description="Vendor code"),
theme_data: VendorThemeUpdate = None,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Update or create theme for a vendor.
Accepts partial updates - only provided fields are updated.
If vendor has no theme, a new one is created.
**Path Parameters:**
- `vendor_code`: Vendor code (e.g., VENDOR001)
**Request Body:**
- `theme_name`: Optional theme name
- `colors`: Optional color palette (primary, secondary, accent, etc.)
- `fonts`: Optional font settings (heading, body)
- `layout`: Optional layout settings (style, header, product_card)
- `branding`: Optional branding assets (logo, favicon, etc.)
- `custom_css`: Optional custom CSS rules
- `social_links`: Optional social media links
**Permissions:** Admin only
**Returns:**
- Updated theme configuration
**Errors:**
- `404`: Vendor not found (VendorNotFoundException)
- `422`: Validation error (ThemeValidationException, InvalidColorFormatException, etc.)
- `500`: Operation failed (ThemeOperationException)
"""
logger.info(f"Updating theme for vendor: {vendor_code}")
# Service handles all validation and raises appropriate exceptions
# Global exception handler converts them to proper HTTP responses
theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
db.commit()
return VendorThemeResponse(**theme.to_dict())
# ============================================================================
# PRESET APPLICATION
# ============================================================================
@router.post("/{vendor_code}/preset/{preset_name}", response_model=ThemePresetResponse)
async def apply_theme_preset(
vendor_code: str = Path(..., description="Vendor code"),
preset_name: str = Path(..., description="Preset name"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Apply a theme preset to a vendor.
Replaces the vendor's current theme with the selected preset.
Available presets can be retrieved from the `/presets` endpoint.
**Path Parameters:**
- `vendor_code`: Vendor code (e.g., VENDOR001)
- `preset_name`: Name of preset to apply (e.g., 'modern', 'classic')
**Available Presets:**
- `default`: Clean and professional
- `modern`: Contemporary tech-inspired
- `classic`: Traditional and trustworthy
- `minimal`: Ultra-clean black and white
- `vibrant`: Bold and energetic
- `elegant`: Sophisticated gray tones
- `nature`: Fresh and eco-friendly
**Permissions:** Admin only
**Returns:**
- Success message and applied theme configuration
**Errors:**
- `404`: Vendor not found (VendorNotFoundException) or preset not found (ThemePresetNotFoundException)
- `500`: Operation failed (ThemeOperationException)
"""
logger.info(f"Applying preset '{preset_name}' to vendor {vendor_code}")
# Service validates preset name and applies it
# Raises ThemePresetNotFoundException if preset doesn't exist
# Global exception handler converts to HTTP 404
theme = vendor_theme_service.apply_theme_preset(db, vendor_code, preset_name)
db.commit()
return ThemePresetResponse(
message=f"Applied {preset_name} preset successfully",
theme=VendorThemeResponse(**theme.to_dict()),
)
# ============================================================================
# THEME DELETION
# ============================================================================
@router.delete("/{vendor_code}", response_model=ThemeDeleteResponse)
async def delete_vendor_theme(
vendor_code: str = Path(..., description="Vendor code"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""
Delete custom theme for a vendor.
Removes the vendor's custom theme. After deletion, the vendor
will revert to using the default platform theme.
**Path Parameters:**
- `vendor_code`: Vendor code (e.g., VENDOR001)
**Permissions:** Admin only
**Returns:**
- Success message
**Errors:**
- `404`: Vendor not found (VendorNotFoundException) or no custom theme (VendorThemeNotFoundException)
- `500`: Operation failed (ThemeOperationException)
"""
logger.info(f"Deleting theme for vendor: {vendor_code}")
# Service handles deletion and raises exceptions if needed
# Global exception handler converts them to proper HTTP responses
result = vendor_theme_service.delete_theme(db, vendor_code)
db.commit()
return ThemeDeleteResponse(
message=result.get("message", "Theme deleted successfully")
)