ifixkart-backend/app/schemas/StorefrontCmsSchema.py

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)