dev #4

Merged
nessi merged 25 commits from dev into main 2026-02-06 13:36:47 +00:00
Showing only changes of commit 8e5a2426e7 - Show all commits

View File

@@ -32,76 +32,217 @@ app.include_router(auth_router)
app.include_router(admin_router)
app.include_router(games_router)
def _rand_join_code(n: int = 6) -> str:
# digits only (kahoot style)
return "".join(random.choice(string.digits) for _ in range(n))
def _has_column(db: Session, table: str, col: str) -> bool:
"""
SQLite + Postgres friendly check.
We use a pragma first (SQLite), fallback to information_schema.
"""
try:
rows = db.execute(text(f"PRAGMA table_info({table})")).all()
return any(r[1] == col for r in rows) # pragma: column name is at index 1
except Exception:
db.rollback()
try:
rows = db.execute(
text(
"""
SELECT column_name
FROM information_schema.columns
WHERE table_name = :t AND column_name = :c
"""
),
{"t": table, "c": col},
).all()
return len(rows) > 0
except Exception:
db.rollback()
return False
def _auto_migrate(db: Session):
"""
Very small, pragmatic auto-migration (no alembic).
- creates missing tables via create_all
- adds missing columns via ALTER TABLE (best effort)
"""
# Users.theme_key
- supports old schema (join_code/chip_code) and new schema (code/chip)
"""
# --- users.theme_key ---
if not _has_column(db, "users", "theme_key"):
try:
db.execute(text("ALTER TABLE users ADD COLUMN theme_key VARCHAR DEFAULT 'default'"))
db.commit()
except Exception:
db.rollback()
# --- games: code / join_code + winner_user_id + host_user_id (optional) ---
# We support both column names:
# old: join_code
# new: code
has_join_code = _has_column(db, "games", "join_code")
has_code = _has_column(db, "games", "code")
# If neither exists, create "code" (new preferred)
if not has_join_code and not has_code:
try:
db.execute(text("ALTER TABLE games ADD COLUMN code VARCHAR"))
db.commit()
has_code = True
except Exception:
db.rollback()
# If only join_code exists but your code now expects "code",
# add "code" too and later mirror values.
if has_join_code and not has_code:
try:
db.execute(text("ALTER TABLE games ADD COLUMN code VARCHAR"))
db.commit()
has_code = True
except Exception:
db.rollback()
# winner_user_id
if not _has_column(db, "games", "winner_user_id"):
try:
db.execute(text("ALTER TABLE games ADD COLUMN winner_user_id VARCHAR"))
db.commit()
except Exception:
db.rollback()
# host_user_id (nice to have for "only host can set winner")
if not _has_column(db, "games", "host_user_id"):
try:
db.execute(text("ALTER TABLE games ADD COLUMN host_user_id VARCHAR"))
db.commit()
except Exception:
db.rollback()
# --- sheet_state chip / chip_code ---
has_chip_code = _has_column(db, "sheet_state", "chip_code")
has_chip = _has_column(db, "sheet_state", "chip")
if not has_chip_code and not has_chip:
# prefer "chip"
try:
db.execute(text("ALTER TABLE sheet_state ADD COLUMN chip VARCHAR"))
db.commit()
has_chip = True
except Exception:
db.rollback()
# if old chip_code exists but new expects chip -> add chip and mirror later
if has_chip_code and not has_chip:
try:
db.execute(text("ALTER TABLE sheet_state ADD COLUMN chip VARCHAR"))
db.commit()
has_chip = True
except Exception:
db.rollback()
# --- indexes for game code ---
# We create unique index for the column(s) that exist.
try:
db.execute(text("ALTER TABLE users ADD COLUMN theme_key VARCHAR DEFAULT 'default'"))
if has_join_code:
db.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_games_join_code ON games (join_code)"))
if has_code:
db.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_games_code ON games (code)"))
db.commit()
except Exception:
db.rollback()
# Games.join_code + winner_user_id
try:
db.execute(text("ALTER TABLE games ADD COLUMN join_code VARCHAR"))
db.commit()
except Exception:
db.rollback()
# --- backfill code values ---
# 1) if join_code exists and code exists, ensure code mirrors join_code where missing
if has_join_code and has_code:
try:
db.execute(text("UPDATE games SET code = join_code WHERE (code IS NULL OR code = '') AND join_code IS NOT NULL AND join_code <> ''"))
db.commit()
except Exception:
db.rollback()
try:
db.execute(text("ALTER TABLE games ADD COLUMN winner_user_id VARCHAR"))
db.commit()
except Exception:
db.rollback()
# 2) generate missing codes in whichever column we have
# Prefer writing into "code" (new), but also keep join_code in sync if present.
code_col = "code" if has_code else "join_code" if has_join_code else None
if code_col:
try:
missing = db.execute(
text(f"SELECT id FROM games WHERE {code_col} IS NULL OR {code_col} = ''")
).all()
except Exception:
db.rollback()
missing = []
# SheetState.chip_code
try:
db.execute(text("ALTER TABLE sheet_state ADD COLUMN chip_code VARCHAR"))
db.commit()
except Exception:
db.rollback()
if missing:
try:
used_rows = db.execute(text(f"SELECT {code_col} FROM games WHERE {code_col} IS NOT NULL")).all()
used = set([r[0] for r in used_rows if r and r[0]])
except Exception:
db.rollback()
used = set()
# Ensure unique index for join_code (best effort)
try:
db.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_games_join_code ON games (join_code)"))
db.commit()
except Exception:
db.rollback()
# Backfill join_code for existing games
games = db.query(Game).filter((Game.join_code == None) | (Game.join_code == "")).all() # noqa: E711
if games:
used = set([r[0] for r in db.execute(text("SELECT join_code FROM games WHERE join_code IS NOT NULL")).all() if r[0]])
for g in games:
code = _rand_join_code()
while code in used:
for (gid,) in missing:
code = _rand_join_code()
g.join_code = code
used.add(code)
db.commit()
while code in used:
code = _rand_join_code()
used.add(code)
try:
# write into main col
db.execute(text(f"UPDATE games SET {code_col} = :c WHERE id = :id"), {"c": code, "id": gid})
# keep both in sync if both exist
if has_join_code and code_col == "code":
db.execute(text("UPDATE games SET join_code = :c WHERE id = :id AND (join_code IS NULL OR join_code = '')"), {"c": code, "id": gid})
if has_code and code_col == "join_code":
db.execute(text("UPDATE games SET code = :c WHERE id = :id AND (code IS NULL OR code = '')"), {"c": code, "id": gid})
db.commit()
except Exception:
db.rollback()
# --- backfill host_user_id: default to owner_user_id ---
try:
if _has_column(db, "games", "host_user_id"):
db.execute(text("UPDATE games SET host_user_id = owner_user_id WHERE host_user_id IS NULL OR host_user_id = ''"))
db.commit()
except Exception:
db.rollback()
# --- backfill membership: ensure owner is member ---
# uses ORM; only relies on existing table GameMember (create_all already ran)
try:
all_games = db.query(Game).all()
for g in all_games:
exists = (
db.query(GameMember)
.filter(GameMember.game_id == g.id, GameMember.user_id == g.owner_user_id)
.first()
)
if not exists:
db.add(GameMember(game_id=g.id, user_id=g.owner_user_id))
db.commit()
except Exception:
db.rollback()
# --- mirror chip_code -> chip if both exist and chip empty ---
if has_chip_code and has_chip:
try:
db.execute(text("UPDATE sheet_state SET chip = chip_code WHERE (chip IS NULL OR chip = '') AND chip_code IS NOT NULL AND chip_code <> ''"))
db.commit()
except Exception:
db.rollback()
# Backfill membership: ensure owner is member
all_games = db.query(Game).all()
for g in all_games:
exists = db.query(GameMember).filter(GameMember.game_id == g.id, GameMember.user_id == g.owner_user_id).first()
if not exists:
db.add(GameMember(game_id=g.id, user_id=g.owner_user_id))
db.commit()
def seed_entries(db: Session):
if db.query(Entry).count() > 0:
return
suspects = ["Draco Malfoy","Crabbe & Goyle","Lucius Malfoy","Dolores Umbridge","Peter Pettigrew","Bellatrix Lestrange"]
items = ["Schlaftrunk","Verschwindekabinett","Portschlüssel","Impedimenta","Petrificus Totalus","Alraune"]
locations = ["Große Halle","Krankenflügel","Raum der Wünsche","Klassenzimmer für Zaubertränke","Pokalszimmer","Klassenzimmer für Wahrsagen","Eulerei","Bibliothek","Verteidigung gegen die dunklen Künste"]
suspects = ["Draco Malfoy", "Crabbe & Goyle", "Lucius Malfoy", "Dolores Umbridge", "Peter Pettigrew", "Bellatrix Lestrange"]
items = ["Schlaftrunk", "Verschwindekabinett", "Portschlüssel", "Impedimenta", "Petrificus Totalus", "Alraune"]
locations = ["Große Halle", "Krankenflügel", "Raum der Wünsche", "Klassenzimmer für Zaubertränke", "Pokalszimmer", "Klassenzimmer für Wahrsagen", "Eulerei", "Bibliothek", "Verteidigung gegen die dunklen Künste"]
for s in suspects:
db.add(Entry(category=Category.suspect.value, label=s))
@@ -111,6 +252,7 @@ def seed_entries(db: Session):
db.add(Entry(category=Category.location.value, label=l))
db.commit()
def ensure_admin(db: Session):
admin_email = os.environ.get("ADMIN_EMAIL", "admin@local").lower().strip()
admin_pw = os.environ.get("ADMIN_PASSWORD", "ChangeMeNow123!")
@@ -126,6 +268,7 @@ def ensure_admin(db: Session):
)
db.commit()
@app.on_event("startup")
def on_startup():
# create new tables