| db/init | ||
| webserver | ||
| .env | ||
| .env.secret.example | ||
| .gitignore | ||
| client.py | ||
| docker-compose.override.yml | ||
| docker-compose.yml | ||
| Makefile | ||
| README.md | ||
game-end
Universal server for turn-based games. A Flask + PostgreSQL backend that handles user accounts, a game lobby, and per-game state, sitting behind cookie-based JWT auth.
Architecture
- webserver — Flask app (
webserver/app.py), served by the Flask dev server in development and bygunicornin production. Python dependencies are managed with uv. - db — PostgreSQL, schema bootstrapped from db/init/01_init.sql on first start.
- client.py — a small interactive Python script for exercising the API by hand (register/login, create/join/list games).
Environment variables & secrets
Two env files, both loaded by docker-compose.yml and the Makefile:
.env— non-secret defaults, safe to commit..env.secret— actual credentials, git-ignored. Copy the example and fill in real values before running.
Running with Docker
Everything runs via docker compose; the Makefile wraps the common commands.
There are two modes, selected by which compose files are combined:
| Mode | Command | Compose files | webserver runs via | Source code |
|---|---|---|---|---|
| Development (default) | make dev |
docker-compose.yml + docker-compose.override.yml (auto-loaded) |
flask run --debug (auto-reload) |
bind-mounted from ./webserver |
| Production | make prod |
docker-compose.yml only |
gunicorn, as a non-root user |
baked into the image |
make build-dev # build images and start the dev stack
make dev # start the dev stack (no rebuild)
make build-prod # build images and start the prod stack
make prod # start the prod stack (no rebuild)
make logs # tail webserver logs
make inspect # open a psql shell in the db container
make stop # stop the stack
make clean # stop the stack and delete the database volume
Development mounts your working copy of webserver/ straight into the
container, so edits to app.py take effect immediately (Flask's reloader
picks them up) without rebuilding. Production instead copies the code into
the image at build time and serves it with gunicorn --workers 2, running
as an unprivileged appuser.
Local development without Docker (editor setup)
Python dependencies for the webserver are managed with uv. To get a local virtualenv that your editor (Pylance/Pyright, PyCharm, etc.) can use for autocomplete and type-checking:
cd webserver
uv sync
This creates webserver/.venv (installing Python 3.12 via uv if it isn't
already available) with every dependency from pyproject.toml/uv.lock.
Point your editor's Python interpreter at webserver/.venv/bin/python
(VS Code: "Python: Select Interpreter"), and it will resolve flask,
psycopg, etc. correctly.
To add or update a dependency, run it from inside webserver/, e.g.:
uv add some-package
uv lock --upgrade-package flask
then rebuild the Docker images so the containers pick up the change:
make build-dev
Running the app itself still requires Postgres and the environment
variables set up above, so day-to-day running/testing is expected to happen
through Docker (make dev), not by running app.py directly on the host.
API overview
All responses are JSON: {"status": "ok"|"error", "message": "...", "payload": ...}.
| Method | Path | Auth | Description | Rate limit / IP |
|---|---|---|---|---|
| POST | /register |
– | create a user, then logs in | 10/day |
| POST | /login |
– | log in, sets JWT + CSRF cookies | 5/minute |
| GET | /logout |
JWT | clears auth cookies | - |
| GET | /lobby |
– | list open games waiting for players | - |
| GET | /games |
JWT | list the current user's games | - |
| GET | /game/<id> |
JWT | fetch one game's full state (players, whose turn, board) | - |
| GET | /me |
JWT | current user's id | - |
| POST | /create |
JWT | create a new game ({"name": "test"}), returns a join key |
- |
| POST | /join |
JWT | join a game via its lobby key (?game=<key>) |
- |
| POST | /play |
JWT | submit a turn: {"game_id": <id>, "move": {...}} |
- |
| GET | /health |
– | checks connectivity to the database | - |
Test client
client.py is a small interactive script for exercising the API by hand against a running stack:
./client.py [url] [username] [password]
It registers the user (or logs in if they already exist) and then offers a menu to create a game, list your games, join a game by key, or list the lobby.