Universal server for games
Find a file
2026-08-19 22:46:32 +02:00
db/init Remember and update who is playing, closes #13 2026-08-19 17:31:08 +02:00
webserver Add /me and /game/<id> endpoints + cleanup 2026-08-19 22:46:32 +02:00
.env Migrate to newer psycopg version 2026-08-19 00:14:45 +02:00
.env.secret.example Add JWT tokens for login management, closes #11 2026-08-12 21:43:41 +02:00
.gitignore Implement listing players' games, #1 2026-08-09 00:08:51 +02:00
client.py Add /me and /game/<id> endpoints + cleanup 2026-08-19 22:46:32 +02:00
docker-compose.override.yml Setup production/development, manage Python packages with uv, documentation in README ft. Claude 2026-08-18 23:51:45 +02:00
docker-compose.yml Generalize webserver port 2026-08-19 00:01:04 +02:00
Makefile Setup production/development, manage Python packages with uv, documentation in README ft. Claude 2026-08-18 23:51:45 +02:00
README.md Add /me and /game/<id> endpoints + cleanup 2026-08-19 22:46:32 +02:00

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 by gunicorn in 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.