- 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
158 lines
3.8 KiB
Python
158 lines
3.8 KiB
Python
"""
|
|
Pydantic schemas for user card collection features.
|
|
|
|
Covers card collection CRUD operations, wishlist management, and collection statistics.
|
|
"""
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
|
|
|
|
# ===== Enum Types =====
|
|
|
|
class CardCondition(str, Enum):
|
|
"""Card condition ratings."""
|
|
NEAR_MINT = "NEAR_MINT"
|
|
LIGHTLY_PLAYED = "LIGHTLY_PLAYED"
|
|
MODERATELY_PLAYED = "MODERATELY_PLAYED"
|
|
HEAVILY_PLAYED = "HEAVILY_PLAYED"
|
|
DAMAGED = "DAMAGED"
|
|
|
|
|
|
class AcquisitionMethod(str, Enum):
|
|
"""How a card was acquired."""
|
|
PACK_OPENING = "PACK_OPENING"
|
|
TRADE = "TRADE"
|
|
PURCHASE = "PURCHASE"
|
|
GIFT = "GIFT"
|
|
CONTEST = "CONTEST"
|
|
OTHER = "OTHER"
|
|
|
|
|
|
# ===== Card Collection Schemas =====
|
|
|
|
class CardCollectionCreate(BaseModel):
|
|
"""Card collection item creation request."""
|
|
card_id: int
|
|
quantity: int = Field(1, ge=1)
|
|
condition: CardCondition = CardCondition.NEAR_MINT
|
|
language: str = Field("EN", max_length=5)
|
|
is_foil: bool = False
|
|
is_alt_art: bool = False
|
|
acquired_date: Optional[datetime] = None
|
|
acquisition_method: Optional[AcquisitionMethod] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class CardCollectionUpdate(BaseModel):
|
|
"""Card collection item update request."""
|
|
quantity: Optional[int] = None
|
|
condition: Optional[CardCondition] = None
|
|
language: Optional[str] = None
|
|
is_foil: Optional[bool] = None
|
|
is_alt_art: Optional[bool] = None
|
|
acquired_date: Optional[datetime] = None
|
|
acquisition_method: Optional[AcquisitionMethod] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class CardCollectionResponse(BaseModel):
|
|
"""Card collection item response."""
|
|
id: int
|
|
user_id: int
|
|
card_id: int
|
|
quantity: int
|
|
condition: str
|
|
language: str
|
|
is_foil: bool
|
|
is_alt_art: bool
|
|
acquired_date: Optional[datetime]
|
|
acquisition_method: Optional[str]
|
|
notes: Optional[str]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class CardCollectionListResponse(BaseModel):
|
|
"""List of user card collection."""
|
|
cards: List[CardCollectionResponse]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
total_pages: int
|
|
|
|
|
|
# ===== Wishlist Schemas =====
|
|
|
|
class WishlistCreate(BaseModel):
|
|
"""Wishlist item creation request."""
|
|
card_id: Optional[int] = None
|
|
max_price: Optional[float] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class WishlistUpdate(BaseModel):
|
|
"""Wishlist item update request."""
|
|
max_price: Optional[float] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class WishlistResponse(BaseModel):
|
|
"""Wishlist item response."""
|
|
id: int
|
|
user_id: int
|
|
card_id: int
|
|
max_price: Optional[float]
|
|
notes: Optional[str]
|
|
created_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class WishlistListResponse(BaseModel):
|
|
"""List of wishlist items."""
|
|
items: List[WishlistResponse]
|
|
total: int
|
|
|
|
|
|
# ===== Collection Statistics Schemas =====
|
|
|
|
class CollectionStatistics(BaseModel):
|
|
"""User collection statistics summary."""
|
|
total_cards: int
|
|
unique_cards: int
|
|
total_quantity: int
|
|
foil_count: int
|
|
alt_art_count: int
|
|
condition_breakdown: Dict[str, int]
|
|
language_breakdown: Dict[str, int]
|
|
acquisition_breakdown: Dict[str, int]
|
|
|
|
|
|
class CollectionSummaryResponse(BaseModel):
|
|
"""Collection summary with statistics."""
|
|
statistics: CollectionStatistics
|
|
recent_acquisitions: List[CardCollectionResponse]
|
|
top_cards: List[CardCollectionResponse]
|
|
|
|
|
|
# ===== Generic Response Schemas =====
|
|
|
|
class MessageResponse(BaseModel):
|
|
"""Generic message response."""
|
|
message: str
|
|
|
|
|
|
class CountResponse(BaseModel):
|
|
"""Generic count response."""
|
|
count: int
|
|
|
|
|
|
class ErrorDetail(BaseModel):
|
|
"""Error detail."""
|
|
error: str
|
|
detail: str
|