Add persistence layer: TOTP auth, game history, restore

- db/ package: async SQLAlchemy engine + Player/Game/Guess models
- api/auth.py: passwordless TOTP login (pyotp), session token via socket auth
- api/history.py: record guesses/points, DB-backed standings, restore
  unfinished games on startup, host-only end_game
- api/__init__.py: auth-gated handlers, accounts map, rejoin via account
- frontend: Auth (QR + code) and History pages, resume/end-game in lobby/table
- docker-compose: real PostgreSQL service wired via DATABASE_URL
- tests_history.py for the persistence/auth layer; refresh CLAUDE.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tim
2026-06-23 23:09:50 +02:00
co-authored by Claude Opus 4.8
parent beaf142ee4
commit 30c32b7714
24 changed files with 1446 additions and 87 deletions
+16 -3
View File
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGameStore } from '../store/gameStore';
import { emit } from '../lib/socket';
import { leaveGame } from '../lib/leaveGame';
import { computePlayable } from '../lib/gameRules';
import Hand from '../components/Hand';
@@ -68,6 +69,11 @@ export default function GameTable() {
const activePlayerName = players.find((p) => p.order === active_player)?.name ?? '';
const handleLeave = () => leaveGame(navigate);
const handleEnd = () => {
if (window.confirm('Naozaj ukoncit celu hru pre vsetkych?')) {
emit.endGame(gameStatus.gid);
}
};
return (
<div className="max-w-lg mx-auto p-3 flex flex-col gap-3 pb-6">
@@ -77,9 +83,16 @@ export default function GameTable() {
<span>Seria {series_number} / Kolo {round_number + 1}</span>
<span className="ml-2 text-gray-500">({cards_in_round} kopok)</span>
</div>
<button onClick={handleLeave} className="text-xs text-gray-500 hover:text-gray-300">
Odist
</button>
<div className="flex gap-3">
{myOrder === 0 && (
<button onClick={handleEnd} className="text-xs text-red-400 hover:text-red-300">
Ukoncit hru
</button>
)}
<button onClick={handleLeave} className="text-xs text-gray-500 hover:text-gray-300">
Odist
</button>
</div>
</div>
{/* Turn indicator */}