Files
orion/app/api/v1/admin/platforms.py
Samir Boulahtit 968002630e feat: complete multi-platform CMS phases 2-5
Phase 2 - OMS Migration & Integration:
- Fix platform_pages.py to use get_platform_page for marketing pages
- Fix shop_pages.py to pass platform_id to content page service calls

Phase 3 - Admin Interface:
- Add platform management API (app/api/v1/admin/platforms.py)
- Add platforms admin page with stats cards
- Add Platforms menu item to admin sidebar
- Update content pages admin with platform filter and four-tab tier system

Phase 4 - Documentation:
- Add comprehensive architecture docs (docs/architecture/multi-platform-cms.md)
- Update implementation plan with completion status

Phase 5 - Vendor Dashboard:
- Add CMS usage API endpoint with tier limits
- Add usage progress bar to vendor content pages
- Add platform-default/{slug} API for preview
- Add View Default button and modal in page editor

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 16:30:31 +01:00

392 lines
12 KiB
Python

# 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,
)