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
This commit is contained in:
@@ -19,8 +19,6 @@ from app.schemas.user_data_schemas import (
|
||||
GameReplayCreate, GameReplayUpdate, GameReplayResponse, GameReplayListResponse,
|
||||
GameOutcomeCreate, GameOutcomeResponse, GameOutcomeListResponse,
|
||||
UserStatisticsResponse, StatisticsUpdateResponse,
|
||||
CardCollectionCreate, CardCollectionUpdate, CardCollectionResponse, CardCollectionListResponse,
|
||||
WishlistCreate, WishlistUpdate, WishlistResponse, WishlistListResponse,
|
||||
GroupCreate, GroupUpdate, GroupMemberCreate, GroupMemberUpdate, GroupMemberRemove,
|
||||
GroupResponse, GroupListResponse, GroupChatMessageCreate, GroupChatMessageResponse, GroupChatMessageListResponse,
|
||||
NetworkCreate, NetworkUpdate, NetworkMemberCreate,
|
||||
@@ -29,6 +27,12 @@ from app.schemas.user_data_schemas import (
|
||||
ActivityLogEntry, ActivityLogListResponse,
|
||||
MessageResponse, CountResponse, ErrorDetail,
|
||||
)
|
||||
from app.schemas.user_card_collection import (
|
||||
CardCondition, AcquisitionMethod,
|
||||
CardCollectionCreate, CardCollectionUpdate, CardCollectionResponse, CardCollectionListResponse,
|
||||
WishlistCreate, WishlistUpdate, WishlistResponse, WishlistListResponse,
|
||||
CollectionStatistics, CollectionSummaryResponse,
|
||||
)
|
||||
from app.schemas.user_deck_schemas import (
|
||||
DeckStatus, DeckZone, SuggestionType,
|
||||
UserDeckCreate, UserDeckUpdate, UserDeckResponse, UserDeckListResponse,
|
||||
@@ -36,20 +40,30 @@ from app.schemas.user_deck_schemas import (
|
||||
PrecedentCreate, PrecedentUpdate, PrecedentResponse, PrecedentListResponse,
|
||||
SuggestionCreate, SuggestionResponse, SuggestionListResponse,
|
||||
DeckFinalizeRequest, DeckFinalizeResponse, DeckDeleteResponse,
|
||||
CardSearchRequest, CardSearchResponse,
|
||||
)
|
||||
from app.schemas.user_card_collection import (
|
||||
CardCondition, AcquisitionMethod,
|
||||
CollectionStatistics, CollectionSummaryResponse,
|
||||
CardSearchRequest,
|
||||
)
|
||||
from app.schemas.card_import_schemas import (
|
||||
CardImportRequest, CardImportResponse, CardImportStatusResponse,
|
||||
CardMatchResult, CardImportSummary,
|
||||
CardImportRequest, CardImportResponse as CardImportResponseV2, CardImportStatusResponse as CardImportStatusResponseV2,
|
||||
CardMatchResult as CardMatchResultV2, CardImportSummary as CardImportSummaryV2,
|
||||
CardImportBatchCreate, CardImportBatchResponse,
|
||||
UserCardImportCreate, UserCardImportResponse, UserCardImportRecordResponse,
|
||||
)
|
||||
from app.schemas.card_search_schemas import (
|
||||
CardResponse, SetResponse, CardTypeResponse,
|
||||
CardSearchResponse as CardSearchResponseV2,
|
||||
CardImportResponse as CardImportResponseV3,
|
||||
CardImportStatusResponse as CardImportStatusResponseV3,
|
||||
CardMatchResult as CardMatchResultV3,
|
||||
CardImportSummary as CardImportSummaryV3,
|
||||
)
|
||||
from app.schemas.game_schemas import (
|
||||
GameCreate as GameCreateV2, GameResponse as GameResponseV2, GameJoinRequest, GameLeaveRequest,
|
||||
GameListResponse, GamePlayerResponse, GameStateResponse,
|
||||
)
|
||||
from app.schemas.mtg_card_schemas import (
|
||||
MtgCardResponse, MtgCardSearchRequest, MtgCardSearchResponse,
|
||||
MtgSetResponse, MtgCardMirrorResponse,
|
||||
DeckCardLinkResponse as DeckCardLinkResponseV2, DeckWithCardsResponse as DeckWithCardsResponseV2,
|
||||
)
|
||||
from app.schemas.proto_messages import (
|
||||
ProtoMessageBase, SessionCommand, GameCommand, GameEvent, Response,
|
||||
@@ -60,14 +74,6 @@ from app.schemas.protocol_constants import (
|
||||
SessionCommandType, GameCommandType, GameEventType, ResponseCode,
|
||||
ZoneType, UserLevelFlag,
|
||||
)
|
||||
from app.schemas.game_schemas import (
|
||||
GameCreate, GameResponse, GameJoinRequest, GameLeaveRequest,
|
||||
GameListResponse, GamePlayerResponse, GameStateResponse,
|
||||
)
|
||||
from app.schemas.mtg_card_schemas import (
|
||||
MtgCardResponse, MtgCardSearchRequest, MtgCardSearchResponse,
|
||||
MtgSetResponse, MtgCardMirrorResponse, DeckCardLinkResponse, DeckWithCardsResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Auth
|
||||
@@ -96,8 +102,6 @@ __all__ = [
|
||||
"GameReplayCreate", "GameReplayUpdate", "GameReplayResponse", "GameReplayListResponse",
|
||||
"GameOutcomeCreate", "GameOutcomeResponse", "GameOutcomeListResponse",
|
||||
"UserStatisticsResponse", "StatisticsUpdateResponse",
|
||||
"CardCollectionCreate", "CardCollectionUpdate", "CardCollectionResponse", "CardCollectionListResponse",
|
||||
"WishlistCreate", "WishlistUpdate", "WishlistResponse", "WishlistListResponse",
|
||||
"GroupCreate", "GroupUpdate", "GroupMemberCreate", "GroupMemberUpdate", "GroupMemberRemove",
|
||||
"GroupResponse", "GroupListResponse", "GroupChatMessageCreate", "GroupChatMessageResponse", "GroupChatMessageListResponse",
|
||||
"NetworkCreate", "NetworkUpdate", "NetworkMemberCreate",
|
||||
@@ -105,6 +109,11 @@ __all__ = [
|
||||
"UserPreferenceUpdate", "UserPreferenceResponse",
|
||||
"ActivityLogEntry", "ActivityLogListResponse",
|
||||
"MessageResponse", "CountResponse", "ErrorDetail",
|
||||
# Card Collection
|
||||
"CardCondition", "AcquisitionMethod",
|
||||
"CardCollectionCreate", "CardCollectionUpdate", "CardCollectionResponse", "CardCollectionListResponse",
|
||||
"WishlistCreate", "WishlistUpdate", "WishlistResponse", "WishlistListResponse",
|
||||
"CollectionStatistics", "CollectionSummaryResponse",
|
||||
# User Deck
|
||||
"DeckStatus", "DeckZone", "SuggestionType",
|
||||
"UserDeckCreate", "UserDeckUpdate", "UserDeckResponse", "UserDeckListResponse",
|
||||
@@ -113,16 +122,21 @@ __all__ = [
|
||||
"SuggestionCreate", "SuggestionResponse", "SuggestionListResponse",
|
||||
"DeckFinalizeRequest", "DeckFinalizeResponse", "DeckDeleteResponse",
|
||||
"CardSearchRequest", "CardSearchResponse",
|
||||
# Card Collection
|
||||
"CardCondition", "AcquisitionMethod",
|
||||
"CollectionStatistics", "CollectionSummaryResponse",
|
||||
# Card Import
|
||||
"CardImportRequest", "CardImportResponse", "CardImportStatusResponse",
|
||||
"CardMatchResult", "CardImportSummary",
|
||||
"CardImportRequest", "CardImportResponseV2", "CardImportStatusResponseV2",
|
||||
"CardMatchResultV2", "CardImportSummaryV2",
|
||||
"CardImportBatchCreate", "CardImportBatchResponse",
|
||||
"UserCardImportCreate", "UserCardImportResponse", "UserCardImportRecordResponse",
|
||||
# Card Search
|
||||
"CardResponse", "SetResponse", "CardTypeResponse",
|
||||
"CardSearchResponseV2", "CardImportResponseV3", "CardImportStatusResponseV3",
|
||||
"CardMatchResultV3", "CardImportSummaryV3",
|
||||
# Game Schemas
|
||||
"GameCreateV2", "GameResponseV2",
|
||||
# MTG Card Schemas
|
||||
"MtgCardResponse", "MtgCardSearchRequest", "MtgCardSearchResponse",
|
||||
"MtgSetResponse", "MtgCardMirrorResponse",
|
||||
"DeckCardLinkResponseV2", "DeckWithCardsResponseV2",
|
||||
# Proto Messages
|
||||
"ProtoMessageBase", "SessionCommand", "GameCommand", "GameEvent", "Response",
|
||||
"ServerInfoUser", "ServerInfoDeckStorageFile", "ServerInfoDeckStorageFolder",
|
||||
|
||||
@@ -111,19 +111,3 @@ class UserCardImportRecordResponse(BaseModel):
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# Generic response models
|
||||
class MessageResponse(BaseModel):
|
||||
"""Generic message response."""
|
||||
message: str
|
||||
|
||||
|
||||
class CountResponse(BaseModel):
|
||||
"""Generic count response."""
|
||||
count: int
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Error response with details."""
|
||||
detail: str
|
||||
error_code: Optional[str] = None
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Pydantic schemas for card search and import features."""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
@@ -23,8 +23,7 @@ class CardResponse(BaseModel):
|
||||
identifiers: Optional[Dict[str, Any]] = None
|
||||
images: Optional[Dict[str, Any]] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SetResponse(BaseModel):
|
||||
@@ -35,8 +34,7 @@ class SetResponse(BaseModel):
|
||||
release_date: Optional[datetime] = None
|
||||
card_count: Optional[int] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class CardTypeResponse(BaseModel):
|
||||
@@ -93,6 +91,7 @@ class CardImportSummary(BaseModel):
|
||||
import_id: Optional[int] = None
|
||||
|
||||
|
||||
# Generic response models
|
||||
class MessageResponse(BaseModel):
|
||||
"""Generic message response."""
|
||||
message: str
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Pydantic schemas for game features."""
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ class GameCreate(BaseModel):
|
||||
game_type: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
max_players: int = Field(4, ge=2, le=8)
|
||||
|
||||
|
||||
class GameResponse(BaseModel):
|
||||
@@ -31,7 +30,6 @@ class GameResponse(BaseModel):
|
||||
class GameJoinRequest(BaseModel):
|
||||
"""Game join request."""
|
||||
game_id: int
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class GameLeaveRequest(BaseModel):
|
||||
@@ -50,15 +48,12 @@ class GamePlayerResponse(BaseModel):
|
||||
user_id: int
|
||||
username: str
|
||||
deck_id: Optional[int] = None
|
||||
is_ready: bool = False
|
||||
is_host: bool = False
|
||||
deck_name: Optional[str] = None
|
||||
|
||||
|
||||
class GameStateResponse(BaseModel):
|
||||
"""Game state response."""
|
||||
game_id: int
|
||||
state: str
|
||||
players: List[GamePlayerResponse]
|
||||
turn: int
|
||||
phase: str
|
||||
zones: Dict[str, Any]
|
||||
updated_at: datetime
|
||||
turn: Optional[int] = None
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"""Generic response schemas used across multiple modules."""
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
"""Generic message response."""
|
||||
message: str
|
||||
|
||||
|
||||
class CountResponse(BaseModel):
|
||||
"""Generic count response."""
|
||||
count: int
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response."""
|
||||
detail: str
|
||||
|
||||
|
||||
class ValidationErrorResponse(BaseModel):
|
||||
"""Validation error response."""
|
||||
detail: List[dict]
|
||||
|
||||
|
||||
class ErrorDetail(BaseModel):
|
||||
"""Error detail."""
|
||||
error: str
|
||||
detail: str
|
||||
@@ -1,33 +1,33 @@
|
||||
"""Pydantic schemas for MTG card data."""
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class MtgCardResponse(BaseModel):
|
||||
"""MTG card response with full details."""
|
||||
"""MTG card response."""
|
||||
id: int
|
||||
source_id: Optional[int] = None
|
||||
source_id: Optional[int]
|
||||
name: str
|
||||
mana_cost: Optional[str] = None
|
||||
type_line: Optional[str] = None
|
||||
oracle_text: Optional[str] = None
|
||||
power: Optional[str] = None
|
||||
toughness: Optional[str] = None
|
||||
rarity: Optional[str] = None
|
||||
layout: Optional[str] = None
|
||||
artist: Optional[str] = None
|
||||
flavor_text: Optional[str] = None
|
||||
numbers: Optional[str] = None
|
||||
identifiers: Optional[Dict[str, Any]] = None
|
||||
images: Optional[Dict[str, Any]] = None
|
||||
image: Optional[str] = None
|
||||
card_parts: Optional[List[str]] = None
|
||||
keywords: Optional[List[str]] = None
|
||||
legalities: Optional[Dict[str, str]] = None
|
||||
set_code: Optional[str] = None
|
||||
set_name: Optional[str] = None
|
||||
synced_at: Optional[datetime] = None
|
||||
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]
|
||||
images: Optional[str]
|
||||
image: Optional[str]
|
||||
card_parts: Optional[str]
|
||||
keywords: Optional[str]
|
||||
legalities: Optional[str]
|
||||
set_code: Optional[str]
|
||||
set_name: Optional[str]
|
||||
synced_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -35,12 +35,9 @@ class MtgCardResponse(BaseModel):
|
||||
|
||||
class MtgCardSearchRequest(BaseModel):
|
||||
"""MTG card search request."""
|
||||
query: str = Field(..., min_length=1, max_length=100)
|
||||
set_code: Optional[str] = None
|
||||
rarity: Optional[str] = None
|
||||
type_line: Optional[str] = None
|
||||
limit: int = Field(50, ge=1, le=200)
|
||||
offset: int = Field(0, ge=0)
|
||||
query: str
|
||||
limit: int = 50
|
||||
offset: int = 0
|
||||
|
||||
|
||||
class MtgCardSearchResponse(BaseModel):
|
||||
@@ -55,41 +52,38 @@ class MtgCardSearchResponse(BaseModel):
|
||||
class MtgSetResponse(BaseModel):
|
||||
"""MTG set response."""
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
release_date: Optional[datetime] = None
|
||||
card_count: Optional[int] = None
|
||||
type: Optional[str] = None
|
||||
border: Optional[str] = None
|
||||
mcm_id: Optional[int] = None
|
||||
code: str
|
||||
release_date: Optional[datetime]
|
||||
card_count: Optional[int]
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MtgCardMirrorResponse(BaseModel):
|
||||
"""Mirrored card data for user decks."""
|
||||
"""Mirrored card data response."""
|
||||
id: int
|
||||
source_id: Optional[int] = None
|
||||
source_id: Optional[int]
|
||||
name: str
|
||||
mana_cost: Optional[str] = None
|
||||
type_line: Optional[str] = None
|
||||
oracle_text: Optional[str] = None
|
||||
power: Optional[str] = None
|
||||
toughness: Optional[str] = None
|
||||
rarity: Optional[str] = None
|
||||
layout: Optional[str] = None
|
||||
artist: Optional[str] = None
|
||||
flavor_text: Optional[str] = None
|
||||
numbers: Optional[str] = None
|
||||
identifiers: Optional[Dict[str, Any]] = None
|
||||
images: Optional[Dict[str, Any]] = None
|
||||
image: Optional[str] = None
|
||||
card_parts: Optional[str] = None
|
||||
keywords: Optional[str] = None
|
||||
legalities: Optional[Dict[str, str]] = None
|
||||
set_code: Optional[str] = None
|
||||
set_name: Optional[str] = None
|
||||
synced_at: Optional[datetime] = None
|
||||
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]
|
||||
images: Optional[str]
|
||||
image: Optional[str]
|
||||
card_parts: Optional[str]
|
||||
keywords: Optional[str]
|
||||
legalities: Optional[str]
|
||||
set_code: Optional[str]
|
||||
set_name: Optional[str]
|
||||
synced_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -114,7 +108,7 @@ class DeckWithCardsResponse(BaseModel):
|
||||
content: str
|
||||
format: str
|
||||
status: str
|
||||
folder_id: Optional[int] = None
|
||||
folder_id: Optional[int]
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
card_links: List[DeckCardLinkResponse] = []
|
||||
|
||||
@@ -3,7 +3,7 @@ Pydantic schemas for request/response validation.
|
||||
|
||||
Provides typed data structures for API endpoints.
|
||||
"""
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from pydantic import BaseModel, EmailStr, Field, ConfigDict
|
||||
from typing import Optional, List, Dict
|
||||
from datetime import datetime
|
||||
|
||||
@@ -49,8 +49,8 @@ class UserCreate(UserBase):
|
||||
"""User registration fields."""
|
||||
password: str = Field(..., min_length=8)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
model_config = ConfigDict(
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"username": "player123",
|
||||
"password": "securepassword123",
|
||||
@@ -58,6 +58,7 @@ class UserCreate(UserBase):
|
||||
"country": "US"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
@@ -83,8 +84,7 @@ class UserResponse(BaseModel):
|
||||
creation_date: datetime
|
||||
last_login: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Deck Schemas =====
|
||||
@@ -117,8 +117,7 @@ class DeckResponse(BaseModel):
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class FolderCreate(BaseModel):
|
||||
@@ -135,8 +134,7 @@ class FolderResponse(BaseModel):
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Game Schemas =====
|
||||
@@ -161,8 +159,7 @@ class GameResponse(BaseModel):
|
||||
started: bool
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Room Schemas =====
|
||||
@@ -177,8 +174,7 @@ class RoomResponse(BaseModel):
|
||||
player_count: int = 0
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Ban Schemas =====
|
||||
@@ -200,8 +196,7 @@ class BanResponse(BaseModel):
|
||||
active: bool
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Auth Error Responses =====
|
||||
@@ -261,8 +256,7 @@ class CardMirrorResponse(BaseModel):
|
||||
synced_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckCardLinkResponse(BaseModel):
|
||||
@@ -274,8 +268,7 @@ class DeckCardLinkResponse(BaseModel):
|
||||
zone: str
|
||||
card: CardMirrorResponse
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckWithCardsResponse(BaseModel):
|
||||
@@ -290,5 +283,4 @@ class DeckWithCardsResponse(BaseModel):
|
||||
creation_date: datetime
|
||||
card_links: List[DeckCardLinkResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -3,7 +3,7 @@ Pydantic schemas for user card collection features.
|
||||
|
||||
Covers card collection CRUD operations, wishlist management, and collection statistics.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -73,8 +73,7 @@ class CardCollectionResponse(BaseModel):
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class CardCollectionListResponse(BaseModel):
|
||||
@@ -90,7 +89,7 @@ class CardCollectionListResponse(BaseModel):
|
||||
|
||||
class WishlistCreate(BaseModel):
|
||||
"""Wishlist item creation request."""
|
||||
card_id: int
|
||||
card_id: Optional[int] = None
|
||||
max_price: Optional[float] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
@@ -110,8 +109,7 @@ class WishlistResponse(BaseModel):
|
||||
notes: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class WishlistListResponse(BaseModel):
|
||||
|
||||
@@ -3,7 +3,7 @@ Pydantic schemas for user data features.
|
||||
|
||||
Covers sessions, decks, replays, cards, groups, networks, preferences, and activity logs.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -72,8 +72,7 @@ class SessionResponse(BaseModel):
|
||||
expires_at: datetime
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SessionCleanupResponse(BaseModel):
|
||||
@@ -108,8 +107,7 @@ class DeckVersionResponse(BaseModel):
|
||||
comment: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckVersionListResponse(BaseModel):
|
||||
@@ -160,8 +158,7 @@ class GameReplayResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
players: List[Dict[str, Any]] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GameReplayListResponse(BaseModel):
|
||||
@@ -199,8 +196,7 @@ class GameOutcomeResponse(BaseModel):
|
||||
rating_change: Optional[int]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GameOutcomeListResponse(BaseModel):
|
||||
@@ -225,8 +221,7 @@ class UserStatisticsResponse(BaseModel):
|
||||
last_game_date: Optional[datetime]
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class StatisticsUpdateResponse(BaseModel):
|
||||
@@ -240,94 +235,12 @@ class StatisticsUpdateResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
# ===== Card Collection Schemas =====
|
||||
|
||||
class CardCollectionCreate(BaseModel):
|
||||
"""Card collection item creation request."""
|
||||
card_id: int
|
||||
quantity: int = Field(1, ge=1)
|
||||
condition: str = Field("NEAR_MINT", max_length=20)
|
||||
language: str = Field("EN", max_length=5)
|
||||
is_foil: bool = False
|
||||
is_alt_art: bool = False
|
||||
acquired_date: Optional[datetime] = None
|
||||
acquisition_method: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
# ===== Card Collection Schemas (moved to user_card_collection.py) =====
|
||||
# These are kept here for backward compatibility but should be imported from user_card_collection.py
|
||||
|
||||
|
||||
class CardCollectionUpdate(BaseModel):
|
||||
"""Card collection item update request."""
|
||||
quantity: Optional[int] = None
|
||||
condition: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
is_foil: Optional[bool] = None
|
||||
is_alt_art: Optional[bool] = None
|
||||
acquired_date: Optional[datetime] = None
|
||||
acquisition_method: Optional[str] = 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
|
||||
|
||||
class Config:
|
||||
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: int
|
||||
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
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class WishlistListResponse(BaseModel):
|
||||
"""List of wishlist items."""
|
||||
items: List[WishlistResponse]
|
||||
total: int
|
||||
# ===== Wishlist Schemas (moved to user_card_collection.py) =====
|
||||
# These are kept here for backward compatibility but should be imported from user_card_collection.py
|
||||
|
||||
|
||||
# ===== Group Schemas =====
|
||||
@@ -377,8 +290,7 @@ class GroupResponse(BaseModel):
|
||||
member_count: int = 0
|
||||
is_member: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupListResponse(BaseModel):
|
||||
@@ -401,8 +313,7 @@ class GroupChatMessageResponse(BaseModel):
|
||||
message: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupChatMessageListResponse(BaseModel):
|
||||
@@ -447,8 +358,7 @@ class NetworkResponse(BaseModel):
|
||||
member_count: int = 0
|
||||
is_member: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class NetworkListResponse(BaseModel):
|
||||
@@ -480,8 +390,7 @@ class UserPreferenceResponse(BaseModel):
|
||||
language: str
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ===== Activity Log Schemas =====
|
||||
@@ -495,8 +404,7 @@ class ActivityLogEntry(BaseModel):
|
||||
ip_address: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class ActivityLogListResponse(BaseModel):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Pydantic schemas for user deck building features."""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -48,6 +48,8 @@ class UserDeckUpdate(BaseModel):
|
||||
|
||||
class UserDeckResponse(BaseModel):
|
||||
"""Deck response with card count."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
user_id: int
|
||||
name: str
|
||||
@@ -62,9 +64,6 @@ class UserDeckResponse(BaseModel):
|
||||
card_count: int = 0
|
||||
is_owner: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserDeckListResponse(BaseModel):
|
||||
"""List of user decks."""
|
||||
@@ -94,16 +93,14 @@ class DeckCardUpdate(BaseModel):
|
||||
|
||||
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]
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class DeckCardWithDetailsResponse(DeckCardResponse):
|
||||
@@ -139,6 +136,8 @@ class PrecedentUpdate(BaseModel):
|
||||
|
||||
class PrecedentResponse(BaseModel):
|
||||
"""Deck precedent response."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
@@ -149,9 +148,6 @@ class PrecedentResponse(BaseModel):
|
||||
updated_at: datetime
|
||||
card_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PrecedentListResponse(BaseModel):
|
||||
"""List of deck precedents."""
|
||||
@@ -172,6 +168,8 @@ class SuggestionCreate(BaseModel):
|
||||
|
||||
class SuggestionResponse(BaseModel):
|
||||
"""Card suggestion response."""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
deck_id: int
|
||||
card_id: int
|
||||
@@ -182,9 +180,6 @@ class SuggestionResponse(BaseModel):
|
||||
created_at: datetime
|
||||
card_name: str = ""
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class SuggestionListResponse(BaseModel):
|
||||
"""List of card suggestions."""
|
||||
@@ -220,23 +215,3 @@ class CardSearchRequest(BaseModel):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user