Files
orion/app/modules/tenancy/routes/api/admin_platforms.py
Samir Boulahtit 45260b6b82
Some checks failed
CI / ruff (push) Successful in 11s
CI / docs (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
feat(admin): separate platform CRUD from CMS, add entity selector macro
- Move platforms menu from CMS to Platform Admin section with create/edit
- Add platform create page, API endpoint, and service method
- Remove CMS-specific content from platform list and detail pages
- Create shared entity_selector + entity_selected_badge Jinja macros
- Create entity-selector.js generalizing store-selector.js for any entity
- Add Tom Select merchant filter to stores page with localStorage persistence
- Migrate store-products page to use shared macros (remove 53 lines of duped CSS)
- Fix broken icons: puzzle→puzzle-piece, building-storefront→store, language→translate, server→cube

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 22:40:15 +01:00

263 lines
8.7 KiB
Python

# app/modules/tenancy/routes/api/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)
- Store defaults (about, terms, privacy)
- Configuration and branding
"""
import logging
from typing import Any
from fastapi import APIRouter, Depends, Path, Query
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_from_cookie_or_header, get_db
from app.modules.tenancy.schemas.auth import UserContext
from app.modules.tenancy.services.platform_service import platform_service
logger = logging.getLogger(__name__)
admin_platforms_router = APIRouter(prefix="/platforms")
# =============================================================================
# Pydantic Schemas
# =============================================================================
class PlatformResponse(BaseModel):
"""Platform response schema."""
id: int
code: str
name: str
description: str | None = None
description_translations: dict[str, 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)
store_count: int = 0
platform_pages_count: int = 0
store_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
description_translations: dict[str, 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 PlatformCreateRequest(BaseModel):
"""Request schema for creating a platform."""
code: str = Field(..., min_length=2, max_length=50, pattern=r"^[a-z][a-z0-9_-]*$")
name: str = Field(..., min_length=1, max_length=100)
description: str | None = None
description_translations: dict[str, str] | None = None
domain: str | None = None
path_prefix: str | None = None
default_language: str = "fr"
supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en", "lb"])
is_active: bool = True
is_public: bool = True
class PlatformStatsResponse(BaseModel):
"""Platform statistics response."""
platform_id: int
platform_code: str
platform_name: str
store_count: int
platform_pages_count: int
store_defaults_count: int
store_overrides_count: int
published_pages_count: int
draft_pages_count: int
# =============================================================================
# Helper Functions
# =============================================================================
def _build_platform_response(db: Session, platform) -> PlatformResponse:
"""Build PlatformResponse from Platform model with computed fields."""
return PlatformResponse(
id=platform.id,
code=platform.code,
name=platform.name,
description=platform.description,
description_translations=platform.description_translations,
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(),
store_count=platform_service.get_store_count(db, platform.id),
platform_pages_count=platform_service.get_platform_pages_count(db, platform.id),
store_defaults_count=platform_service.get_store_defaults_count(db, platform.id),
)
# =============================================================================
# API Endpoints
# =============================================================================
@admin_platforms_router.get("", response_model=PlatformListResponse)
async def list_platforms(
db: Session = Depends(get_db),
current_user: UserContext = 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 store 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))
@admin_platforms_router.post("", response_model=PlatformResponse, status_code=201)
async def create_platform(
create_data: PlatformCreateRequest,
db: Session = Depends(get_db),
current_user: UserContext = Depends(get_current_admin_from_cookie_or_header),
):
"""
Create a new platform.
Creates a new platform with the provided configuration.
"""
data = create_data.model_dump()
platform = platform_service.create_platform(db, data)
db.commit()
db.refresh(platform)
logger.info(f"[PLATFORMS] Created platform: {platform.code}")
return _build_platform_response(db, platform)
@admin_platforms_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: UserContext = 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)
@admin_platforms_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: UserContext = Depends(get_current_admin_from_cookie_or_header),
):
"""
Update platform settings.
Allows updating name, description, branding, and configuration.
"""
platform = platform_service.get_platform_by_code(db, code)
update_dict = update_data.model_dump(exclude_unset=True)
platform = platform_service.update_platform(db, platform, update_dict)
db.commit()
db.refresh(platform)
return _build_platform_response(db, platform)
@admin_platforms_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: UserContext = Depends(get_current_admin_from_cookie_or_header),
):
"""
Get detailed statistics for a platform.
Returns counts for stores, pages, and content breakdown.
"""
platform = platform_service.get_platform_by_code(db, code)
stats = platform_service.get_platform_stats(db, platform)
return PlatformStatsResponse(
platform_id=stats.platform_id,
platform_code=stats.platform_code,
platform_name=stats.platform_name,
store_count=stats.store_count,
platform_pages_count=stats.platform_pages_count,
store_defaults_count=stats.store_defaults_count,
store_overrides_count=stats.store_overrides_count,
published_pages_count=stats.published_pages_count,
draft_pages_count=stats.draft_pages_count,
)