A browser-based game prototype at https://documentbrain.com/agame/
Players visit the page, automatically get a persistent anonymous account, and earn points by clicking buttons. Points are saved server-side. Closing and reopening the browser preserves progress via a long-lived session cookie.
| Layer | Technology |
|---|---|
| Backend | Django + Django REST Framework |
| Frontend | React + Vite |
| Styling | Tailwind CSS |
| Database | MariaDB |
| Web Server | Caddy |
| Process Manager | systemd |
/home/agame/
backend/ # Django project
config/ # Django settings, urls, wsgi
game/ # App: API views (raw SQL via game/db.py), CSRF enforcement
venv/ # Python virtual environment
frontend/ # React app (Vite)
src/ # Components, entry point
dist/ # Production build (served by Caddy)
content/ # Writer-editable text (served at runtime)
ui.json # UI strings: title, buttons, labels
dialog/ # NPC dialog (future)
story/ # Story text (future)
- Caddy terminates HTTPS and serves the React build at
/agame/* - API requests (
/agame/api/*) are proxied to Django/gunicorn on port 8001 - Content files (
/agame/content/*) are served directly by Caddy with no-cache - Django sessions (stored in MariaDB) track anonymous users via a 2-year cookie
- All game text lives in
/content/— editable without a rebuild
Instance-specific values are centralized via environment variables and small helpers in backend/config/game_config.py.
Backend env vars:
GAME_SLUG(default:agame) — URL prefix and cookie name prefixDB_NAME(default: same asGAME_SLUG) — MariaDB database nameDB_USER(default: same asDB_NAME) — MariaDB usernameDJANGO_SECRET_KEY,DB_PASSWORD— required secrets
Frontend build env vars:
VITE_GAME_SLUG(default:agame) — must matchGAME_SLUG
Cookie names are derived from GAME_SLUG (<slug>_session, <slug>_csrf) and paths use /<slug>/.
Vite also uses VITE_GAME_SLUG to set the build base path, so you must set it for any non-/agame/ instance.
Two tables in the agame database, linked 1:1 by user_id (UUID, no FK constraint):
- user_login — user identity (user_id, name, created_at)
- players — game state (user_id, points, updated_at)
All SQL must go through backend/game/db.py. Do not call django.db.connection or cursor() directly elsewhere.
| Method | Path | Description |
|---|---|---|
| GET | /agame/api/user/me/ |
Get or create anonymous user |
| POST | /agame/api/user/me/points/ |
Add points (body: {"amount": N}) |
# Build frontend
cd /home/agame/frontend && npm run build
# Lint (backend: ruff + house rules)
cd /home/agame && ./lint
# Backend tests (Django)
cd /home/agame/backend && source venv/bin/activate && DB_PASSWORD=<password> python manage.py test game
# Frontend tests (Vitest)
cd /home/agame/frontend && npx vitest run# Restart backend after code changes
systemctl restart agame
# Edit game text (no rebuild needed)
nano /home/agame/content/ui.json
# View logs
journalctl -u agame -f
tail -f /var/log/agame-access.log
# Check database
mariadb -u root agame -e "SELECT * FROM user_login; SELECT * FROM players;"
# Reload Caddy after config changes
systemctl reload caddyThe ./lint command runs:
- Ruff (Python lint) on
backend/ - House rules (currently: all SQL must go through
backend/game/db.py)
Do not access django.db.connection or cursor() directly outside the DB helper module.