Files
mtgonline/backend/app/schemas/schemas.py
T
akadmin 1df04aea52 Phase 7: End-to-End API Testing - Database setup, migrations, and application fixes
- Switched from psycopg2 to asyncpg for async SQLAlchemy support
- Fixed router registration in main.py - removed duplicate prefixes
- Added user_data export to routers/__init__.py
- Refactored decks router to use DeckManager service layer
- Integrated FuzzyCardMatcher into card_router search endpoints
- Made WishlistCreate.card_id optional for proper schema validation
- Set PostgreSQL password and configured scram-sha-256 auth
- Updated alembic.ini to use local PostgreSQL instead of Docker hostname
- Created generic_schemas.py for reusable schema patterns
- Added test_routers.py and test_schema_validation.py test files
- All 6 Alembic migrations applied successfully (37 tables created)
- Application running on port 8000 with all services connected
2026-08-18 03:35:19 +00:00

287 lines
6.6 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: 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)