from datetime import datetime from typing import List, Optional from pydantic import BaseModel, EmailStr, field_validator class RegisterRequest(BaseModel): email: EmailStr password: str @field_validator("password") @classmethod def password_min_length(cls, v: str) -> str: if len(v) < 8: raise ValueError("Password must be at least 8 characters") return v class LoginRequest(BaseModel): email: EmailStr password: str class UserResponse(BaseModel): id: str email: str created_at: datetime class Config: from_attributes = True class AssetResponse(BaseModel): id: str asset_type: str mime_type: str original_filename: str storage_path: str size_bytes: int width: Optional[int] = None height: Optional[int] = None duration_seconds: Optional[float] = None thumbnail_path: Optional[str] = None is_trashed: bool = False delete_after_at: Optional[datetime] = None created_at: datetime class Config: from_attributes = True class JobCreateRequest(BaseModel): mode: str submode: Optional[str] = None prompt: str negative_prompt: Optional[str] = None ground_truth_asset_id: str motion_asset_id: Optional[str] = None audio_asset_id: Optional[str] = None reference_asset_ids: Optional[List[str]] = None pose_asset_id: Optional[str] = None settings: Optional[dict] = None @field_validator("mode") @classmethod def validate_mode(cls, v: str) -> str: if v not in ("animate", "audio"): raise ValueError("mode must be 'animate' or 'audio'") return v @field_validator("submode") @classmethod def validate_submode(cls, v: Optional[str]) -> Optional[str]: if v is not None and v not in ("move", "mix"): raise ValueError("submode must be 'move' or 'mix'") return v class JobOutputResponse(BaseModel): id: str output_type: str file_path: str poster_path: Optional[str] = None created_at: datetime class Config: from_attributes = True class JobEventResponse(BaseModel): id: str event_type: str message: Optional[str] = None created_at: datetime class Config: from_attributes = True class AssetTrashRequest(BaseModel): asset_ids: List[str] class JobResponse(BaseModel): id: str mode: str submode: Optional[str] = None prompt: str negative_prompt: Optional[str] = None status: str comfy_prompt_id: Optional[str] = None workflow_template_name: Optional[str] = None error_message: Optional[str] = None ground_truth_asset_id: Optional[str] = None motion_asset_id: Optional[str] = None audio_asset_id: Optional[str] = None pose_asset_id: Optional[str] = None outputs: List[JobOutputResponse] = [] events: List[JobEventResponse] = [] created_at: datetime updated_at: datetime class Config: from_attributes = True class JobListResponse(BaseModel): id: str mode: str submode: Optional[str] = None prompt: str error_message: Optional[str] = None status: str ground_truth_asset_id: Optional[str] = None motion_asset_id: Optional[str] = None audio_asset_id: Optional[str] = None pose_asset_id: Optional[str] = None outputs: List[JobOutputResponse] = [] created_at: datetime updated_at: datetime class Config: from_attributes = True