""" Pydantic schemas for request/response validation. Provides typed data structures for API endpoints. """ from pydantic import BaseModel, EmailStr, Field, ConfigDict from typing import Optional, List, Dict from datetime import datetime # ===== Authentication Schemas ===== class LoginRequest(BaseModel): """Login request payload.""" username: str = Field(..., min_length=3, max_length=64) password: str = Field(..., min_length=6, max_length=128) class LoginResponse(BaseModel): """Login response payload.""" access_token: str refresh_token: str token_type: str = "bearer" user: dict class RefreshTokenRequest(BaseModel): """Refresh token request.""" refresh_token: str class TokenResponse(BaseModel): """Token response.""" access_token: str token_type: str = "bearer" # ===== User Schemas ===== class UserBase(BaseModel): """User base fields.""" username: str email: Optional[str] = None country: Optional[str] = Field(None, max_length=2) real_name: Optional[str] = None class UserCreate(UserBase): """User registration fields.""" password: str = Field(..., min_length=8) model_config = ConfigDict( json_schema_extra={ "example": { "username": "player123", "password": "securepassword123", "email": "player@example.com", "country": "US" } } ) class UserUpdate(BaseModel): """User update fields.""" email: Optional[str] = None country: Optional[str] = None real_name: Optional[str] = None new_password: Optional[str] = Field(None, min_length=8, max_length=128) class UserResponse(BaseModel): """User response payload.""" id: int username: str email: Optional[str] country: Optional[str] real_name: Optional[str] privlevel: str vip_status: int is_active: bool is_banned: bool ban_reason: Optional[str] creation_date: datetime last_login: Optional[datetime] model_config = ConfigDict(from_attributes=True) # ===== Deck Schemas ===== class DeckCreate(BaseModel): """Deck creation request.""" name: str = Field(..., min_length=1, max_length=255) content: str = Field(..., min_length=1) folder_id: Optional[int] = None format: str = Field("native", pattern="^(native|plain)$") status: str = Field("DRAUGHT", pattern="^(DRAUGHT|FINAL)$") class DeckUpdate(BaseModel): """Deck update request.""" name: Optional[str] = None content: Optional[str] = None folder_id: Optional[int] = None status: Optional[str] = Field(None, pattern="^(DRAUGHT|FINAL)$") class DeckResponse(BaseModel): """Deck response payload.""" id: int name: str content: str format: str status: str folder_id: Optional[int] owner_id: int creation_date: datetime model_config = ConfigDict(from_attributes=True) class FolderCreate(BaseModel): """Folder creation request.""" name: str = Field(..., min_length=1, max_length=255) parent_id: Optional[int] = None class FolderResponse(BaseModel): """Folder response payload.""" id: int name: str parent_id: Optional[int] owner_id: int creation_date: datetime model_config = ConfigDict(from_attributes=True) # ===== Game Schemas ===== class GameCreate(BaseModel): """Game creation request.""" room_id: int game_type: Optional[str] = None description: Optional[str] = None password: Optional[str] = None class GameResponse(BaseModel): """Game response payload.""" id: int room_id: int game_type: Optional[str] description: Optional[str] with_password: bool max_players: int player_count: int started: bool creation_date: datetime model_config = ConfigDict(from_attributes=True) # ===== Room Schemas ===== class RoomResponse(BaseModel): """Room response payload.""" id: int name: str description: Optional[str] is_password_protected: bool game_types: List[str] = [] player_count: int = 0 creation_date: datetime model_config = ConfigDict(from_attributes=True) # ===== Ban Schemas ===== class BanCreate(BaseModel): """Ban creation request.""" user_id: int reason: str = Field(..., min_length=1, max_length=1000) expiration_time: Optional[datetime] = None class BanResponse(BaseModel): """Ban response payload.""" id: int user_id: int reason: str moderators: Optional[str] expiration_time: Optional[datetime] active: bool creation_date: datetime model_config = ConfigDict(from_attributes=True) # ===== Auth Error Responses ===== class ErrorResponse(BaseModel): """Standard error response.""" detail: str class ValidationErrorResponse(BaseModel): """Validation error response.""" detail: List[dict] # Pydantic validation errors # ===== Pagination Schemas ===== class PaginationParams(BaseModel): """Common pagination parameters.""" page: int = Field(1, ge=1) page_size: int = Field(50, ge=1, le=100) class PaginatedResponse(BaseModel): """Generic paginated response.""" items: List[dict] total: int page: int page_size: int total_pages: int # ===== Card Mirror Schemas ===== class CardMirrorResponse(BaseModel): """Mirrored card data for user decks.""" id: int source_id: Optional[int] name: str mana_cost: Optional[str] type_line: Optional[str] oracle_text: Optional[str] power: Optional[str] toughness: Optional[str] rarity: Optional[str] layout: Optional[str] artist: Optional[str] flavor_text: Optional[str] numbers: Optional[str] identifiers: Optional[str] # JSON string images: Optional[str] # JSON string image: Optional[str] card_parts: Optional[str] keywords: Optional[str] legalities: Optional[str] # JSON string set_code: Optional[str] set_name: Optional[str] synced_at: Optional[datetime] created_at: datetime model_config = ConfigDict(from_attributes=True) class DeckCardLinkResponse(BaseModel): """Deck-card link response.""" id: int deck_id: int card_id: int quantity: int zone: str card: CardMirrorResponse model_config = ConfigDict(from_attributes=True) class DeckWithCardsResponse(BaseModel): """Deck response with card links.""" id: int name: str content: str format: str status: str folder_id: Optional[int] owner_id: int creation_date: datetime card_links: List[DeckCardLinkResponse] = [] model_config = ConfigDict(from_attributes=True)