# app/modules/tenancy/schemas/store_domain.py """ Pydantic schemas for Store Domain operations. Schemas include: - StoreDomainCreate: For adding custom domains - StoreDomainUpdate: For updating domain settings - StoreDomainResponse: Standard domain response - StoreDomainListResponse: Paginated domain list - DomainVerificationInstructions: DNS verification instructions """ import re from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, field_validator class StoreDomainCreate(BaseModel): """Schema for adding a custom domain to store.""" domain: str = Field( ..., description="Custom domain (e.g., myshop.com or shop.mybrand.com)", min_length=3, max_length=255, ) is_primary: bool = Field( default=False, description="Set as primary domain for the store" ) platform_id: int | None = Field(None, description="Platform this domain belongs to") @field_validator("domain") @classmethod def validate_domain(cls, v: str) -> str: """Validate and normalize domain.""" # Remove protocol if present domain = v.replace("https://", "").replace("http://", "") # noqa: SEC-034 # Remove trailing slash domain = domain.rstrip("/") # Convert to lowercase domain = domain.lower().strip() # Basic validation if not domain or "/" in domain: raise ValueError("Invalid domain format") if "." not in domain: raise ValueError("Domain must have at least one dot") # Check for reserved subdomains reserved = ["www", "admin", "api", "mail", "smtp", "ftp", "cpanel", "webmail"] first_part = domain.split(".")[0] if first_part in reserved: raise ValueError( f"Domain cannot start with reserved subdomain: {first_part}" ) # Validate domain format (basic regex) domain_pattern = r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$" if not re.match(domain_pattern, domain): raise ValueError("Invalid domain format") return domain class StoreDomainUpdate(BaseModel): """Schema for updating store domain settings.""" is_primary: bool | None = Field(None, description="Set as primary domain") is_active: bool | None = Field(None, description="Activate or deactivate domain") model_config = ConfigDict(from_attributes=True) class StoreDomainResponse(BaseModel): """Standard schema for store domain response.""" model_config = ConfigDict(from_attributes=True) id: int store_id: int domain: str is_primary: bool is_active: bool is_verified: bool ssl_status: str platform_id: int | None = None verification_token: str | None = None verified_at: datetime | None = None ssl_verified_at: datetime | None = None created_at: datetime updated_at: datetime class StoreDomainListResponse(BaseModel): """Schema for paginated store domain list.""" domains: list[StoreDomainResponse] total: int class DomainVerificationInstructions(BaseModel): """DNS verification instructions for domain ownership.""" domain: str verification_token: str instructions: dict[str, str] txt_record: dict[str, str] common_registrars: dict[str, str] model_config = ConfigDict(from_attributes=True) class DomainVerificationResponse(BaseModel): """Response after domain verification.""" message: str domain: str verified_at: datetime is_verified: bool class DomainDeletionResponse(BaseModel): """Response after domain deletion.""" message: str domain: str store_id: int