- Add AdminMenuConfig model for per-platform menu customization - Add menu registry for centralized menu configuration - Add my-menu-config and platform-menu-config admin pages - Update sidebar with improved layout and Alpine.js interactions - Add FrontendType enum for admin/vendor menu separation - Document self-contained module patterns in session note Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
311 lines
8.0 KiB
Python
311 lines
8.0 KiB
Python
# app/services/platform_service.py
|
|
"""
|
|
Platform Service
|
|
|
|
Business logic for platform management in the Multi-Platform CMS.
|
|
|
|
Platforms represent different business offerings (OMS, Loyalty, Site Builder, Main Marketing).
|
|
Each platform has its own:
|
|
- Marketing pages (homepage, pricing, features)
|
|
- Vendor defaults (about, terms, privacy)
|
|
- Configuration and branding
|
|
"""
|
|
|
|
import logging
|
|
from dataclasses import dataclass
|
|
|
|
from sqlalchemy import func
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.exceptions.platform import (
|
|
PlatformNotFoundException,
|
|
)
|
|
from app.modules.cms.models import ContentPage
|
|
from models.database.platform import Platform
|
|
from models.database.vendor_platform import VendorPlatform
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass
|
|
class PlatformStats:
|
|
"""Platform statistics."""
|
|
|
|
platform_id: int
|
|
platform_code: str
|
|
platform_name: str
|
|
vendor_count: int
|
|
platform_pages_count: int
|
|
vendor_defaults_count: int
|
|
vendor_overrides_count: int = 0
|
|
published_pages_count: int = 0
|
|
draft_pages_count: int = 0
|
|
|
|
|
|
class PlatformService:
|
|
"""Service for platform operations."""
|
|
|
|
@staticmethod
|
|
def get_platform_by_code(db: Session, code: str) -> Platform:
|
|
"""
|
|
Get platform by code.
|
|
|
|
Args:
|
|
db: Database session
|
|
code: Platform code (oms, loyalty, main, etc.)
|
|
|
|
Returns:
|
|
Platform object
|
|
|
|
Raises:
|
|
PlatformNotFoundException: If platform not found
|
|
"""
|
|
platform = db.query(Platform).filter(Platform.code == code).first()
|
|
|
|
if not platform:
|
|
raise PlatformNotFoundException(code)
|
|
|
|
return platform
|
|
|
|
@staticmethod
|
|
def get_platform_by_code_optional(db: Session, code: str) -> Platform | None:
|
|
"""
|
|
Get platform by code, returns None if not found.
|
|
|
|
Args:
|
|
db: Database session
|
|
code: Platform code
|
|
|
|
Returns:
|
|
Platform object or None
|
|
"""
|
|
return db.query(Platform).filter(Platform.code == code).first()
|
|
|
|
@staticmethod
|
|
def get_platform_by_id(db: Session, platform_id: int) -> Platform:
|
|
"""
|
|
Get platform by ID.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Platform object
|
|
|
|
Raises:
|
|
PlatformNotFoundException: If platform not found
|
|
"""
|
|
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
|
|
|
if not platform:
|
|
raise PlatformNotFoundException(str(platform_id))
|
|
|
|
return platform
|
|
|
|
@staticmethod
|
|
def list_platforms(
|
|
db: Session, include_inactive: bool = False
|
|
) -> list[Platform]:
|
|
"""
|
|
List all platforms.
|
|
|
|
Args:
|
|
db: Database session
|
|
include_inactive: Include inactive platforms
|
|
|
|
Returns:
|
|
List of Platform objects
|
|
"""
|
|
query = db.query(Platform)
|
|
|
|
if not include_inactive:
|
|
query = query.filter(Platform.is_active == True)
|
|
|
|
return query.order_by(Platform.id).all()
|
|
|
|
@staticmethod
|
|
def get_vendor_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of vendors on a platform.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Vendor count
|
|
"""
|
|
return (
|
|
db.query(func.count(VendorPlatform.vendor_id))
|
|
.filter(VendorPlatform.platform_id == platform_id)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@staticmethod
|
|
def get_platform_pages_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of platform marketing pages.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Platform pages count
|
|
"""
|
|
return (
|
|
db.query(func.count(ContentPage.id))
|
|
.filter(
|
|
ContentPage.platform_id == platform_id,
|
|
ContentPage.vendor_id == None,
|
|
ContentPage.is_platform_page == True,
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@staticmethod
|
|
def get_vendor_defaults_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of vendor default pages.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Vendor defaults count
|
|
"""
|
|
return (
|
|
db.query(func.count(ContentPage.id))
|
|
.filter(
|
|
ContentPage.platform_id == platform_id,
|
|
ContentPage.vendor_id == None,
|
|
ContentPage.is_platform_page == False,
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@staticmethod
|
|
def get_vendor_overrides_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of vendor override pages.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Vendor overrides count
|
|
"""
|
|
return (
|
|
db.query(func.count(ContentPage.id))
|
|
.filter(
|
|
ContentPage.platform_id == platform_id,
|
|
ContentPage.vendor_id != None,
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@staticmethod
|
|
def get_published_pages_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of published pages on a platform.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Published pages count
|
|
"""
|
|
return (
|
|
db.query(func.count(ContentPage.id))
|
|
.filter(
|
|
ContentPage.platform_id == platform_id,
|
|
ContentPage.is_published == True,
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@staticmethod
|
|
def get_draft_pages_count(db: Session, platform_id: int) -> int:
|
|
"""
|
|
Get count of draft pages on a platform.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform_id: Platform ID
|
|
|
|
Returns:
|
|
Draft pages count
|
|
"""
|
|
return (
|
|
db.query(func.count(ContentPage.id))
|
|
.filter(
|
|
ContentPage.platform_id == platform_id,
|
|
ContentPage.is_published == False,
|
|
)
|
|
.scalar()
|
|
or 0
|
|
)
|
|
|
|
@classmethod
|
|
def get_platform_stats(cls, db: Session, platform: Platform) -> PlatformStats:
|
|
"""
|
|
Get comprehensive statistics for a platform.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform: Platform object
|
|
|
|
Returns:
|
|
PlatformStats dataclass
|
|
"""
|
|
return PlatformStats(
|
|
platform_id=platform.id,
|
|
platform_code=platform.code,
|
|
platform_name=platform.name,
|
|
vendor_count=cls.get_vendor_count(db, platform.id),
|
|
platform_pages_count=cls.get_platform_pages_count(db, platform.id),
|
|
vendor_defaults_count=cls.get_vendor_defaults_count(db, platform.id),
|
|
vendor_overrides_count=cls.get_vendor_overrides_count(db, platform.id),
|
|
published_pages_count=cls.get_published_pages_count(db, platform.id),
|
|
draft_pages_count=cls.get_draft_pages_count(db, platform.id),
|
|
)
|
|
|
|
@staticmethod
|
|
def update_platform(
|
|
db: Session, platform: Platform, update_data: dict
|
|
) -> Platform:
|
|
"""
|
|
Update platform fields.
|
|
|
|
Note: This method does NOT commit the transaction.
|
|
The caller (API endpoint) is responsible for committing.
|
|
|
|
Args:
|
|
db: Database session
|
|
platform: Platform to update
|
|
update_data: Dictionary of fields to update
|
|
|
|
Returns:
|
|
Updated Platform object (with pending changes)
|
|
"""
|
|
for field, value in update_data.items():
|
|
if hasattr(platform, field):
|
|
setattr(platform, field, value)
|
|
|
|
logger.info(f"[PLATFORMS] Updated platform: {platform.code}")
|
|
|
|
return platform
|
|
|
|
|
|
# Singleton instance for convenience
|
|
platform_service = PlatformService()
|