Files
mtgonline/backend/app/schemas/user_deck_schemas.py
T
akadmin c23f88cd41 feat: complete deckbuilding feature with local card mirror
- Add MtgonlineCard model (local card data mirror in mtgonline DB)
- Create user_deck.py models (UserDeck, UserDeckCard, DeckPrecedent, CardSuggestion)
- Create user_deck_schemas.py with Pydantic schemas
- Update decks.py router to use local MtgonlineCard instead of cross-DB MtgCard
- Add migration 002 (user deck building tables)
- Add migration 003 (mtgonline_cards table)
- All card lookups now use local mirror for fast queries
- No cross-DB joins in deckbuilding endpoints
2026-07-24 04:42:22 +00:00

243 lines
5.3 KiB
Python

"""Pydantic schemas for user deck building features."""
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
from enum import Enum
# ===== Enum Types =====
class DeckStatus(str, Enum):
DRAFT = "DRAFT"
FINAL = "FINAL"
class DeckZone(str, Enum):
MAIN = "main"
SIDEBOARD = "sideboard"
class SuggestionType(str, Enum):
SIMILAR = "SIMILAR"
PAIRING = "PAIRING"
ALTERNATIVE = "ALTERNATIVE"
# ===== Deck Schemas =====
class UserDeckCreate(BaseModel):
"""Deck creation request."""
name: str = Field(..., min_length=1, max_length=255)
folder_id: Optional[int] = None
format: Optional[str] = Field("standard", max_length=50)
notes: Optional[str] = None
is_precedent: bool = False
precedent_name: Optional[str] = None
class UserDeckUpdate(BaseModel):
"""Deck update request."""
name: Optional[str] = None
folder_id: Optional[int] = None
format: Optional[str] = None
notes: Optional[str] = None
status: Optional[DeckStatus] = None
is_precedent: Optional[bool] = None
precedent_name: Optional[str] = None
class UserDeckResponse(BaseModel):
"""Deck response with card count."""
id: int
user_id: int
name: str
status: str
folder_id: Optional[int]
format: Optional[str]
notes: Optional[str]
is_precedent: bool
precedent_name: Optional[str]
created_at: datetime
updated_at: datetime
card_count: int = 0
is_owner: bool = False
class Config:
from_attributes = True
class UserDeckListResponse(BaseModel):
"""List of user decks."""
decks: List[UserDeckResponse]
total: int
page: int
page_size: int
total_pages: int
# ===== Deck Card Schemas =====
class DeckCardCreate(BaseModel):
"""Add card to deck."""
card_id: int
quantity: int = Field(1, ge=1)
zone: DeckZone = DeckZone.MAIN
position: Optional[int] = None
class DeckCardUpdate(BaseModel):
"""Update card in deck."""
quantity: Optional[int] = None
zone: Optional[DeckZone] = None
position: Optional[int] = None
class DeckCardResponse(BaseModel):
"""Deck card response."""
id: int
deck_id: int
card_id: int
quantity: int
zone: str
position: Optional[int]
created_at: datetime
class Config:
from_attributes = True
class DeckCardWithDetailsResponse(DeckCardResponse):
"""Deck card with card details."""
card_name: str = ""
card_type_line: str = ""
card_image: Optional[str] = None
class DeckCardListResponse(BaseModel):
"""List of deck cards."""
cards: List[DeckCardWithDetailsResponse]
total: int
# ===== Deck Precedent Schemas =====
class PrecedentCreate(BaseModel):
"""Create deck precedent."""
name: str = Field(..., min_length=1, max_length=255)
description: Optional[str] = None
format: Optional[str] = Field("standard", max_length=50)
is_public: bool = True
class PrecedentUpdate(BaseModel):
"""Update deck precedent."""
name: Optional[str] = None
description: Optional[str] = None
format: Optional[str] = None
is_public: Optional[bool] = None
class PrecedentResponse(BaseModel):
"""Deck precedent response."""
id: int
name: str
description: Optional[str]
format: Optional[str]
is_public: bool
created_by: Optional[int]
created_at: datetime
updated_at: datetime
card_count: int = 0
class Config:
from_attributes = True
class PrecedentListResponse(BaseModel):
"""List of deck precedents."""
precedents: List[PrecedentResponse]
total: int
# ===== Card Suggestion Schemas =====
class SuggestionCreate(BaseModel):
"""Create card suggestion."""
card_id: int
source_card_id: Optional[int] = None
suggestion_type: SuggestionType = SuggestionType.SIMILAR
confidence: Optional[float] = Field(None, ge=0.0, le=1.0)
notes: Optional[str] = None
class SuggestionResponse(BaseModel):
"""Card suggestion response."""
id: int
deck_id: int
card_id: int
source_card_id: Optional[int]
suggestion_type: str
confidence: Optional[float]
notes: Optional[str]
created_at: datetime
card_name: str = ""
class Config:
from_attributes = True
class SuggestionListResponse(BaseModel):
"""List of card suggestions."""
suggestions: List[SuggestionResponse]
total: int
# ===== Deck Action Schemas =====
class DeckFinalizeRequest(BaseModel):
"""Request to finalize a deck."""
status: DeckStatus = DeckStatus.FINAL
class DeckFinalizeResponse(BaseModel):
"""Response after finalizing a deck."""
deck_id: int
status: str
message: str
class DeckDeleteResponse(BaseModel):
"""Response after deleting a deck."""
deck_id: int
message: str
# ===== Search Schemas =====
class CardSearchRequest(BaseModel):
"""Card search request."""
query: str = Field(..., min_length=1, max_length=100)
limit: int = Field(50, ge=1, le=200)
offset: int = Field(0, ge=0)
class CardSearchResponse(BaseModel):
"""Card search response."""
cards: List[Dict[str, Any]]
total: int
page: int
page_size: int
total_pages: int
# ===== Generic Schemas =====
class MessageResponse(BaseModel):
"""Generic message response."""
message: str
class CountResponse(BaseModel):
"""Generic count response."""
count: int