# app/api/v1/admin/platforms.py """ Admin API endpoints for Platform management (Multi-Platform CMS). Provides CRUD operations for platforms: - GET /platforms - List all platforms - GET /platforms/{code} - Get platform details - PUT /platforms/{code} - Update platform settings - GET /platforms/{code}/stats - Get platform statistics Platforms are business offerings (OMS, Loyalty, Site Builder) with their own: - Marketing pages (homepage, pricing, features) - Vendor defaults (about, terms, privacy) - Configuration and branding """ import logging from typing import Any from fastapi import APIRouter, Depends, HTTPException, 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 models.database.user import User from models.database.vendor_platform import VendorPlatform logger = logging.getLogger(__name__) router = APIRouter(prefix="/platforms") # ============================================================================= # Pydantic Schemas # ============================================================================= class PlatformResponse(BaseModel): """Platform response schema.""" id: int code: str name: str description: str | None = None domain: str | None = None path_prefix: str | None = None logo: str | None = None logo_dark: str | None = None favicon: str | None = None theme_config: dict[str, Any] = Field(default_factory=dict) default_language: str = "fr" supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en"]) is_active: bool = True is_public: bool = True settings: dict[str, Any] = Field(default_factory=dict) created_at: str updated_at: str # Computed fields (added by endpoint) vendor_count: int = 0 platform_pages_count: int = 0 vendor_defaults_count: int = 0 class Config: from_attributes = True class PlatformListResponse(BaseModel): """Response for platform list.""" platforms: list[PlatformResponse] total: int class PlatformUpdateRequest(BaseModel): """Request schema for updating a platform.""" name: str | None = None description: str | None = None domain: str | None = None path_prefix: str | None = None logo: str | None = None logo_dark: str | None = None favicon: str | None = None theme_config: dict[str, Any] | None = None default_language: str | None = None supported_languages: list[str] | None = None is_active: bool | None = None is_public: bool | None = None settings: dict[str, Any] | None = None class PlatformStatsResponse(BaseModel): """Platform statistics response.""" platform_id: int platform_code: str platform_name: str vendor_count: int platform_pages_count: int vendor_defaults_count: int vendor_overrides_count: int published_pages_count: int draft_pages_count: int # ============================================================================= # 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. """ 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 ) return 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, ) @router.put("/{code}", response_model=PlatformResponse) async def update_platform( update_data: PlatformUpdateRequest, code: str = Path(..., description="Platform code"), db: Session = Depends(get_db), current_user: User = Depends(get_current_admin_from_cookie_or_header), ): """ Update platform settings. Allows updating name, description, branding, and configuration. """ platform = db.query(Platform).filter(Platform.code == code).first() 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) 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) @router.get("/{code}/stats", response_model=PlatformStatsResponse) async def get_platform_stats( code: str = Path(..., description="Platform code"), db: Session = Depends(get_db), current_user: User = Depends(get_current_admin_from_cookie_or_header), ): """ Get detailed statistics for a platform. 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 ) 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, )