fix(api): use proper Pydantic models in vendor themes/domains

vendor_themes.py:
- Return ThemePresetListResponse instead of raw dict (API-001 fix)
- Add ThemePresetResponse response_model to apply_theme_preset
- Add ThemeDeleteResponse response_model to delete endpoint

vendor_domain.py:
- Remove _get_vendor_by_id helper with direct DB query
- Use vendor_service.get_vendor_by_id() instead (API-002 fix)

models/schema/vendor_theme.py:
- Add ThemeDeleteResponse model for delete endpoint response

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 21:34:20 +01:00
parent 6d05b91e9f
commit 901471bb76
3 changed files with 21 additions and 34 deletions

View File

@@ -5,7 +5,7 @@ Admin endpoints for managing vendor custom domains.
Follows the architecture pattern: Follows the architecture pattern:
- Endpoints only handle HTTP layer - Endpoints only handle HTTP layer
- Business logic in service layer - Business logic in service layer
- Proper exception handling - Domain exceptions bubble up to global handler
- Pydantic schemas for validation - Pydantic schemas for validation
""" """
@@ -16,10 +16,9 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api from app.api.deps import get_current_admin_api
from app.core.database import get_db from app.core.database import get_db
from app.exceptions import VendorNotFoundException
from app.services.vendor_domain_service import vendor_domain_service from app.services.vendor_domain_service import vendor_domain_service
from app.services.vendor_service import vendor_service
from models.database.user import User from models.database.user import User
from models.database.vendor import Vendor
from models.schema.vendor_domain import ( from models.schema.vendor_domain import (
DomainDeletionResponse, DomainDeletionResponse,
DomainVerificationInstructions, DomainVerificationInstructions,
@@ -34,26 +33,6 @@ router = APIRouter(prefix="/vendors")
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _get_vendor_by_id(db: Session, vendor_id: int) -> Vendor:
"""
Helper to get vendor by ID.
Args:
db: Database session
vendor_id: Vendor ID
Returns:
Vendor object
Raises:
VendorNotFoundException: If vendor not found
"""
vendor = db.query(Vendor).filter(Vendor.id == vendor_id).first()
if not vendor:
raise VendorNotFoundException(str(vendor_id), identifier_type="id")
return vendor
@router.post("/{vendor_id}/domains", response_model=VendorDomainResponse) @router.post("/{vendor_id}/domains", response_model=VendorDomainResponse)
def add_vendor_domain( def add_vendor_domain(
vendor_id: int = Path(..., description="Vendor ID", gt=0), vendor_id: int = Path(..., description="Vendor ID", gt=0),
@@ -122,8 +101,8 @@ def list_vendor_domains(
**Raises:** **Raises:**
- 404: Vendor not found - 404: Vendor not found
""" """
# Verify vendor exists # Verify vendor exists (raises VendorNotFoundException if not found)
_get_vendor_by_id(db, vendor_id) vendor_service.get_vendor_by_id(db, vendor_id)
domains = vendor_domain_service.get_vendor_domains(db, vendor_id) domains = vendor_domain_service.get_vendor_domains(db, vendor_id)

View File

@@ -21,7 +21,9 @@ from app.api.deps import get_current_admin_api, get_db
from app.services.vendor_theme_service import vendor_theme_service from app.services.vendor_theme_service import vendor_theme_service
from models.database.user import User from models.database.user import User
from models.schema.vendor_theme import ( from models.schema.vendor_theme import (
ThemeDeleteResponse,
ThemePresetListResponse, ThemePresetListResponse,
ThemePresetResponse,
VendorThemeResponse, VendorThemeResponse,
VendorThemeUpdate, VendorThemeUpdate,
) )
@@ -51,7 +53,7 @@ async def get_theme_presets(current_admin: User = Depends(get_current_admin_api)
logger.info("Getting theme presets") logger.info("Getting theme presets")
presets = vendor_theme_service.get_available_presets() presets = vendor_theme_service.get_available_presets()
return {"presets": presets} return ThemePresetListResponse(presets=presets)
# ============================================================================ # ============================================================================
@@ -134,7 +136,7 @@ async def update_vendor_theme(
# Service handles all validation and raises appropriate exceptions # Service handles all validation and raises appropriate exceptions
# Global exception handler converts them to proper HTTP responses # Global exception handler converts them to proper HTTP responses
theme = vendor_theme_service.update_theme(db, vendor_code, theme_data) theme = vendor_theme_service.update_theme(db, vendor_code, theme_data)
return theme.to_dict() return VendorThemeResponse(**theme.to_dict())
# ============================================================================ # ============================================================================
@@ -142,7 +144,7 @@ async def update_vendor_theme(
# ============================================================================ # ============================================================================
@router.post("/{vendor_code}/preset/{preset_name}") @router.post("/{vendor_code}/preset/{preset_name}", response_model=ThemePresetResponse)
async def apply_theme_preset( async def apply_theme_preset(
vendor_code: str = Path(..., description="Vendor code"), vendor_code: str = Path(..., description="Vendor code"),
preset_name: str = Path(..., description="Preset name"), preset_name: str = Path(..., description="Preset name"),
@@ -184,10 +186,10 @@ async def apply_theme_preset(
# Global exception handler converts to HTTP 404 # Global exception handler converts to HTTP 404
theme = vendor_theme_service.apply_theme_preset(db, vendor_code, preset_name) theme = vendor_theme_service.apply_theme_preset(db, vendor_code, preset_name)
return { return ThemePresetResponse(
"message": f"Applied {preset_name} preset successfully", message=f"Applied {preset_name} preset successfully",
"theme": theme.to_dict(), theme=VendorThemeResponse(**theme.to_dict()),
} )
# ============================================================================ # ============================================================================
@@ -195,7 +197,7 @@ async def apply_theme_preset(
# ============================================================================ # ============================================================================
@router.delete("/{vendor_code}") @router.delete("/{vendor_code}", response_model=ThemeDeleteResponse)
async def delete_vendor_theme( async def delete_vendor_theme(
vendor_code: str = Path(..., description="Vendor code"), vendor_code: str = Path(..., description="Vendor code"),
db: Session = Depends(get_db), db: Session = Depends(get_db),
@@ -224,4 +226,4 @@ async def delete_vendor_theme(
# Service handles deletion and raises exceptions if needed # Service handles deletion and raises exceptions if needed
# Global exception handler converts them to proper HTTP responses # Global exception handler converts them to proper HTTP responses
result = vendor_theme_service.delete_theme(db, vendor_code) result = vendor_theme_service.delete_theme(db, vendor_code)
return result return ThemeDeleteResponse(message=result.get("message", "Theme deleted successfully"))

View File

@@ -100,3 +100,9 @@ class ThemePresetListResponse(BaseModel):
"""List of available theme presets.""" """List of available theme presets."""
presets: list[ThemePresetPreview] = Field(..., description="Available presets") presets: list[ThemePresetPreview] = Field(..., description="Available presets")
class ThemeDeleteResponse(BaseModel):
"""Response after deleting a theme."""
message: str = Field(..., description="Success message")