Integrate join codes, player management, and themes

This update introduces "join codes" for games to simplify game joining. Enhancements include player role and winner management for better organization. Additionally, theme preferences are now user-configurable and persisted server-side.
This commit is contained in:
2026-02-06 11:08:41 +01:00
parent be0f5e9a9f
commit d0f65b856e
10 changed files with 564 additions and 147 deletions

View File

@@ -1,7 +1,7 @@
import enum
import uuid
from sqlalchemy import String, Boolean, DateTime, ForeignKey, Integer, SmallInteger, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
from .db import Base
@@ -21,14 +21,39 @@ class User(Base):
password_hash: Mapped[str] = mapped_column(String)
role: Mapped[str] = mapped_column(String, default=Role.user.value)
disabled: Mapped[bool] = mapped_column(Boolean, default=False)
# UI preferences (persisted server-side)
theme_key: Mapped[str] = mapped_column(String, default="default")
created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
class Game(Base):
__tablename__ = "games"
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
# Creator/owner (for audit), membership controls access
owner_user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
name: Mapped[str] = mapped_column(String)
seed: Mapped[int] = mapped_column(Integer)
# "Kahoot"-style join code
join_code: Mapped[str] = mapped_column(String, unique=True, index=True)
# Winner (shared for the game)
winner_user_id: Mapped[str | None] = mapped_column(String, ForeignKey("users.id"), nullable=True)
created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
class GameMember(Base):
__tablename__ = "game_members"
__table_args__ = (
UniqueConstraint("game_id", "user_id", name="uq_game_member"),
)
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
game_id: Mapped[str] = mapped_column(String, ForeignKey("games.id"), index=True)
user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
class Entry(Base):
@@ -46,6 +71,10 @@ class SheetState(Base):
game_id: Mapped[str] = mapped_column(String, ForeignKey("games.id"), index=True)
owner_user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
entry_id: Mapped[str] = mapped_column(String, ForeignKey("entries.id"), index=True)
status: Mapped[int] = mapped_column(SmallInteger, default=0) # 0 unknown, 1 crossed, 2 confirmed
status: Mapped[int] = mapped_column(SmallInteger, default=0) # 0 unknown, 1 crossed, 2 confirmed, 3 maybe
note_tag: Mapped[str | None] = mapped_column(String, nullable=True) # null | 'i' | 'm' | 's'
# Frontend "s.XX" chip selection (persisted)
chip_code: Mapped[str | None] = mapped_column(String, nullable=True)