Files
mtgonline/backend/app/schemas/schemas.py
T
akadmin 167a352d44 Fix backend test suite - all 20 tests passing
- Updated JWT tokens to include privlevel for authorization
- Fixed test fixtures to properly hash passwords with bcrypt
- Fixed client fixture to share database session with test fixtures
- Reordered deck router routes to prevent conflicts
- Removed relationship fields from FolderResponse schema
- Updated conftest.py with proper async session management
2026-07-18 16:58:39 +00:00

231 lines
5.0 KiB
Python

"""
Pydantic schemas for request/response validation.
Provides typed data structures for API endpoints.
"""
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
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)
class Config:
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]
class Config:
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)$")
class DeckUpdate(BaseModel):
"""Deck update request."""
name: Optional[str] = None
content: Optional[str] = None
folder_id: Optional[int] = None
class DeckResponse(BaseModel):
"""Deck response payload."""
id: int
name: str
content: str
format: str
folder_id: Optional[int]
owner_id: int
creation_date: datetime
class Config:
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
class Config:
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
class Config:
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
class Config:
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
class Config:
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