nocturn9x a1c3c6e92a Increase nginx upload limit to 100MB and fix path step riddle update bug
The 413 error on large file uploads was caused by nginx's default 1MB
client_max_body_size. Increased to 100MB.

The path step update UNIQUE constraint error occurred because ORM-level
deletes weren't reliably flushed before re-inserts. Switched to a raw
DELETE statement which executes immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:37:18 +02:00
2026-03-30 16:06:35 +02:00

Caccia al Tesoro v2

A real-time web application for playing "Find the Treasure" (Caccia al Tesoro) scavenger hunts. Admins create games with riddles, share a link + join code with players, and teams race to solve shuffled riddles.

Features

  • Admin panel: create and manage riddles (text or multiple-choice, with optional images), create games, start/stop games, view live leaderboard and per-team/per-riddle analytics
  • Player flow: join via link + code, form a team, wait for game start, solve riddles sequentially
  • Shuffled riddle order: each team gets the same riddles in a different random order
  • Real-time updates: WebSocket-powered waiting room, game start signal, and live leaderboard
  • Anti-spam: server-enforced exponential backoff on wrong answers (5s, 10s, 20s, ... up to 120s cap)
  • Analytics: tracks time spent per riddle, attempt counts, completion rates

Project structure

caccia_tesoro_v2/
├── backend/                      Python (FastAPI) backend
│   ├── pyproject.toml            Poetry project + dependencies
│   ├── app/
│   │   ├── main.py               FastAPI app entry point
│   │   ├── config.py             Settings (DB URL, JWT secret, etc.)
│   │   ├── database.py           SQLAlchemy async engine + session
│   │   ├── dependencies.py       Dependency injection (get_db, auth)
│   │   ├── models/               SQLAlchemy ORM models
│   │   │   ├── admin.py          Admin users
│   │   │   ├── riddle.py         Riddle definitions
│   │   │   ├── game.py           Games
│   │   │   ├── game_riddle.py    Game-riddle associations (with ordering)
│   │   │   ├── team.py           Teams (with shuffled riddle order)
│   │   │   ├── team_member.py    Team members
│   │   │   ├── submission.py     Answer submissions
│   │   │   └── riddle_analytics.py  Per-team per-riddle timing/attempt data
│   │   ├── schemas/              Pydantic request/response models
│   │   ├── routers/
│   │   │   ├── auth.py           POST /api/auth/register, /api/auth/login
│   │   │   ├── riddles.py        CRUD /api/riddles (admin)
│   │   │   ├── games.py          CRUD /api/games + start/stop/leaderboard/analytics (admin)
│   │   │   ├── play.py           /api/play/* player endpoints (join, riddle, submit)
│   │   │   └── ws.py             WebSocket /api/ws/{slug}
│   │   └── services/
│   │       ├── auth.py           Password hashing, JWT creation/verification
│   │       ├── game.py           Game logic (shuffle, answer check, leaderboard)
│   │       └── ws_manager.py     WebSocket connection manager
│   └── uploads/                  Riddle images (gitignored)
├── frontend/                     React (TypeScript) SPA
│   ├── package.json
│   ├── vite.config.ts            Dev proxy to backend
│   └── src/
│       ├── App.tsx               Router setup
│       ├── api/client.ts         API fetch helpers
│       ├── context/
│       │   ├── AuthContext.tsx    Admin JWT state
│       │   └── GameContext.tsx    Player session state
│       ├── hooks/
│       │   └── useWebSocket.ts   WebSocket hook with auto-reconnect
│       ├── pages/
│       │   ├── admin/            Login, Dashboard, Riddles CRUD, Game form, Live game view
│       │   └── player/           Join, Waiting room, Play, Results
│       ├── components/           Shared UI (Leaderboard, ProtectedRoute)
│       └── types/index.ts        TypeScript interfaces
└── .gitignore

Prerequisites

  • Python 3.11+
  • Poetry 1.7+
  • Node.js 18+
  • npm

Setup

Backend

cd backend
poetry install

The database (SQLite) is created automatically on first startup. No migrations needed.

Frontend

cd frontend
npm install

Running (development)

Open two terminals:

Terminal 1 -- Backend (runs on port 8000):

cd backend
poetry run uvicorn app.main:app --reload --port 8000

Terminal 2 -- Frontend (runs on port 5173, proxies API to backend):

cd frontend
npm run dev

Open http://localhost:5173 in your browser.

Usage

1. Create an admin account

Go to http://localhost:5173/admin/login, click "Need an account? Register", and create an account.

2. Create riddles

Navigate to Riddles in the admin panel. Create riddles of two types:

  • Text: players type a free-text answer (case-insensitive matching)
  • Multiple choice: players pick from options A/B/C/D

Riddles can optionally include an image.

3. Create a game

Go to New Game, give it a name, set a join code (e.g. TREASURE42), and select which riddles to include (click to toggle, order follows selection order).

On the game's live page you'll see the player link, e.g. http://localhost:5173/play/abc12345. Share this link and the join code with players.

5. Players join

Players open the link, enter the join code, pick a team name, and add team member names. They'll land in a waiting room.

6. Start the game

Click Start Game on the admin live page. All waiting players are notified via WebSocket and taken to the play screen.

7. Play

Each team sees the same riddles but in a different shuffled order. They solve riddles one at a time -- get it right to unlock the next one. Wrong answers trigger an exponential cooldown (5s, 10s, 20s, 40s, 80s, max 120s).

The live leaderboard updates in real time for both players and admins.

8. Results

The first team to solve all riddles wins. The admin can also stop the game manually. The results page shows the final leaderboard.

9. Analytics

On the admin game page, click the Analytics tab to see:

  • Per-team: riddles solved, total attempts, total time, average time per riddle
  • Per-riddle: average time, average attempts, completion rate

Configuration

Environment variables (or .env file in backend/):

Variable Default Description
DATABASE_URL sqlite+aiosqlite:///./caccia_tesoro.db Database connection string
JWT_SECRET change-me-in-production Secret key for JWT tokens
JWT_ALGORITHM HS256 JWT signing algorithm
JWT_EXPIRY_HOURS 24 JWT token lifetime in hours
UPLOAD_DIR uploads Directory for riddle images

Important: change JWT_SECRET to a random value in production.

API overview

Endpoint Auth Description
POST /api/auth/register -- Create admin account
POST /api/auth/login -- Get admin JWT
GET/POST /api/riddles Admin List / create riddles
GET/PUT/DELETE /api/riddles/{id} Admin Get / update / delete riddle
GET/POST /api/games Admin List / create games
POST /api/games/{id}/start Admin Start a game
POST /api/games/{id}/stop Admin Stop a game
GET /api/games/{id}/leaderboard Admin Get leaderboard
GET /api/games/{id}/analytics Admin Get analytics data
GET /api/play/{slug} -- Game public info
POST /api/play/{slug}/join -- Join game (returns session token)
GET /api/play/team/current-riddle Team Get current riddle
POST /api/play/team/submit Team Submit answer
GET /api/play/team/status Team Team progress
GET /api/play/team/leaderboard Team Leaderboard from player side
WS /api/ws/{slug}?token=... Admin/Team Real-time events

Full API docs available at http://localhost:8000/docs (Swagger UI) when the backend is running.

Tech stack

  • Backend: Python, FastAPI, SQLAlchemy (async), SQLite, python-jose (JWT), bcrypt, Poetry
  • Frontend: React 19, TypeScript, Vite, React Router v7
  • Real-time: FastAPI WebSockets
Description
A vibecoded web app to play "treasure hunt"
Readme Apache-2.0 1 MiB
Languages
TypeScript 57.1%
Python 41.7%
CSS 0.8%
JavaScript 0.2%
Dockerfile 0.1%