"""Image schemas for request/response validation.""" from datetime import datetime from typing import Any from uuid import UUID from pydantic import BaseModel, Field, field_validator class ImageMetadata(BaseModel): """Image metadata structure.""" format: str = Field(..., description="Image format (jpeg, png, etc)") checksum: str = Field(..., description="SHA256 checksum of file") exif: dict[str, Any] | None = Field(None, description="EXIF data if available") thumbnails: dict[str, str] = Field(default_factory=dict, description="Thumbnail URLs by quality level") class ImageUploadResponse(BaseModel): """Response after successful image upload.""" id: UUID filename: str storage_path: str file_size: int mime_type: str width: int height: int metadata: dict[str, Any] created_at: datetime class Config: """Pydantic config.""" from_attributes = True class ImageResponse(BaseModel): """Full image response with all fields.""" id: UUID user_id: UUID filename: str storage_path: str file_size: int mime_type: str width: int height: int metadata: dict[str, Any] created_at: datetime reference_count: int class Config: """Pydantic config.""" from_attributes = True class BoardImageCreate(BaseModel): """Schema for adding image to board.""" image_id: UUID = Field(..., description="ID of uploaded image") position: dict[str, float] = Field(default_factory=lambda: {"x": 0, "y": 0}, description="Canvas position") transformations: dict[str, Any] = Field( default_factory=lambda: { "scale": 1.0, "rotation": 0, "opacity": 1.0, "flipped_h": False, "flipped_v": False, "greyscale": False, }, description="Image transformations", ) z_order: int = Field(default=0, description="Layer order") @field_validator("position") @classmethod def validate_position(cls, v: dict[str, float]) -> dict[str, float]: """Validate position has x and y.""" if "x" not in v or "y" not in v: raise ValueError("Position must contain 'x' and 'y' coordinates") return v class BoardImageUpdate(BaseModel): """Schema for updating board image position/transformations.""" position: dict[str, float] | None = Field(None, description="Canvas position") transformations: dict[str, Any] | None = Field(None, description="Image transformations") z_order: int | None = Field(None, description="Layer order") group_id: UUID | None = Field(None, description="Group membership") @field_validator("position") @classmethod def validate_position(cls, v: dict[str, float] | None) -> dict[str, float] | None: """Validate position has x and y if provided.""" if v is not None and ("x" not in v or "y" not in v): raise ValueError("Position must contain 'x' and 'y' coordinates") return v class BoardImageResponse(BaseModel): """Response for board image with all metadata.""" id: UUID board_id: UUID image_id: UUID position: dict[str, float] transformations: dict[str, Any] z_order: int group_id: UUID | None created_at: datetime updated_at: datetime image: ImageResponse class Config: """Pydantic config.""" from_attributes = True class BulkImageUpdate(BaseModel): """Schema for bulk updating multiple images.""" image_ids: list[UUID] = Field(..., description="List of image IDs to update") position_delta: dict[str, float] | None = Field(None, description="Position delta to apply") transformations: dict[str, Any] | None = Field(None, description="Transformations to apply") z_order_delta: int | None = Field(None, description="Z-order delta to apply") @field_validator("position_delta") @classmethod def validate_position_delta(cls, v: dict[str, float] | None) -> dict[str, float] | None: """Validate position delta has dx and dy.""" if v is not None and ("dx" not in v or "dy" not in v): raise ValueError("Position delta must contain 'dx' and 'dy'") return v class BulkUpdateResponse(BaseModel): """Response for bulk update operation.""" updated_count: int = Field(..., description="Number of images updated") failed_count: int = Field(default=0, description="Number of images that failed to update") image_ids: list[UUID] = Field(..., description="IDs of successfully updated images") class ImageListResponse(BaseModel): """Paginated list of images.""" images: list[ImageResponse] total: int page: int page_size: int