Files
mtgonline/backend/app/models/models.py
T
akadmin 2c74a107bc Phase 2.1: Card mirror system for deckbuilding features
- Created MtgCardMirror and DeckCardLink models in mirror_models.py
- Created card_mirror_service.py with sync_mirrors functionality
- Added sync_mirrors() method to mtgjson_manager.py
- Added mirror_get_db() dependency to database.py
- Added DecklistFile.status column (DRAUGHT/FINAL)
- Updated DeckCreate schema with status field
- Added DeckWithCardsResponse schema with card_count
- Updated deck router to query card mirrors and return card counts
- Added plain text deck content support
2026-07-22 03:35:57 +00:00

172 lines
6.6 KiB
Python

"""
SQLAlchemy ORM models for the MTG Online database.
Mirrors the original MySQL schema with modern PostgreSQL features.
"""
from sqlalchemy import Column, Integer, String, BigInteger, Boolean, DateTime, Text, ForeignKey, Index
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class User(Base):
"""User account model."""
__tablename__ = "mtgonline_users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(64), unique=True, nullable=False, index=True)
password_hash = Column(String(128), nullable=False) # bcrypt hash
salt = Column(String(128), nullable=False) # password salt
email = Column(String(255), nullable=True, index=True)
country = Column(String(2), nullable=True)
real_name = Column(String(128), nullable=True)
avatar_bmp = Column(Text, nullable=True) # Base64 encoded avatar
privlevel = Column(String(50), nullable=True, default="User")
# Account status
is_active = Column(Boolean, default=True)
is_banned = Column(Boolean, default=False)
ban_reason = Column(Text, nullable=True)
ban_ends = Column(DateTime, nullable=True)
# VIP/Donator status
vip_status = Column(Integer, default=0) # 0=normal, 1=vip, 2=donator
vip_expiry = Column(DateTime, nullable=True)
# Timestamps
creation_date = Column(DateTime, server_default=func.now())
last_login = Column(DateTime, nullable=True)
# Relationships
decklist_files = relationship("DecklistFile", back_populates="owner", cascade="all, delete-orphan")
decklist_folders = relationship("DecklistFolder", back_populates="owner", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<User {self.username} (ID: {self.id})>"
class DecklistFolder(Base):
"""User deck folder."""
__tablename__ = "mtgonline_decklist_folders"
id = Column(Integer, primary_key=True, index=True)
owner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False)
name = Column(String(255), nullable=False)
parent_id = Column(Integer, ForeignKey("mtgonline_decklist_folders.id"), nullable=True)
creation_date = Column(DateTime, server_default=func.now())
# Relationships
owner = relationship("User", back_populates="decklist_folders")
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")
class DecklistFile(Base):
"""Deck file stored in a folder."""
__tablename__ = "mtgonline_decklist_files"
id = Column(Integer, primary_key=True, index=True)
folder_id = Column(Integer, ForeignKey("mtgonline_decklist_folders.id"), nullable=True)
owner_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False)
name = Column(String(255), nullable=False)
content = Column(Text, nullable=False) # Native XML or plain text deck format
format = Column(String(50), default="native") # 'native' or 'plain'
status = Column(String(20), default="DRAUGHT") # 'DRAUGHT' or 'FINAL'
creation_date = Column(DateTime, server_default=func.now())
# Relationships
folder = relationship("DecklistFolder", back_populates="files")
owner = relationship("User", back_populates="decklist_files")
def __repr__(self) -> str:
return f"<DecklistFile {self.name} (ID: {self.id})>"
class Room(Base):
"""Chat room."""
__tablename__ = "mtgonline_rooms"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), unique=True, nullable=False)
description = Column(Text, nullable=True)
is_password_protected = Column(Boolean, default=False)
password_hash = Column(String(128), nullable=True)
creation_date = Column(DateTime, server_default=func.now())
# Game types available in this room
game_types = relationship("RoomGameType", back_populates="room", cascade="all, delete-orphan")
class RoomGameType(Base):
"""Game type definition for a room."""
__tablename__ = "mtgonline_rooms_gametypes"
id = Column(Integer, primary_key=True, index=True)
room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=False)
name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
# Relationships
room = relationship("Room", back_populates="game_types")
class Ban(Base):
"""User ban record."""
__tablename__ = "mtgonline_bans"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False)
server_id = Column(Integer, nullable=True)
reason = Column(Text, nullable=False)
moderators = Column(String(255), nullable=True) # Admin usernames
ip_address = Column(String(45), nullable=True) # IPv4 or IPv6
expiration_time = Column(DateTime, nullable=True)
active = Column(Boolean, default=True)
creation_date = Column(DateTime, server_default=func.now())
# Relationships
user = relationship("User")
def __repr__(self) -> str:
return f"<Ban for User {self.user_id} (ID: {self.id})>"
class GameLog(Base):
"""Game log entry."""
__tablename__ = "mtgonline_log"
id = Column(Integer, primary_key=True, index=True)
room_id = Column(Integer, ForeignKey("mtgonline_rooms.id"), nullable=True)
player_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True)
message = Column(Text, nullable=False)
timestamp = Column(DateTime, server_default=func.now())
# Relationships
room = relationship("Room")
player = relationship("User")
class AuditLog(Base):
"""Audit trail for administrative actions."""
__tablename__ = "mtgonline_audit"
id = Column(Integer, primary_key=True, index=True)
admin_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=False)
action_type = Column(String(50), nullable=False) # 'ban', 'unban', 'warn', etc.
target_user_id = Column(Integer, ForeignKey("mtgonline_users.id"), nullable=True)
details = Column(Text, nullable=True)
ip_address = Column(String(45), nullable=True)
timestamp = Column(DateTime, server_default=func.now())
# Relationships
admin = relationship("User", foreign_keys=[admin_id])
target_user = relationship("User", foreign_keys=[target_user_id])
# Indexes for performance
Index("idx_decks_owner", DecklistFile.owner_id)
Index("idx_decks_folder", DecklistFile.folder_id)
Index("idx_bans_active", Ban.active)
Index("idx_log_timestamp", GameLog.timestamp)