# models/api_models.py from pydantic import BaseModel, Field, field_validator, EmailStr from typing import Optional, List from datetime import datetime # User Authentication Models class UserRegister(BaseModel): email: EmailStr = Field(..., description="Valid email address") username: str = Field(..., min_length=3, max_length=50, description="Username (3-50 characters)") password: str = Field(..., min_length=6, description="Password (minimum 6 characters)") @field_validator('username') @classmethod def validate_username(cls, v): if not v.isalnum(): raise ValueError('Username must contain only alphanumeric characters') return v.lower().strip() @field_validator('password') @classmethod def validate_password(cls, v): if len(v) < 6: raise ValueError('Password must be at least 6 characters long') return v class UserLogin(BaseModel): username: str = Field(..., description="Username") password: str = Field(..., description="Password") @field_validator('username') @classmethod def validate_username(cls, v): return v.strip() class UserResponse(BaseModel): id: int email: str username: str role: str is_active: bool last_login: Optional[datetime] = None created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class LoginResponse(BaseModel): access_token: str token_type: str = "bearer" expires_in: int user: UserResponse # Base Product Models class ProductBase(BaseModel): product_id: Optional[str] = None title: Optional[str] = None description: Optional[str] = None link: Optional[str] = None image_link: Optional[str] = None availability: Optional[str] = None price: Optional[str] = None brand: Optional[str] = None gtin: Optional[str] = None mpn: Optional[str] = None condition: Optional[str] = None adult: Optional[str] = None multipack: Optional[int] = None is_bundle: Optional[str] = None age_group: Optional[str] = None color: Optional[str] = None gender: Optional[str] = None material: Optional[str] = None pattern: Optional[str] = None size: Optional[str] = None size_type: Optional[str] = None size_system: Optional[str] = None item_group_id: Optional[str] = None google_product_category: Optional[str] = None product_type: Optional[str] = None custom_label_0: Optional[str] = None custom_label_1: Optional[str] = None custom_label_2: Optional[str] = None custom_label_3: Optional[str] = None custom_label_4: Optional[str] = None additional_image_link: Optional[str] = None sale_price: Optional[str] = None unit_pricing_measure: Optional[str] = None unit_pricing_base_measure: Optional[str] = None identifier_exists: Optional[str] = None shipping: Optional[str] = None currency: Optional[str] = None class ProductCreate(ProductBase): product_id: str = Field(..., min_length=1, description="Product ID is required") title: str = Field(..., min_length=1, description="Title is required") @field_validator('product_id', 'title') @classmethod def validate_required_fields(cls, v): if not v or not v.strip(): raise ValueError('Field cannot be empty') return v.strip() class ProductUpdate(ProductBase): pass class ProductResponse(ProductBase): id: int created_at: datetime updated_at: datetime model_config = {"from_attributes": True} # Stock Models class StockBase(BaseModel): gtin: str = Field(..., min_length=1, description="GTIN is required") location: str = Field(..., min_length=1, description="Location is required") class StockCreate(StockBase): quantity: int = Field(ge=0, description="Quantity must be non-negative") class StockAdd(StockBase): quantity: int = Field(gt=0, description="Quantity to add must be positive") class StockUpdate(BaseModel): quantity: int = Field(ge=0, description="Quantity must be non-negative") class StockResponse(BaseModel): id: int gtin: str location: str quantity: int created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class StockLocationResponse(BaseModel): location: str quantity: int class StockSummaryResponse(BaseModel): gtin: str total_quantity: int locations: List[StockLocationResponse] product_title: Optional[str] = None # Import Models class CSVImportRequest(BaseModel): url: str = Field(..., description="URL to CSV file") batch_size: Optional[int] = Field(1000, gt=0, le=10000, description="Batch size for processing") @field_validator('url') @classmethod def validate_url(cls, v): if not v.startswith(('http://', 'https://')): raise ValueError('URL must start with http:// or https://') return v class ImportJobResponse(BaseModel): job_id: int status: str message: Optional[str] = None imported: Optional[int] = 0 updated: Optional[int] = 0 total_processed: Optional[int] = 0 error_count: Optional[int] = 0 error_message: Optional[str] = None created_at: Optional[datetime] = None started_at: Optional[datetime] = None completed_at: Optional[datetime] = None # Response Models class ProductListResponse(BaseModel): products: List[ProductResponse] total: int skip: int limit: int class ProductDetailResponse(BaseModel): product: ProductResponse stock_info: Optional[StockSummaryResponse] = None class StatsResponse(BaseModel): total_products: int unique_brands: int unique_categories: int total_stock_entries: int = 0 total_inventory_quantity: int = 0