Files
mtgonline/backend/app/schemas/schemas.py
T
akadmin e9f5377e0a Phase 5: Complete Core Game Engine implementation
- Added MTGOnlineGame, GamePlayer, GameHistory models
- Implemented full game CRUD endpoints (create, list, get, join, leave, start, end)
- Integrated WebSocket game server with database persistence
- Added Commander deck validation service (100-card min, color identity, ban list)
- Created Alembic migration 006 for new game tables
- All game endpoints now database-backed (no more stubs)
2026-08-27 05:29:38 +00:00

336 lines
7.9 KiB
Python

"""
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: str = Field("Commander", pattern="^(Commander|Standard|Modern|Pauper)$")
max_players: int = Field(4, ge=2, le=10)
is_password_protected: bool = False
description: Optional[str] = None
class GameUpdate(BaseModel):
"""Game update request."""
game_type: Optional[str] = Field(None, pattern="^(Commander|Standard|Modern|Pauper)$")
max_players: Optional[int] = Field(None, ge=2, le=10)
is_password_protected: Optional[bool] = None
class GamePlayerResponse(BaseModel):
"""Game player response."""
id: int
game_id: int
user_id: int
seat_number: Optional[int]
life_total: int
is_ready: bool
is_host: bool
joined_at: datetime
model_config = ConfigDict(from_attributes=True)
class GameResponse(BaseModel):
"""Game response payload."""
id: int
room_id: int
host_id: int
game_type: str
state: str
is_password_protected: bool
max_players: int
current_players: int
created_at: datetime
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
players: List[GamePlayerResponse] = []
model_config = ConfigDict(from_attributes=True)
class GameHistoryResponse(BaseModel):
"""Game history response."""
id: int
game_id: Optional[int] = None
room_id: Optional[int] = None
game_type: str
winner_id: Optional[int] = None
duration_seconds: Optional[int] = None
result_type: Optional[str] = None
replay_data: Optional[dict] = None
created_at: datetime
model_config = ConfigDict(from_attributes=True)
class GameJoinResponse(BaseModel):
"""Game join/leave response."""
message: str
game_id: int
player_id: int
current_players: int
max_players: int
# ===== 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)