""" SQLAlchemy ORM models for MTG card data and mirrors. Consolidated from mtg_models.py and mirror_models.py into a single module. This file contains all MTG card data models: - MtgSet, MtgCard: Canonical card/set data from mtgjson.com (mtg_data DB) - MtgCardMirror: Mirrored card data copied to mtgonline DB for fast deckbuilding queries - DeckCardLink: Junction table linking user decks to mirrored cards Cross-model references to application models (e.g., DeckCardLink.deck → DecklistFile) use string-based relationship() calls to avoid circular imports. """ from sqlalchemy import ( Column, Integer, String, Text, DateTime, ForeignKey, Index, UniqueConstraint, ) from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.core.database import Base # --------------------------------------------------------------------------- # MTG Set and Card (canonical data from mtgjson.com) # --------------------------------------------------------------------------- class MtgSet(Base): """MTG Set model.""" __tablename__ = "mtg_sets" id = Column(Integer, primary_key=True, index=True) code = Column(String(10), unique=True, nullable=False, index=True) name = Column(String(255), nullable=True) type = Column(String(100), nullable=True) release_date = Column(DateTime, nullable=True) base_set_size = Column(Integer, nullable=True) total_size = Column(Integer, nullable=True) is_foil_only = Column(Integer, nullable=True) is_non_foil_only = Column(Integer, nullable=True) digital = Column(Integer, nullable=True) icon_svg_url = Column(Text, nullable=True) parent_code = Column(String(10), nullable=True) mtgo_code = Column(String(10), nullable=True) image = Column(Text, nullable=True) # Card image URL from MTGJSON updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) # Relationships cards = relationship("MtgCard", back_populates="set") def __repr__(self) -> str: return f"" class MtgCard(Base): """MTG Card model.""" __tablename__ = "mtg_cards" id = Column(Integer, primary_key=True, index=True) set_id = Column(Integer, ForeignKey("mtg_sets.id"), nullable=True, index=True) name = Column(String(255), nullable=True, index=True) mana_cost = Column(String(255), nullable=True, index=True) type_line = Column(String(255), nullable=True, index=True) oracle_text = Column(Text, nullable=True) power = Column(String(50), nullable=True) toughness = Column(String(50), nullable=True) rarity = Column(String(50), nullable=True, index=True) layout = Column(String(50), nullable=True) artist = Column(String(255), nullable=True) flavor_text = Column(Text, nullable=True) numbers = Column(String(100), nullable=True) identifiers = Column(Text, nullable=True) # JSON string images = Column(Text, nullable=True) # JSON string image = Column(Text, nullable=True) # Card image URL from MTGJSON updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) # Relationships set = relationship("MtgSet", back_populates="cards") def __repr__(self) -> str: return f"" # --------------------------------------------------------------------------- # Card Mirror and Deck Links (mtgonline DB) # --------------------------------------------------------------------------- class MtgCardMirror(Base): """ Mirrored card data for user decks. Contains all relevant card fields copied from mtg_cards (mtg_data) to avoid cross-DB joins during deckbuilding. Back-references source_id to the canonical mtg_cards.id for sync tracking. """ __tablename__ = "mtg_cards_mirror" id = Column(Integer, primary_key=True, index=True) 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) oracle_text = Column(Text, nullable=True) power = Column(String(50), nullable=True) toughness = Column(String(50), nullable=True) rarity = Column(String(50), nullable=True) layout = Column(String(50), nullable=True) artist = Column(String(255), nullable=True) flavor_text = Column(Text, nullable=True) numbers = Column(String(100), nullable=True) identifiers = Column(Text, nullable=True) # JSON string of all identifiers images = Column(Text, nullable=True) # JSON string of image URLs image = Column(Text, nullable=True) # Card image URL from MTGJSON card_parts = Column(Text, nullable=True) # Comma-separated list of face names keywords = Column(Text, nullable=True) # Comma-separated keywords legalities = Column(Text, nullable=True) # JSON of format legality set_code = Column(String(10), nullable=True, index=True) set_name = Column(String(255), nullable=True) # Sync tracking synced_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) created_at = Column(DateTime, server_default=func.now()) # Relationships deck_links = relationship( "DeckCardLink", back_populates="card", cascade="all, delete-orphan", ) def __repr__(self) -> str: return f"" class DeckCardLink(Base): """ Junction table linking user decks to mirrored cards. Represents a specific quantity of a mirrored card in a specific zone (main deck or sideboard) of a user's deck. """ __tablename__ = "deck_card_links" id = Column(Integer, primary_key=True, index=True) deck_id = Column( Integer, ForeignKey("mtgonline_decklist_files.id", ondelete="CASCADE"), nullable=False, ) card_id = Column( Integer, ForeignKey("mtg_cards_mirror.id"), nullable=False, ) quantity = Column(Integer, nullable=False, default=1) zone = Column(String(20), nullable=False, default="main") # 'main' or 'sideboard' # Composite unique: a card can only appear once per zone in a deck __table_args__ = ( UniqueConstraint("deck_id", "card_id", "zone", name="uq_deck_card_link"), Index("idx_deck_card_deck", "deck_id"), Index("idx_deck_card_card", "card_id"), ) # Relationships # String reference to DecklistFile in models.py to avoid circular imports deck = relationship("DecklistFile", back_populates="card_links") card = relationship("MtgCardMirror", back_populates="deck_links") def __repr__(self) -> str: return ( f"" ) # --------------------------------------------------------------------------- # Indexes for performance # --------------------------------------------------------------------------- Index("idx_mtg_cards_name_set", MtgCard.name, MtgCard.set_id) Index("idx_mtg_cards_type", MtgCard.type_line) Index("idx_mtg_cards_rarity", MtgCard.rarity)