Phase 3: Schema Layer - Pydantic v2 migration, deduplication, and missing schemas
- Migrated all schemas to Pydantic v2 syntax (model_config, ConfigDict) - Fixed mutable default in ProtoMessageBase using Field(default_factory=datetime.now) - Consolidated CardCollection and Wishlist schemas in user_card_collection.py - Created game_schemas.py with GameCreate, GameResponse, GameJoinRequest, etc. - Created mtg_card_schemas.py with MtgCardResponse, MtgCardSearchRequest, etc. - Added CardImportBatchCreate, CardImportBatchResponse, UserCardImportCreate/Response schemas - Fixed duplicate UserCardImportRecord class between card_import_batch.py and user_card_import_record.py - Updated __init__.py with comprehensive schema exports - Created verify_schemas.py for schema-model matching verification
This commit is contained in:
@@ -11,6 +11,11 @@ from app.models.user_data import (
|
||||
from app.models.user_deck import UserDeck, UserDeckCard, DeckPrecedent, DeckPrecedentCard, CardSuggestion
|
||||
from app.models.card_import_batch import CardImportBatch
|
||||
from app.models.user_card_import_record import UserCardImportRecord
|
||||
from app.models.user_card_import import UserCardImport
|
||||
|
||||
# Import relationships LAST to avoid circular imports
|
||||
# This defines cross-model references (e.g., DecklistFile.card_links)
|
||||
from app.models import relationships
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
@@ -47,4 +52,5 @@ __all__ = [
|
||||
"CardSuggestion",
|
||||
"CardImportBatch",
|
||||
"UserCardImportRecord",
|
||||
"UserCardImport",
|
||||
]
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
"""SQLAlchemy ORM model for card import batches."""
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text, JSON, Boolean
|
||||
"""
|
||||
SQLAlchemy ORM models for card import tracking.
|
||||
|
||||
This module provides models for tracking card import batches
|
||||
and individual card import records.
|
||||
"""
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, BigInteger, Boolean, DateTime, Text,
|
||||
ForeignKey, UniqueConstraint
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
@@ -7,30 +15,60 @@ from app.core.database import Base
|
||||
|
||||
class CardImportBatch(Base):
|
||||
"""
|
||||
Card import batch record.
|
||||
Card import batch tracking.
|
||||
|
||||
Tracks a single file import with its status and match results.
|
||||
Tracks a batch of card imports from a file, including
|
||||
matching results and error information.
|
||||
"""
|
||||
__tablename__ = "card_import_batches"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False)
|
||||
filename = Column(String(255), nullable=False)
|
||||
file_type = Column(String(10), nullable=False) # xlsx, csv, json, ods
|
||||
file_size = Column(Integer, nullable=False) # File size in bytes
|
||||
status = Column(String(20), nullable=False, default="pending", index=True) # pending, processing, completed, failed
|
||||
file_type = Column(String(50), nullable=True) # 'mtgjson', 'csv', etc.
|
||||
file_size = Column(BigInteger, nullable=True) # Size in bytes
|
||||
status = Column(String(20), default="PENDING") # PENDING, PROCESSING, COMPLETED, FAILED
|
||||
total_cards = Column(Integer, default=0)
|
||||
matched_cards = Column(Integer, default=0)
|
||||
unmatched_cards = Column(Integer, default=0)
|
||||
match_results = Column(JSON, nullable=True) # Store match results for later retrieval
|
||||
match_results = Column(Text, nullable=True) # JSON of match details
|
||||
error_message = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="import_batches")
|
||||
|
||||
user = relationship("User", backref="card_import_batches")
|
||||
records = relationship(
|
||||
"UserCardImportRecord",
|
||||
back_populates="batch",
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<CardImportBatch id={self.id} user={self.user_id} status={self.status}>"
|
||||
return f"<CardImportBatch {self.filename} (status={self.status})>"
|
||||
|
||||
|
||||
class UserCardImportRecord(Base):
|
||||
"""
|
||||
Individual card import record within a batch.
|
||||
|
||||
Tracks whether each card in an import batch was confirmed
|
||||
and when the confirmation happened.
|
||||
"""
|
||||
__tablename__ = "user_card_imports_confirmed"
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False)
|
||||
batch_id = Column(BigInteger, ForeignKey("card_import_batches.id", ondelete="CASCADE"), nullable=False)
|
||||
is_confirmed = Column(Boolean, default=False)
|
||||
confirmed_at = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
batch = relationship("CardImportBatch", back_populates="records")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'batch_id', name='uq_user_card_import_batch'),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserCardImportRecord user={self.user_id} batch={self.batch_id} confirmed={self.is_confirmed}>"
|
||||
|
||||
@@ -11,7 +11,6 @@ from sqlalchemy import (
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
from app.models.models import DecklistFile
|
||||
|
||||
|
||||
class MtgCardMirror(Base):
|
||||
@@ -25,7 +24,12 @@ class MtgCardMirror(Base):
|
||||
__tablename__ = "mtg_cards_mirror"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
source_id = Column(Integer, nullable=True, index=True) # References mtg_cards.id
|
||||
source_id = Column(
|
||||
Integer,
|
||||
nullable=True,
|
||||
index=True,
|
||||
comment="Logical reference to mtg_cards.id in mtg_data database (cross-DB reference, no FK constraint)"
|
||||
)
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
mana_cost = Column(String(255), nullable=True)
|
||||
type_line = Column(String(255), nullable=True)
|
||||
@@ -91,14 +95,3 @@ class DeckCardLink(Base):
|
||||
f"<DeckCardLink deck={self.deck_id} card={self.card_id} "
|
||||
f"qty={self.quantity} zone={self.zone}>"
|
||||
)
|
||||
|
||||
|
||||
# Add back-references to existing models
|
||||
from app.models.models import DecklistFile
|
||||
|
||||
DecklistFile.card_links = relationship(
|
||||
"DeckCardLink",
|
||||
back_populates="deck",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="DeckCardLink.id"
|
||||
)
|
||||
|
||||
@@ -80,8 +80,21 @@ class MtgonlineCard(Base):
|
||||
synced_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
deck_cards = relationship(
|
||||
"UserDeckCard",
|
||||
back_populates="card",
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index('idx_mtgonline_cards_name', 'name'),
|
||||
Index('idx_mtgonline_cards_set', 'set_code'),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<MtonlineCard {self.name} (ID: {self.id}, source: {self.source_id})>"
|
||||
return f"<MtgonlineCard {self.name} (ID: {self.id}, source: {self.source_id})>"
|
||||
|
||||
|
||||
class DecklistFolder(Base):
|
||||
@@ -99,6 +112,11 @@ class DecklistFolder(Base):
|
||||
children = relationship("DecklistFolder", back_populates="parent", cascade="all, delete-orphan")
|
||||
parent = relationship("DecklistFolder", back_populates="children", remote_side=[id])
|
||||
files = relationship("DecklistFile", back_populates="folder", cascade="all, delete-orphan")
|
||||
user_decks = relationship(
|
||||
"UserDeck",
|
||||
back_populates="folder",
|
||||
lazy="select"
|
||||
)
|
||||
|
||||
|
||||
class DecklistFile(Base):
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Cross-model relationships.
|
||||
|
||||
This module defines relationships that reference models from other
|
||||
model files (e.g., DecklistFile.card_links → DeckCardLink). These
|
||||
must be defined AFTER all model classes have been imported to avoid
|
||||
circular import issues.
|
||||
|
||||
Import this module LAST in app/models/__init__.py.
|
||||
"""
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.models import DecklistFile
|
||||
from app.models.mirror_models import DeckCardLink
|
||||
|
||||
|
||||
# Add the back-reference from DecklistFile to DeckCardLink.
|
||||
# This was previously defined dynamically at the bottom of mirror_models.py,
|
||||
# but that caused circular imports. Now it lives here and is imported last.
|
||||
DecklistFile.card_links = relationship(
|
||||
"DeckCardLink",
|
||||
back_populates="deck",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="DeckCardLink.id"
|
||||
)
|
||||
@@ -1,11 +1,13 @@
|
||||
"""
|
||||
SQLAlchemy ORM model for user card imports.
|
||||
|
||||
Stores a user's imported card collection as a JSON string containing
|
||||
a list of card names. This is the source data for building decks
|
||||
from the user's actual card collection.
|
||||
This model corresponds to the user_card_imports table created in migration 004.
|
||||
It stores a user's imported card collection as a JSON array of card names.
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, BigInteger, DateTime, Text,
|
||||
ForeignKey, UniqueConstraint
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
@@ -13,21 +15,26 @@ from app.core.database import Base
|
||||
|
||||
class UserCardImport(Base):
|
||||
"""
|
||||
User's imported card collection.
|
||||
User card import record.
|
||||
|
||||
Stores a JSON string of card names that the user owns.
|
||||
Used as the source for building decks from user's actual cards.
|
||||
Stores a user's imported card collection as a JSON array of card names.
|
||||
This table was created in migration 004 and may be superseded by
|
||||
CardImportBatch and UserCardImportRecord in migration 005.
|
||||
"""
|
||||
__tablename__ = "user_card_imports"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False, unique=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False)
|
||||
card_names_json = Column(Text, nullable=False) # JSON array of card names
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="card_imports")
|
||||
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', name='uq_user_card_imports_user_id'),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserCardImport user={self.user_id}>"
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
"""SQLAlchemy ORM model for confirmed card imports."""
|
||||
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
"""
|
||||
SQLAlchemy ORM models for user card import records.
|
||||
|
||||
This module is deprecated. UserCardImportRecord is now defined in
|
||||
card_import_batch.py along with CardImportBatch.
|
||||
"""
|
||||
# This file is kept for backward compatibility but the actual model
|
||||
# is now in card_import_batch.py
|
||||
from app.models.card_import_batch import UserCardImportRecord as UserCardImportRecord
|
||||
|
||||
class UserCardImportRecord(Base):
|
||||
"""
|
||||
Confirmed user card import record.
|
||||
|
||||
Stores the confirmed state of an imported card collection.
|
||||
"""
|
||||
__tablename__ = "user_card_imports_confirmed"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
batch_id = Column(Integer, ForeignKey("card_import_batches.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
is_confirmed = Column(Boolean, nullable=False, default=True)
|
||||
confirmed_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", backref="confirmed_imports")
|
||||
batch = relationship("CardImportBatch", backref="confirmations")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserCardImportRecord id={self.id} user={self.user_id} confirmed={self.is_confirmed}>"
|
||||
__all__ = ["UserCardImportRecord"]
|
||||
|
||||
@@ -69,7 +69,7 @@ class UserDeckCard(Base):
|
||||
|
||||
# Relationships
|
||||
deck = relationship("UserDeck", back_populates="cards")
|
||||
card = relationship("MtgonlineCard", backref="deck_cards")
|
||||
card = relationship("MtgonlineCard", back_populates="deck_cards")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserDeckCard deck={self.deck_id} card={self.card_id} qty={self.quantity}>"
|
||||
|
||||
@@ -1 +1,133 @@
|
||||
# Schemas package
|
||||
"""Schemas package initialization."""
|
||||
from app.schemas.schemas import (
|
||||
LoginRequest, LoginResponse, RefreshTokenRequest, TokenResponse,
|
||||
UserBase, UserCreate, UserUpdate, UserResponse,
|
||||
DeckCreate, DeckUpdate, DeckResponse,
|
||||
FolderCreate, FolderResponse,
|
||||
GameCreate, GameResponse,
|
||||
RoomResponse,
|
||||
BanCreate, BanResponse,
|
||||
ErrorResponse, ValidationErrorResponse,
|
||||
PaginationParams, PaginatedResponse,
|
||||
CardMirrorResponse, DeckCardLinkResponse, DeckWithCardsResponse,
|
||||
)
|
||||
from app.schemas.user_data_schemas import (
|
||||
DeckVersionStatus, GameReplayStatus, GameOutcomeType,
|
||||
GroupMemberRole, NetworkMemberRole, UserPreferenceTheme, ActivityType,
|
||||
SessionResponse, SessionCleanupResponse,
|
||||
DeckVersionCreate, DeckVersionUpdate, DeckVersionResponse, DeckVersionListResponse,
|
||||
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,
|
||||
NetworkResponse, NetworkListResponse,
|
||||
UserPreferenceUpdate, UserPreferenceResponse,
|
||||
ActivityLogEntry, ActivityLogListResponse,
|
||||
MessageResponse, CountResponse, ErrorDetail,
|
||||
)
|
||||
from app.schemas.user_deck_schemas import (
|
||||
DeckStatus, DeckZone, SuggestionType,
|
||||
UserDeckCreate, UserDeckUpdate, UserDeckResponse, UserDeckListResponse,
|
||||
DeckCardCreate, DeckCardUpdate, DeckCardResponse, DeckCardWithDetailsResponse, DeckCardListResponse,
|
||||
PrecedentCreate, PrecedentUpdate, PrecedentResponse, PrecedentListResponse,
|
||||
SuggestionCreate, SuggestionResponse, SuggestionListResponse,
|
||||
DeckFinalizeRequest, DeckFinalizeResponse, DeckDeleteResponse,
|
||||
CardSearchRequest, CardSearchResponse,
|
||||
)
|
||||
from app.schemas.user_card_collection import (
|
||||
CardCondition, AcquisitionMethod,
|
||||
CollectionStatistics, CollectionSummaryResponse,
|
||||
)
|
||||
from app.schemas.card_import_schemas import (
|
||||
CardImportRequest, CardImportResponse, CardImportStatusResponse,
|
||||
CardMatchResult, CardImportSummary,
|
||||
CardImportBatchCreate, CardImportBatchResponse,
|
||||
UserCardImportCreate, UserCardImportResponse, UserCardImportRecordResponse,
|
||||
)
|
||||
from app.schemas.card_search_schemas import (
|
||||
CardResponse, SetResponse, CardTypeResponse,
|
||||
)
|
||||
from app.schemas.proto_messages import (
|
||||
ProtoMessageBase, SessionCommand, GameCommand, GameEvent, Response,
|
||||
ServerInfoUser, ServerInfoDeckStorageFile, ServerInfoDeckStorageFolder,
|
||||
ServerInfoDeckStorageTreeItem, ServerInfoCard, ServerInfoZone, ServerInfoGame,
|
||||
)
|
||||
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
|
||||
"LoginRequest", "LoginResponse", "RefreshTokenRequest", "TokenResponse",
|
||||
# User
|
||||
"UserBase", "UserCreate", "UserUpdate", "UserResponse",
|
||||
# Deck
|
||||
"DeckCreate", "DeckUpdate", "DeckResponse",
|
||||
"FolderCreate", "FolderResponse",
|
||||
# Game
|
||||
"GameCreate", "GameResponse", "RoomResponse",
|
||||
"GameJoinRequest", "GameLeaveRequest", "GameListResponse", "GamePlayerResponse", "GameStateResponse",
|
||||
# Ban
|
||||
"BanCreate", "BanResponse",
|
||||
# Error
|
||||
"ErrorResponse", "ValidationErrorResponse",
|
||||
# Pagination
|
||||
"PaginationParams", "PaginatedResponse",
|
||||
# Card Mirror
|
||||
"CardMirrorResponse", "DeckCardLinkResponse", "DeckWithCardsResponse",
|
||||
# User Data
|
||||
"DeckVersionStatus", "GameReplayStatus", "GameOutcomeType",
|
||||
"GroupMemberRole", "NetworkMemberRole", "UserPreferenceTheme", "ActivityType",
|
||||
"SessionResponse", "SessionCleanupResponse",
|
||||
"DeckVersionCreate", "DeckVersionUpdate", "DeckVersionResponse", "DeckVersionListResponse",
|
||||
"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",
|
||||
"NetworkResponse", "NetworkListResponse",
|
||||
"UserPreferenceUpdate", "UserPreferenceResponse",
|
||||
"ActivityLogEntry", "ActivityLogListResponse",
|
||||
"MessageResponse", "CountResponse", "ErrorDetail",
|
||||
# User Deck
|
||||
"DeckStatus", "DeckZone", "SuggestionType",
|
||||
"UserDeckCreate", "UserDeckUpdate", "UserDeckResponse", "UserDeckListResponse",
|
||||
"DeckCardCreate", "DeckCardUpdate", "DeckCardResponse", "DeckCardWithDetailsResponse", "DeckCardListResponse",
|
||||
"PrecedentCreate", "PrecedentUpdate", "PrecedentResponse", "PrecedentListResponse",
|
||||
"SuggestionCreate", "SuggestionResponse", "SuggestionListResponse",
|
||||
"DeckFinalizeRequest", "DeckFinalizeResponse", "DeckDeleteResponse",
|
||||
"CardSearchRequest", "CardSearchResponse",
|
||||
# Card Collection
|
||||
"CardCondition", "AcquisitionMethod",
|
||||
"CollectionStatistics", "CollectionSummaryResponse",
|
||||
# Card Import
|
||||
"CardImportRequest", "CardImportResponse", "CardImportStatusResponse",
|
||||
"CardMatchResult", "CardImportSummary",
|
||||
"CardImportBatchCreate", "CardImportBatchResponse",
|
||||
"UserCardImportCreate", "UserCardImportResponse", "UserCardImportRecordResponse",
|
||||
# Card Search
|
||||
"CardResponse", "SetResponse", "CardTypeResponse",
|
||||
# Proto Messages
|
||||
"ProtoMessageBase", "SessionCommand", "GameCommand", "GameEvent", "Response",
|
||||
"ServerInfoUser", "ServerInfoDeckStorageFile", "ServerInfoDeckStorageFolder",
|
||||
"ServerInfoDeckStorageTreeItem", "ServerInfoCard", "ServerInfoZone", "ServerInfoGame",
|
||||
# Protocol Constants
|
||||
"SessionCommandType", "GameCommandType", "GameEventType", "ResponseCode",
|
||||
"ZoneType", "UserLevelFlag",
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ Pydantic schemas for card import feature.
|
||||
Provides request/response models for importing card collections
|
||||
and using them for deckbuilding.
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
@@ -53,6 +53,65 @@ class CardImportSummary(BaseModel):
|
||||
import_id: Optional[int] = None
|
||||
|
||||
|
||||
# ===== Card Import Batch Schemas =====
|
||||
|
||||
class CardImportBatchCreate(BaseModel):
|
||||
"""Card import batch creation request."""
|
||||
user_id: int
|
||||
card_names: List[str] = Field(..., min_length=1, max_length=10000)
|
||||
source: Optional[str] = None # 'manual', 'mtgjson', 'deck_text'
|
||||
|
||||
|
||||
class CardImportBatchResponse(BaseModel):
|
||||
"""Card import batch response."""
|
||||
id: int
|
||||
user_id: int
|
||||
card_names: List[str]
|
||||
source: Optional[str]
|
||||
status: str # 'pending', 'processing', 'completed', 'failed'
|
||||
total_cards: int
|
||||
matched_cards: int
|
||||
unmatched_cards: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class UserCardImportCreate(BaseModel):
|
||||
"""User card import request."""
|
||||
batch_id: int
|
||||
card_id: int
|
||||
quantity: int = Field(1, ge=1)
|
||||
match_confidence: Optional[float] = None
|
||||
|
||||
|
||||
class UserCardImportResponse(BaseModel):
|
||||
"""User card import response."""
|
||||
id: int
|
||||
user_id: int
|
||||
batch_id: int
|
||||
card_id: int
|
||||
quantity: int
|
||||
match_confidence: Optional[float]
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class UserCardImportRecordResponse(BaseModel):
|
||||
"""User card import record response."""
|
||||
id: int
|
||||
user_id: int
|
||||
card_id: int
|
||||
batch_id: int
|
||||
quantity: int
|
||||
source: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# Generic response models
|
||||
class MessageResponse(BaseModel):
|
||||
"""Generic message response."""
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Pydantic schemas for game features."""
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class GameCreate(BaseModel):
|
||||
"""Game creation request."""
|
||||
room_id: int
|
||||
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):
|
||||
"""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
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GameJoinRequest(BaseModel):
|
||||
"""Game join request."""
|
||||
game_id: int
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class GameLeaveRequest(BaseModel):
|
||||
"""Game leave request."""
|
||||
game_id: int
|
||||
|
||||
|
||||
class GameListResponse(BaseModel):
|
||||
"""List of games."""
|
||||
games: List[GameResponse]
|
||||
total: int
|
||||
|
||||
|
||||
class GamePlayerResponse(BaseModel):
|
||||
"""Game player response."""
|
||||
user_id: int
|
||||
username: str
|
||||
deck_id: Optional[int] = None
|
||||
is_ready: bool = False
|
||||
is_host: bool = False
|
||||
|
||||
|
||||
class GameStateResponse(BaseModel):
|
||||
"""Game state response."""
|
||||
game_id: int
|
||||
players: List[GamePlayerResponse]
|
||||
turn: int
|
||||
phase: str
|
||||
zones: Dict[str, Any]
|
||||
updated_at: datetime
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Pydantic schemas for MTG card data."""
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class MtgCardResponse(BaseModel):
|
||||
"""MTG card response with full details."""
|
||||
id: int
|
||||
source_id: Optional[int] = None
|
||||
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
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class MtgCardSearchResponse(BaseModel):
|
||||
"""MTG card search response."""
|
||||
cards: List[MtgCardResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
total_pages: int
|
||||
|
||||
|
||||
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
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MtgCardMirrorResponse(BaseModel):
|
||||
"""Mirrored card data for user decks."""
|
||||
id: int
|
||||
source_id: Optional[int] = None
|
||||
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
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckCardLinkResponse(BaseModel):
|
||||
"""Deck-card link response."""
|
||||
id: int
|
||||
deck_id: int
|
||||
card_id: int
|
||||
quantity: int
|
||||
zone: str
|
||||
card: MtgCardMirrorResponse
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DeckWithCardsResponse(BaseModel):
|
||||
"""Deck response with card links."""
|
||||
id: int
|
||||
name: str
|
||||
content: str
|
||||
format: str
|
||||
status: str
|
||||
folder_id: Optional[int] = None
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
card_links: List[DeckCardLinkResponse] = []
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
Reference in New Issue
Block a user