from datetime import datetime from typing import Literal from pydantic import BaseModel, EmailStr, Field, field_validator class LoginIn(BaseModel): email: EmailStr password: str = Field(min_length=6) class ProductImageIn(BaseModel): url: str = Field(min_length=1) alt: str | None = None sortOrder: int = 0 class TextBlock(BaseModel): type: Literal["text"] text: str = Field(min_length=1) class ImageBlock(BaseModel): type: Literal["image"] url: str = Field(min_length=1) alt: str | None = None class ProductDetailSectionIn(BaseModel): key: str = Field(min_length=1) label: str = Field(min_length=1) title: str | None = None blocks: list[TextBlock | ImageBlock] = [] class ProductCreateIn(BaseModel): title: str = Field(min_length=2) subtitle: str | None = None destinationId: str | None = None priceAmount: int | None = Field(default=None, ge=0) priceUnit: str | None = None tags: list[str] = [] coverImage: str | None = None summary: str | None = None images: list[ProductImageIn] | None = None detailSections: list[ProductDetailSectionIn] | None = None status: Literal["draft", "published", "archived"] = "draft" sortWeight: int = 0 class ProductUpdateIn(BaseModel): title: str | None = Field(default=None, min_length=2) subtitle: str | None = None destinationId: str | None = None priceAmount: int | None = Field(default=None, ge=0) priceUnit: str | None = None tags: list[str] | None = None coverImage: str | None = None summary: str | None = None images: list[ProductImageIn] | None = None detailSections: list[ProductDetailSectionIn] | None = None status: Literal["draft", "published", "archived"] | None = None sortWeight: int | None = None class LeadCreateIn(BaseModel): destination: str | None = None phone: str = Field(min_length=2, max_length=64) travelDate: datetime | None = None peopleCount: int | None = Field(default=None, gt=0) budgetMin: int | None = Field(default=None, ge=0) budgetMax: int | None = Field(default=None, ge=0) note: str | None = Field(default=None, max_length=1000) sourcePage: str | None = None sourceProductId: str | None = None @field_validator("phone") @classmethod def normalize_phone(cls, value: str) -> str: return " ".join(value.strip().split()) class LeadStatusIn(BaseModel): status: Literal["new", "assigned", "contacted", "planning", "won", "invalid"] class ProductQuery(BaseModel): keyword: str | None = None destinationId: str | None = None status: str = "published" take: int = Field(default=48, ge=1, le=100) class AdminProductQuery(BaseModel): keyword: str | None = None status: str | None = None take: int = Field(default=100, ge=1, le=200) class LeadQuery(BaseModel): status: str | None = None take: int = Field(default=100, ge=1, le=200) class SiteConfigPatchIn(BaseModel): title: str | None = None kicker: str | None = None name: str | None = None label: str | None = None alt: str | None = None image: str | None = None targetType: str | None = None targetValue: str | None = None isActive: bool | None = None