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:
288
app/services/platform_service.py
Normal file
288
app/services/platform_service.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# 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 models.database.content_page 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 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()
|
||||
@@ -257,6 +257,19 @@ class VendorService:
|
||||
|
||||
return vendor
|
||||
|
||||
def get_vendor_by_id_optional(self, db: Session, vendor_id: int) -> Vendor | None:
|
||||
"""
|
||||
Get vendor by ID, returns None if not found.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
vendor_id: Vendor ID to find
|
||||
|
||||
Returns:
|
||||
Vendor object or None if not found
|
||||
"""
|
||||
return db.query(Vendor).filter(Vendor.id == vendor_id).first()
|
||||
|
||||
def get_active_vendor_by_code(self, db: Session, vendor_code: str) -> Vendor:
|
||||
"""
|
||||
Get active vendor by vendor_code for public access (no auth required).
|
||||
|
||||
Reference in New Issue
Block a user