""" @schema StorefrontCmsSchema (Backend/app/schemas/StorefrontCmsSchema.py) @purpose Pydantic v2 request/response schemas for Storefront CMS endpoints covering Footer Info, Store Settings, Mega Menu, Catalog Filters, and Reviews. """ from __future__ import annotations from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field, field_validator import re # --------------------------------------------------------------------------- # Shared sub-models # --------------------------------------------------------------------------- class NavLink(BaseModel): label: str href: str class MegaMenuGroup(BaseModel): title: str links: List[NavLink] = [] class MegaMenuPromo(BaseModel): image_url: Optional[str] = None badge: Optional[str] = None title: Optional[str] = None href: Optional[str] = None class SocialLink(BaseModel): platform: str url: str icon: Optional[str] = None class FooterColumn(BaseModel): title: str links: List[NavLink] = [] class PaymentMethod(BaseModel): name: str icon_url: Optional[str] = None class HighlightFilter(BaseModel): label: str value: str icon: Optional[str] = None class PriceRange(BaseModel): label: str min: Optional[float] = None max: Optional[float] = None # --------------------------------------------------------------------------- # Footer Info # --------------------------------------------------------------------------- class FooterInfoResponse(BaseModel): phone: Optional[str] = None email: Optional[str] = None address: Optional[str] = None copyright: Optional[str] = None social_links: List[SocialLink] = [] columns: List[FooterColumn] = [] payment_methods: List[PaymentMethod] = [] model_config = {"from_attributes": True} class FooterInfoUpdate(BaseModel): phone: Optional[str] = Field(None, max_length=32) email: Optional[str] = Field(None, max_length=128) address: Optional[str] = None copyright: Optional[str] = Field(None, max_length=255) social_links: Optional[List[SocialLink]] = None columns: Optional[List[FooterColumn]] = None payment_methods: Optional[List[PaymentMethod]] = None @field_validator("email") @classmethod def validate_email(cls, v: Optional[str]) -> Optional[str]: if v and "@" not in v: raise ValueError("Invalid email address") return v # --------------------------------------------------------------------------- # Storefront Settings # --------------------------------------------------------------------------- class StorefrontSettingsResponse(BaseModel): """Flat key→value map returned from GET /settings""" store_name: Optional[str] = None logo_url: Optional[str] = None primary_wordmark_url: Optional[str] = None secondary_wordmark_url: Optional[str] = None favicon_url: Optional[str] = None support_phone: Optional[str] = None currency_code: Optional[str] = "INR" advance_percent: Optional[float] = 20.0 theme_color: Optional[str] = None size_chart: Optional[list] = Field(default_factory=list) model_config = {"extra": "allow"} # forward-compatible: include any extra keys class StorefrontSettingsUpdate(BaseModel): store_name: Optional[str] = Field(None, max_length=128) logo_url: Optional[str] = Field(None, max_length=1000) primary_wordmark_url: Optional[str] = Field(None, max_length=1000) secondary_wordmark_url: Optional[str] = Field(None, max_length=1000) favicon_url: Optional[str] = Field(None, max_length=1000) support_phone: Optional[str] = Field(None, max_length=32) currency_code: Optional[str] = Field(None, max_length=8) advance_percent: Optional[float] = Field(None, ge=0, le=100) theme_color: Optional[str] = Field(None, max_length=32) size_chart: Optional[list] = None # [{size, width, height, depth}, ...] @field_validator("advance_percent") @classmethod def clamp_advance(cls, v: Optional[float]) -> Optional[float]: if v is not None and not (0 <= v <= 100): raise ValueError("advance_percent must be between 0 and 100") return v # --------------------------------------------------------------------------- # Mega Menu # --------------------------------------------------------------------------- class MegaMenuResponse(BaseModel): nav_key: str groups: List[MegaMenuGroup] = [] promo: Optional[MegaMenuPromo] = None featured_category_ids: List[str] = [] model_config = {"from_attributes": True} class MegaMenuUpdate(BaseModel): nav_key: str = Field(..., pattern="^(shop|deals|products)$") groups: Optional[List[MegaMenuGroup]] = None promo: Optional[MegaMenuPromo] = None featured_category_ids: Optional[List[str]] = None class AllMegaMenusResponse(BaseModel): shop: Optional[MegaMenuResponse] = None deals: Optional[MegaMenuResponse] = None products: Optional[MegaMenuResponse] = None # --------------------------------------------------------------------------- # Catalog Filters # --------------------------------------------------------------------------- class CatalogFiltersResponse(BaseModel): highlights: List[HighlightFilter] = [] price_ranges: List[PriceRange] = [] model_config = {"from_attributes": True} class CatalogFiltersUpdate(BaseModel): highlights: Optional[List[HighlightFilter]] = None price_ranges: Optional[List[PriceRange]] = None # --------------------------------------------------------------------------- # Category metadata update # --------------------------------------------------------------------------- class CategoryCmsUpdate(BaseModel): show_in_sidebar: Optional[bool] = None mega_group: Optional[str] = Field(None, max_length=64) badge: Optional[str] = Field(None, max_length=32) name: Optional[str] = Field(None, max_length=100) description: Optional[str] = Field(None, max_length=500) image_url: Optional[str] = Field(None, max_length=500) is_active: Optional[bool] = None sort_order: Optional[str] = Field(None, max_length=10) # --------------------------------------------------------------------------- # Review moderation # --------------------------------------------------------------------------- class ReviewModerationResponse(BaseModel): review_id: str product_id: str author_name: str author_email: Optional[str] = None rating: int title: str comment: str verified_purchase: bool is_approved: bool helpful_count: int admin_reply: Optional[str] = None review_date: Optional[str] = None images: List[str] = [] model_config = {"from_attributes": True} class ReviewApprovalPayload(BaseModel): admin_reply: Optional[str] = Field(None, max_length=1000)