Files
mtgonline/backend/app/schemas/user_deck_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

218 lines
5.0 KiB
Python

"""Pydantic schemas for user deck building features."""
from pydantic import BaseModel, Field, ConfigDict
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."""
model_config = ConfigDict(from_attributes=True)
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 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."""
model_config = ConfigDict(from_attributes=True)
id: int
deck_id: int
card_id: int
quantity: int
zone: str
position: Optional[int]
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."""
model_config = ConfigDict(from_attributes=True)
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 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."""
model_config = ConfigDict(from_attributes=True)
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 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)