# app/api/v1/vendor/content_pages.py """ Vendor Content Pages API 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 pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.api.deps import get_current_vendor_api, get_db from app.exceptions.content_page import VendorNotAssociatedException from app.services.content_page_service import content_page_service from models.database.user import User router = APIRouter() logger = logging.getLogger(__name__) # ============================================================================ # REQUEST/RESPONSE SCHEMAS # ============================================================================ class VendorContentPageCreate(BaseModel): """Schema for creating a vendor content page.""" slug: str = Field( ..., max_length=100, description="URL-safe identifier (about, faq, contact, etc.)", ) title: str = Field(..., max_length=200, description="Page title") content: str = Field(..., description="HTML or Markdown content") content_format: str = Field( default="html", description="Content format: html or markdown" ) meta_description: str | None = Field( None, max_length=300, description="SEO meta description" ) meta_keywords: str | None = Field(None, max_length=300, description="SEO keywords") is_published: bool = Field(default=False, description="Publish immediately") show_in_footer: bool = Field(default=True, description="Show in footer navigation") show_in_header: bool = Field(default=False, description="Show in header navigation") display_order: int = Field(default=0, description="Display order (lower = first)") class VendorContentPageUpdate(BaseModel): """Schema for updating a vendor content page.""" title: str | None = Field(None, max_length=200) content: str | None = None content_format: str | None = None meta_description: str | None = Field(None, max_length=300) meta_keywords: str | None = Field(None, max_length=300) is_published: bool | None = None show_in_footer: bool | None = None show_in_header: bool | None = None display_order: int | None = None class ContentPageResponse(BaseModel): """Schema for content page response.""" id: int vendor_id: int | None vendor_name: str | None slug: str title: str content: str content_format: str meta_description: str | None meta_keywords: str | None is_published: bool published_at: str | None display_order: int show_in_footer: bool show_in_header: bool is_platform_default: bool is_vendor_override: bool created_at: str updated_at: str created_by: int | None updated_by: int | None # ============================================================================ # 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). """ if not current_user.vendor_id: raise VendorNotAssociatedException() pages = content_page_service.list_pages_for_vendor( db, vendor_id=current_user.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. """ if not current_user.vendor_id: raise VendorNotAssociatedException() pages = content_page_service.list_all_vendor_pages( db, vendor_id=current_user.vendor_id, include_unpublished=include_unpublished ) return [page.to_dict() for page in pages] @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. """ if not current_user.vendor_id: raise VendorNotAssociatedException() page = content_page_service.get_page_for_vendor_or_raise( db, slug=slug, vendor_id=current_user.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. """ if not current_user.vendor_id: raise VendorNotAssociatedException() page = content_page_service.create_page( db, slug=page_data.slug, title=page_data.title, content=page_data.content, vendor_id=current_user.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, 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. """ if not current_user.vendor_id: raise VendorNotAssociatedException() # Update with ownership check in service layer page = content_page_service.update_vendor_page( db, page_id=page_id, vendor_id=current_user.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, 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). """ if not current_user.vendor_id: raise VendorNotAssociatedException() # Delete with ownership check in service layer content_page_service.delete_vendor_page(db, page_id, current_user.vendor_id) db.commit()