""" SQLAlchemy ORM models for user data features. Includes sessions, decks, replays, cards, groups, and networks. """ from sqlalchemy import ( Column, Integer, String, BigInteger, Boolean, DateTime, Text, ForeignKey, Index, UniqueConstraint, Float, JSON ) from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.core.database import Base class UserSession(Base): """User authentication session.""" __tablename__ = "user_sessions" id = Column(BigInteger, primary_key=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False, index=True) session_token_hash = Column(String(255), unique=True, nullable=False, index=True) ip_address = Column(String(45), nullable=True) user_agent = Column(Text, nullable=True) created_at = Column(DateTime, server_default=func.now()) expires_at = Column(DateTime, nullable=False) is_active = Column(Boolean, default=True) user = relationship("User", backref="sessions") def __repr__(self) -> str: return f"" class DeckVersion(Base): """Deck version history.""" __tablename__ = "deck_versions" id = Column(BigInteger, primary_key=True) deck_id = Column(Integer, ForeignKey("mtgonline_decklist_files.id", ondelete="CASCADE"), nullable=False, index=True) version_number = Column(Integer, nullable=False) content = Column(Text, nullable=False) status = Column(String(20), server_default="DRAFT") comment = Column(Text, nullable=True) created_at = Column(DateTime, server_default=func.now()) deck = relationship("DecklistFile", backref="versions") def __repr__(self) -> str: return f"" class GameReplay(Base): """Game replay recording.""" __tablename__ = "game_replays" id = Column(BigInteger, primary_key=True) game_uuid = Column(String(36), unique=True, nullable=False) room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=True, index=True) game_type = Column(String(50), nullable=True) format = Column(String(50), nullable=True) duration_seconds = Column(Integer, nullable=True) start_time = Column(DateTime, nullable=False) end_time = Column(DateTime, nullable=True) status = Column(String(20), server_default="IN_PROGRESS") replay_data = Column(JSON, nullable=True) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) players = relationship("ReplayPlayer", back_populates="replay", cascade="all, delete-orphan") outcomes = relationship("GameOutcome", back_populates="replay", cascade="all, delete-orphan") def __repr__(self) -> str: return f"" class ReplayPlayer(Base): """Player in a game replay.""" __tablename__ = "replay_players" id = Column(BigInteger, primary_key=True) replay_id = Column(BigInteger, ForeignKey("game_replays.id", ondelete="CASCADE"), nullable=False, index=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) position = Column(Integer, nullable=True) deck_id = Column(Integer, ForeignKey("mtgonline_decklist_files.id"), nullable=True) won = Column(Boolean, nullable=True) lost = Column(Boolean, nullable=True) concession = Column(Boolean, default=False) turn_one = Column(Boolean, default=False) created_at = Column(DateTime, server_default=func.now()) replay = relationship("GameReplay", back_populates="players") user = relationship("User") deck = relationship("DecklistFile") def __repr__(self) -> str: return f"" class GameOutcome(Base): """Game outcome record.""" __tablename__ = "game_outcomes" id = Column(BigInteger, primary_key=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) game_uuid = Column(String(36), ForeignKey("game_replays.game_uuid"), nullable=False, index=True) outcome = Column(String(20), nullable=False, index=True) opponent_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True) format = Column(String(50), nullable=True) rating_before = Column(Integer, nullable=True) rating_after = Column(Integer, nullable=True) rating_change = Column(Integer, nullable=True) created_at = Column(DateTime, server_default=func.now()) replay = relationship("GameReplay", back_populates="outcomes") user = relationship("User", foreign_keys=[user_id]) opponent = relationship("User", foreign_keys=[opponent_id]) def __repr__(self) -> str: return f"" class UserStatistics(Base): """User game statistics summary.""" __tablename__ = "user_statistics" user_id = Column(Integer, ForeignKey("mtgonline_users.id"), primary_key=True) total_games = Column(Integer, default=0) total_wins = Column(Integer, default=0) total_losses = Column(Integer, default=0) total_concessions = Column(Integer, default=0) win_rate = Column(Float, default=0.0) current_streak = Column(Integer, default=0) best_streak = Column(Integer, default=0) average_rating = Column(Float, default=0.0) last_game_date = Column(DateTime, nullable=True) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) user = relationship("User") def __repr__(self) -> str: return f"" class UserCardCollection(Base): """User card collection.""" __tablename__ = "user_card_collection" id = Column(BigInteger, primary_key=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False, index=True) card_id = Column(Integer, nullable=False, index=True) quantity = Column(Integer, default=1) condition = Column(String(20), server_default="NEAR_MINT") language = Column(String(5), server_default="EN") is_foil = Column(Boolean, default=False) is_alt_art = Column(Boolean, default=False) acquired_date = Column(DateTime, server_default=func.now()) acquisition_method = Column(String(50), nullable=True) notes = Column(Text, nullable=True) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) __table_args__ = ( UniqueConstraint('user_id', 'card_id', 'is_foil', 'is_alt_art', name='uq_collection_unique'), Index('idx_collection_user_card', 'user_id', 'card_id'), ) user = relationship("User", backref="card_collection") def __repr__(self) -> str: return f"" class CardWishlist(Base): """User card wishlist.""" __tablename__ = "card_wishlist" id = Column(BigInteger, primary_key=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id", ondelete="CASCADE"), nullable=False) card_id = Column(Integer, nullable=False) max_price = Column(Float, nullable=True) notes = Column(Text, nullable=True) created_at = Column(DateTime, server_default=func.now()) __table_args__ = ( UniqueConstraint('user_id', 'card_id', name='uq_wishlist_user_card'), ) user = relationship("User", backref="wishlist") def __repr__(self) -> str: return f"" class UserGroup(Base): """User group.""" __tablename__ = "user_groups" id = Column(BigInteger, primary_key=True) name = Column(String(100), nullable=False) description = Column(Text, nullable=True) owner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) is_public = Column(Boolean, default=True) max_members = Column(Integer, default=50) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) owner = relationship("User", foreign_keys=[owner_id]) members = relationship("GroupMember", back_populates="group", cascade="all, delete-orphan") messages = relationship("GroupChatMessage", back_populates="group", cascade="all, delete-orphan") def __repr__(self) -> str: return f"" class GroupMember(Base): """Group member.""" __tablename__ = "group_members" id = Column(BigInteger, primary_key=True) group_id = Column(BigInteger, ForeignKey("user_groups.id", ondelete="CASCADE"), nullable=False, index=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) role = Column(String(20), server_default="MEMBER") joined_at = Column(DateTime, server_default=func.now()) group = relationship("UserGroup", back_populates="members") user = relationship("User") __table_args__ = ( UniqueConstraint('group_id', 'user_id', name='uq_group_member'), ) def __repr__(self) -> str: return f"" class GroupChatMessage(Base): """Group chat message.""" __tablename__ = "group_chat_messages" id = Column(BigInteger, primary_key=True) group_id = Column(BigInteger, ForeignKey("user_groups.id", ondelete="CASCADE"), nullable=False, index=True) sender_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) message = Column(Text, nullable=False) created_at = Column(DateTime, server_default=func.now(), index=True) group = relationship("UserGroup", back_populates="messages") sender = relationship("User", foreign_keys=[sender_id]) def __repr__(self) -> str: return f"" class UserNetwork(Base): """User network (extended social connection).""" __tablename__ = "user_networks" id = Column(BigInteger, primary_key=True) name = Column(String(100), nullable=False) description = Column(Text, nullable=True) creator_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False) is_public = Column(Boolean, default=True) created_at = Column(DateTime, server_default=func.now()) creator = relationship("User", foreign_keys=[creator_id]) members = relationship("NetworkMember", back_populates="network", cascade="all, delete-orphan") def __repr__(self) -> str: return f"" class NetworkMember(Base): """Network member.""" __tablename__ = "network_members" id = Column(BigInteger, primary_key=True) network_id = Column(BigInteger, ForeignKey("user_networks.id", ondelete="CASCADE"), nullable=False, index=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) role = Column(String(20), server_default="MEMBER") joined_at = Column(DateTime, server_default=func.now()) network = relationship("UserNetwork", back_populates="members") user = relationship("User") __table_args__ = ( UniqueConstraint('network_id', 'user_id', name='uq_network_member'), ) def __repr__(self) -> str: return f"" class UserPreference(Base): """User preferences and settings.""" __tablename__ = "user_preferences" user_id = Column(Integer, ForeignKey("mtgonline_users.id"), primary_key=True) theme = Column(String(20), server_default="light") notifications_enabled = Column(Boolean, default=True) email_notifications = Column(Boolean, default=True) auto_save_decks = Column(Boolean, default=True) default_format = Column(String(50), server_default="standard") language = Column(String(5), server_default="EN") updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) user = relationship("User") def __repr__(self) -> str: return f"" class UserActivityLog(Base): """User activity log.""" __tablename__ = "user_activity_log" id = Column(BigInteger, primary_key=True) user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False, index=True) activity_type = Column(String(50), nullable=False, index=True) activity_data = Column(JSON, nullable=True) ip_address = Column(String(45), nullable=True) created_at = Column(DateTime, server_default=func.now(), index=True) user = relationship("User", backref="activity_logs") def __repr__(self) -> str: return f""