"""ORM modely: Player, Game, Guess. Zamerne minimalne (3 tabulky). `won` sa neuklada -- vyplyva z `points > 0` (trafeny tip = 10 + tip, inak 0; pozri Round.get_points_summary v bridzik.py). """ import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column from db.db import Base class Player(Base): """Trvaly ucet hraca + autentifikacia (TOTP).""" __tablename__ = "players" id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(String(40), unique=True, index=True) totp_secret: Mapped[str] = mapped_column(String(32)) # Posledny pouzity TOTP casovy krok -- ochrana proti replay v ramci okna. totp_last_step: Mapped[int] = mapped_column(Integer, default=0) # Session token pre auto-reconnect (poslany v Socket.IO `auth`). auth_token: Mapped[str | None] = mapped_column( String(64), unique=True, nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now() ) class Game(Base): """Jedna partia. Drzi priamo 4 ID hracov podla sedadla (0-3).""" __tablename__ = "games" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()) ) name: Mapped[str] = mapped_column(String(40), default="") player0_id: Mapped[int] = mapped_column(ForeignKey("players.id")) player1_id: Mapped[int] = mapped_column(ForeignKey("players.id")) player2_id: Mapped[int] = mapped_column(ForeignKey("players.id")) player3_id: Mapped[int] = mapped_column(ForeignKey("players.id")) # Aktualna pozicia hry. Spolu s rozdanim kariet nanovo plne urcuju stav hry # po zaciatok kola -> staci na restore (pozri api/history.rebuild_core). series: Mapped[int] = mapped_column(Integer, default=0) round: Mapped[int] = mapped_column(Integer, default=0) created_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now() ) ended_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) def player_id_for_seat(self, seat: int) -> int: return (self.player0_id, self.player1_id, self.player2_id, self.player3_id)[seat] class Guess(Base): """Tip a vysledok jedneho hraca v jednom kole. Unikat (game_id, series_number, round_number, player_id) robi zapis idempotentnym -- opakovany `record_completed_rounds` nezaklada duplikaty. """ __tablename__ = "guesses" __table_args__ = ( UniqueConstraint( "game_id", "series_number", "round_number", "player_id", name="uq_guess_round_player", ), ) id: Mapped[int] = mapped_column(primary_key=True) game_id: Mapped[str] = mapped_column(ForeignKey("games.id"), index=True) player_id: Mapped[int] = mapped_column(ForeignKey("players.id"), index=True) series_number: Mapped[int] = mapped_column(Integer) round_number: Mapped[int] = mapped_column(Integer) guess: Mapped[int] = mapped_column(Integer) points: Mapped[int] = mapped_column(Integer)