# Penalty Tracker A small, self-contained system for tracking competition penalties (e.g. for hot-air balloon competitions). ## Features - **System Admin** can create competitions and manage all users. - **Chief Scorer** (per competition) manages pilots, members, settings, and can export penalties as CSV. Chief Scorers can promote others to Chief Scorer or Scorer. - **Scorer** (per competition) adds penalties. Scorers can only edit/delete their own entries unless the competition setting "allow any scorer to edit" is on. - Pilots can be added manually or imported from CSV (`number,last_name,first_name,country,balloon_id`). - Rule catalogues per language are loaded from `rules/*.csv`. The penalty form lets you search rules by number or text and shows the suggested penalty plus repeat-behaviour (same / doubled / escalates). - All open clients receive **live updates** for a competition via WebSocket — works for 30+ concurrent users on a single SQLite process. - The penalty table is **client-side sortable** by every column and updates **in-place without flicker** when new rows arrive. - Multi-lingual UI: English, German, Polish, Russian, French, Spanish, Portuguese (set per user in user settings). - Clean black / white design with `#2b6cb0` accent, max border-radius `0.375rem`. ## Build & run ```bash go build -o penaltytracker . ./penaltytracker ``` Then open . **Default login (created automatically on first start):** ``` username: admin password: admin ``` Change the password immediately under the user menu. ## Configuration (env vars) | Variable | Default | Description | |-----------------|----------------------|--------------------------------------------------------------------------| | `ADDR` | `:8080` | HTTP listen address | | `DB_PATH` | `penaltytracker.db` | SQLite database path | | `RULES_DIR` | `rules` | Directory containing per-language rule CSV files | | `WEB_DIR` | `web` | Directory containing the frontend static files | | `CORS_ORIGINS` | *(empty)* | Comma-separated allowed frontend origins (e.g. `http://127.0.0.1:36823,https://app.example.com`). When set, CORS headers are added. Use `*` to allow any origin. | | `COOKIE_CROSS_SITE` | *(empty)* | Set to `true` only when the frontend runs on a different *site* (different registrable domain) than the API and you need cross-site cookies. Switches the session cookie to `SameSite=None; Secure` — requires HTTPS on both sides. Leave unset for same-host different-port setups (e.g. `127.0.0.1:36823 → 127.0.0.1:8080`); `SameSite=Lax` already works there. | ### Frontend backend URL If you host the frontend on a different origin than the API, open the login screen and fill in **Backend URL** (e.g. `http://192.168.0.10:8080` or `https://api.example.com`). It is stored in `localStorage` (`api_base`). Leave it empty to use the same origin as the page. You can also pre-set it by editing `web/config.js` (`DEFAULT_API_BASE`). Cross-origin cookies require **HTTPS on both sides** (so the `Secure` cookie flag is accepted). For LAN testing on plain HTTP, host the frontend and backend on the same origin (the default). ## Rules CSV format `rules/.csv` — one file per language code (`en`, `de`, `pl`, `ru`, `fr`, `es`, `pt`). ```csv rule_number,rule_text,suggested_penalty,escalation_mode R1.1,Late at briefing,Warning,escalate:Warning|50 CP|100 CP|DSQ R3.1,Unsafe flying,200 CP,doubled R4.2,Incorrect declaration,No Result,same ``` `escalation_mode`: - `same` — penalty stays the same on repeat. - `doubled` — penalty doubles each time. - `escalate:tier1|tier2|tier3|...` — penalty climbs through the listed tiers. To reload rules without restart, POST `/api/rules/reload` as a system admin. ## Pilot import CSV format ```csv number,last_name,first_name,country,balloon_id 1,Doe,John,DE,D-OABC 2,Müller,Anna,DE, ``` Header row is optional and auto-detected. ## Notes - Storage is a single SQLite file in WAL mode — easy to back up by copying the `.db` file (along with `-wal` / `-shm` if present) while the server is briefly stopped. - The frontend is pure HTML/CSS/JS, no build step. - The Go binary embeds nothing — it serves `./web` and `./rules` from the working directory.