207 lines
6.7 KiB
Python
207 lines
6.7 KiB
Python
"""
|
|
@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)
|