# app/modules/cms/routes/api/vendor.py """ Vendor Content Pages API Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern). The get_current_vendor_api dependency guarantees token_vendor_id is present. Vendors can: - View their content pages (includes platform defaults) - Create/edit/delete their own content page overrides - Preview pages before publishing """ import logging from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api, get_db from app.modules.cms.exceptions import ContentPageNotFoundException from app.modules.cms.schemas import ( VendorContentPageCreate, VendorContentPageUpdate, ContentPageResponse, CMSUsageResponse, ) from app.modules.cms.services import content_page_service from app.services.vendor_service import VendorService # noqa: MOD-004 - shared platform service from models.database.user import User vendor_service = VendorService() router = APIRouter() logger = logging.getLogger(__name__) # ============================================================================ # VENDOR CONTENT PAGES # ============================================================================ @router.get("/", response_model=list[ContentPageResponse]) def list_vendor_pages( include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ List all content pages available for this vendor. Returns vendor-specific overrides + platform defaults (vendor overrides take precedence). """ pages = content_page_service.list_pages_for_vendor( db, vendor_id=current_user.token_vendor_id, include_unpublished=include_unpublished ) return [page.to_dict() for page in pages] @router.get("/overrides", response_model=list[ContentPageResponse]) def list_vendor_overrides( include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ List only vendor-specific content pages (no platform defaults). Shows what the vendor has customized. """ pages = content_page_service.list_all_vendor_pages( db, vendor_id=current_user.token_vendor_id, include_unpublished=include_unpublished ) return [page.to_dict() for page in pages] @router.get("/usage", response_model=CMSUsageResponse) def get_cms_usage( current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get CMS usage statistics for the vendor. Returns page counts and limits based on subscription tier. """ vendor = vendor_service.get_vendor_by_id_optional(db, current_user.token_vendor_id) if not vendor: return CMSUsageResponse( total_pages=0, custom_pages=0, override_pages=0, pages_limit=3, custom_pages_limit=0, can_create_page=False, can_create_custom=False, usage_percent=0, custom_usage_percent=0, ) # Get vendor's pages vendor_pages = content_page_service.list_all_vendor_pages( db, vendor_id=current_user.token_vendor_id, include_unpublished=True ) total_pages = len(vendor_pages) override_pages = sum(1 for p in vendor_pages if p.is_vendor_override) custom_pages = total_pages - override_pages # Get limits from subscription tier pages_limit = None custom_pages_limit = None if vendor.subscription and vendor.subscription.tier: pages_limit = vendor.subscription.tier.cms_pages_limit custom_pages_limit = vendor.subscription.tier.cms_custom_pages_limit # Calculate can_create flags can_create_page = pages_limit is None or total_pages < pages_limit can_create_custom = custom_pages_limit is None or custom_pages < custom_pages_limit # Calculate usage percentages usage_percent = 0 if pages_limit is None else min(100, (total_pages / pages_limit) * 100) if pages_limit > 0 else 100 custom_usage_percent = 0 if custom_pages_limit is None else min(100, (custom_pages / custom_pages_limit) * 100) if custom_pages_limit > 0 else 100 return CMSUsageResponse( total_pages=total_pages, custom_pages=custom_pages, override_pages=override_pages, pages_limit=pages_limit, custom_pages_limit=custom_pages_limit, can_create_page=can_create_page, can_create_custom=can_create_custom, usage_percent=usage_percent, custom_usage_percent=custom_usage_percent, ) @router.get("/platform-default/{slug}", response_model=ContentPageResponse) def get_platform_default( slug: str, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get the platform default content for a slug. Useful for vendors to view the original before/after overriding. """ # Get vendor's platform vendor = vendor_service.get_vendor_by_id_optional(db, current_user.token_vendor_id) platform_id = 1 # Default to OMS if vendor and vendor.platforms: platform_id = vendor.platforms[0].id # Get platform default (vendor_id=None) page = content_page_service.get_vendor_default_page( db, platform_id=platform_id, slug=slug, include_unpublished=True ) if not page: raise ContentPageNotFoundException(slug) return page.to_dict() @router.get("/{slug}", response_model=ContentPageResponse) def get_page( slug: str, include_unpublished: bool = Query(False, description="Include draft pages"), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get a specific content page by slug. Returns vendor override if exists, otherwise platform default. """ page = content_page_service.get_page_for_vendor_or_raise( db, slug=slug, vendor_id=current_user.token_vendor_id, include_unpublished=include_unpublished, ) return page.to_dict() @router.post("/", response_model=ContentPageResponse, status_code=201) def create_vendor_page( page_data: VendorContentPageCreate, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Create a vendor-specific content page override. This will be shown instead of the platform default for this vendor. """ page = content_page_service.create_page( db, slug=page_data.slug, title=page_data.title, content=page_data.content, vendor_id=current_user.token_vendor_id, content_format=page_data.content_format, meta_description=page_data.meta_description, meta_keywords=page_data.meta_keywords, is_published=page_data.is_published, show_in_footer=page_data.show_in_footer, show_in_header=page_data.show_in_header, show_in_legal=page_data.show_in_legal, display_order=page_data.display_order, created_by=current_user.id, ) db.commit() return page.to_dict() @router.put("/{page_id}", response_model=ContentPageResponse) def update_vendor_page( page_id: int, page_data: VendorContentPageUpdate, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Update a vendor-specific content page. Can only update pages owned by this vendor. """ # Update with ownership check in service layer page = content_page_service.update_vendor_page( db, page_id=page_id, vendor_id=current_user.token_vendor_id, title=page_data.title, content=page_data.content, content_format=page_data.content_format, meta_description=page_data.meta_description, meta_keywords=page_data.meta_keywords, is_published=page_data.is_published, show_in_footer=page_data.show_in_footer, show_in_header=page_data.show_in_header, show_in_legal=page_data.show_in_legal, display_order=page_data.display_order, updated_by=current_user.id, ) db.commit() return page.to_dict() @router.delete("/{page_id}", status_code=204) def delete_vendor_page( page_id: int, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Delete a vendor-specific content page. Can only delete pages owned by this vendor. After deletion, platform default will be shown (if exists). """ # Delete with ownership check in service layer content_page_service.delete_vendor_page(db, page_id, current_user.token_vendor_id) db.commit()