fix: resolve architecture validation errors
- Create platform_service.py to move DB queries from platforms.py API - Create platform.py exceptions for PlatformNotFoundException - Update platforms.py API to use platform_service - Update vendor/content_pages.py to use vendor_service - Add get_vendor_by_id_optional method to VendorService - Fix platforms.js: add centralized logger and init guard - Fix content-page-edit.html: use modal macro instead of inline modal All 21 architecture validation errors resolved. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,16 +17,13 @@ Platforms are business offerings (OMS, Loyalty, Site Builder) with their own:
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
||||
from fastapi import APIRouter, Depends, Path, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_from_cookie_or_header, get_db
|
||||
from models.database.content_page import ContentPage
|
||||
from models.database.platform import Platform
|
||||
from app.services.platform_service import platform_service
|
||||
from models.database.user import User
|
||||
from models.database.vendor_platform import VendorPlatform
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/platforms")
|
||||
@@ -107,140 +104,12 @@ class PlatformStatsResponse(BaseModel):
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Endpoints
|
||||
# Helper Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@router.get("", response_model=PlatformListResponse)
|
||||
async def list_platforms(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
include_inactive: bool = Query(False, description="Include inactive platforms"),
|
||||
):
|
||||
"""
|
||||
List all platforms with their statistics.
|
||||
|
||||
Returns all platforms (OMS, Loyalty, etc.) with vendor counts and page counts.
|
||||
"""
|
||||
query = db.query(Platform)
|
||||
|
||||
if not include_inactive:
|
||||
query = query.filter(Platform.is_active == True)
|
||||
|
||||
platforms = query.order_by(Platform.id).all()
|
||||
|
||||
# Build response with computed fields
|
||||
result = []
|
||||
for platform in platforms:
|
||||
# Count vendors on this platform
|
||||
vendor_count = (
|
||||
db.query(func.count(VendorPlatform.vendor_id))
|
||||
.filter(VendorPlatform.platform_id == platform.id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count platform marketing pages
|
||||
platform_pages_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == True,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count vendor default pages
|
||||
vendor_defaults_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == False,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
platform_data = PlatformResponse(
|
||||
id=platform.id,
|
||||
code=platform.code,
|
||||
name=platform.name,
|
||||
description=platform.description,
|
||||
domain=platform.domain,
|
||||
path_prefix=platform.path_prefix,
|
||||
logo=platform.logo,
|
||||
logo_dark=platform.logo_dark,
|
||||
favicon=platform.favicon,
|
||||
theme_config=platform.theme_config or {},
|
||||
default_language=platform.default_language,
|
||||
supported_languages=platform.supported_languages or ["fr", "de", "en"],
|
||||
is_active=platform.is_active,
|
||||
is_public=platform.is_public,
|
||||
settings=platform.settings or {},
|
||||
created_at=platform.created_at.isoformat(),
|
||||
updated_at=platform.updated_at.isoformat(),
|
||||
vendor_count=vendor_count,
|
||||
platform_pages_count=platform_pages_count,
|
||||
vendor_defaults_count=vendor_defaults_count,
|
||||
)
|
||||
result.append(platform_data)
|
||||
|
||||
logger.info(f"[PLATFORMS] Listed {len(result)} platforms")
|
||||
|
||||
return PlatformListResponse(platforms=result, total=len(result))
|
||||
|
||||
|
||||
@router.get("/{code}", response_model=PlatformResponse)
|
||||
async def get_platform(
|
||||
code: str = Path(..., description="Platform code (oms, loyalty, etc.)"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
):
|
||||
"""
|
||||
Get platform details by code.
|
||||
|
||||
Returns full platform configuration including statistics.
|
||||
"""
|
||||
platform = db.query(Platform).filter(Platform.code == code).first()
|
||||
|
||||
if not platform:
|
||||
raise HTTPException(status_code=404, detail=f"Platform not found: {code}")
|
||||
|
||||
# Count vendors on this platform
|
||||
vendor_count = (
|
||||
db.query(func.count(VendorPlatform.vendor_id))
|
||||
.filter(VendorPlatform.platform_id == platform.id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count platform marketing pages
|
||||
platform_pages_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == True,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count vendor default pages
|
||||
vendor_defaults_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == False,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
def _build_platform_response(db: Session, platform) -> PlatformResponse:
|
||||
"""Build PlatformResponse from Platform model with computed fields."""
|
||||
return PlatformResponse(
|
||||
id=platform.id,
|
||||
code=platform.code,
|
||||
@@ -259,12 +128,52 @@ async def get_platform(
|
||||
settings=platform.settings or {},
|
||||
created_at=platform.created_at.isoformat(),
|
||||
updated_at=platform.updated_at.isoformat(),
|
||||
vendor_count=vendor_count,
|
||||
platform_pages_count=platform_pages_count,
|
||||
vendor_defaults_count=vendor_defaults_count,
|
||||
vendor_count=platform_service.get_vendor_count(db, platform.id),
|
||||
platform_pages_count=platform_service.get_platform_pages_count(db, platform.id),
|
||||
vendor_defaults_count=platform_service.get_vendor_defaults_count(db, platform.id),
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Endpoints
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@router.get("", response_model=PlatformListResponse)
|
||||
async def list_platforms(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
include_inactive: bool = Query(False, description="Include inactive platforms"),
|
||||
):
|
||||
"""
|
||||
List all platforms with their statistics.
|
||||
|
||||
Returns all platforms (OMS, Loyalty, etc.) with vendor counts and page counts.
|
||||
"""
|
||||
platforms = platform_service.list_platforms(db, include_inactive=include_inactive)
|
||||
|
||||
result = [_build_platform_response(db, platform) for platform in platforms]
|
||||
|
||||
logger.info(f"[PLATFORMS] Listed {len(result)} platforms")
|
||||
|
||||
return PlatformListResponse(platforms=result, total=len(result))
|
||||
|
||||
|
||||
@router.get("/{code}", response_model=PlatformResponse)
|
||||
async def get_platform(
|
||||
code: str = Path(..., description="Platform code (oms, loyalty, etc.)"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
):
|
||||
"""
|
||||
Get platform details by code.
|
||||
|
||||
Returns full platform configuration including statistics.
|
||||
"""
|
||||
platform = platform_service.get_platform_by_code(db, code)
|
||||
return _build_platform_response(db, platform)
|
||||
|
||||
|
||||
@router.put("/{code}", response_model=PlatformResponse)
|
||||
async def update_platform(
|
||||
update_data: PlatformUpdateRequest,
|
||||
@@ -277,24 +186,15 @@ async def update_platform(
|
||||
|
||||
Allows updating name, description, branding, and configuration.
|
||||
"""
|
||||
platform = db.query(Platform).filter(Platform.code == code).first()
|
||||
platform = platform_service.get_platform_by_code(db, code)
|
||||
|
||||
if not platform:
|
||||
raise HTTPException(status_code=404, detail=f"Platform not found: {code}")
|
||||
|
||||
# Update fields if provided
|
||||
update_dict = update_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_dict.items():
|
||||
if hasattr(platform, field):
|
||||
setattr(platform, field, value)
|
||||
platform = platform_service.update_platform(db, platform, update_dict)
|
||||
|
||||
db.commit()
|
||||
db.refresh(platform)
|
||||
|
||||
logger.info(f"[PLATFORMS] Updated platform: {code}")
|
||||
|
||||
# Return updated platform with stats
|
||||
return await get_platform(code=code, db=db, current_user=current_user)
|
||||
return _build_platform_response(db, platform)
|
||||
|
||||
|
||||
@router.get("/{code}/stats", response_model=PlatformStatsResponse)
|
||||
@@ -308,84 +208,17 @@ async def get_platform_stats(
|
||||
|
||||
Returns counts for vendors, pages, and content breakdown.
|
||||
"""
|
||||
platform = db.query(Platform).filter(Platform.code == code).first()
|
||||
|
||||
if not platform:
|
||||
raise HTTPException(status_code=404, detail=f"Platform not found: {code}")
|
||||
|
||||
# Count vendors
|
||||
vendor_count = (
|
||||
db.query(func.count(VendorPlatform.vendor_id))
|
||||
.filter(VendorPlatform.platform_id == platform.id)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count platform marketing pages
|
||||
platform_pages_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == True,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count vendor default pages
|
||||
vendor_defaults_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id == None,
|
||||
ContentPage.is_platform_page == False,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count vendor override pages
|
||||
vendor_overrides_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.vendor_id != None,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count published pages
|
||||
published_pages_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.is_published == True,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
# Count draft pages
|
||||
draft_pages_count = (
|
||||
db.query(func.count(ContentPage.id))
|
||||
.filter(
|
||||
ContentPage.platform_id == platform.id,
|
||||
ContentPage.is_published == False,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
platform = platform_service.get_platform_by_code(db, code)
|
||||
stats = platform_service.get_platform_stats(db, platform)
|
||||
|
||||
return PlatformStatsResponse(
|
||||
platform_id=platform.id,
|
||||
platform_code=platform.code,
|
||||
platform_name=platform.name,
|
||||
vendor_count=vendor_count,
|
||||
platform_pages_count=platform_pages_count,
|
||||
vendor_defaults_count=vendor_defaults_count,
|
||||
vendor_overrides_count=vendor_overrides_count,
|
||||
published_pages_count=published_pages_count,
|
||||
draft_pages_count=draft_pages_count,
|
||||
platform_id=stats.platform_id,
|
||||
platform_code=stats.platform_code,
|
||||
platform_name=stats.platform_name,
|
||||
vendor_count=stats.vendor_count,
|
||||
platform_pages_count=stats.platform_pages_count,
|
||||
vendor_defaults_count=stats.vendor_defaults_count,
|
||||
vendor_overrides_count=stats.vendor_overrides_count,
|
||||
published_pages_count=stats.published_pages_count,
|
||||
draft_pages_count=stats.draft_pages_count,
|
||||
)
|
||||
|
||||
11
app/api/v1/vendor/content_pages.py
vendored
11
app/api/v1/vendor/content_pages.py
vendored
@@ -19,8 +19,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_vendor_api, get_db
|
||||
from app.services.content_page_service import content_page_service
|
||||
from app.services.vendor_service import VendorService
|
||||
from models.database.user import User
|
||||
|
||||
vendor_service = VendorService()
|
||||
|
||||
router = APIRouter(prefix="/content-pages")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -272,9 +275,7 @@ def get_cms_usage(
|
||||
|
||||
Returns page counts and limits based on subscription tier.
|
||||
"""
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
vendor = db.query(Vendor).filter(Vendor.id == current_user.token_vendor_id).first()
|
||||
vendor = vendor_service.get_vendor_by_id_optional(db, current_user.token_vendor_id)
|
||||
if not vendor:
|
||||
return CMSUsageResponse(
|
||||
total_pages=0,
|
||||
@@ -336,10 +337,8 @@ def get_platform_default(
|
||||
|
||||
Useful for vendors to view the original before/after overriding.
|
||||
"""
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
# Get vendor's platform
|
||||
vendor = db.query(Vendor).filter(Vendor.id == current_user.token_vendor_id).first()
|
||||
vendor = vendor_service.get_vendor_by_id_optional(db, current_user.token_vendor_id)
|
||||
platform_id = 1 # Default to OMS
|
||||
|
||||
if vendor and vendor.platforms:
|
||||
|
||||
Reference in New Issue
Block a user