All checks were successful
Build and Push / build (push) Successful in 44s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🥏 Disc Agenda — Frisbee Tournament Platform
A self-hosted tournament management web app for ultimate frisbee, featuring live multiplayer scoring via WebSockets, a mobile-friendly questionnaire with QR codes, and a clean athletic visual design.
Architecture
┌──────────────────────────────────────────┐
│ Go 1.26 Backend (single binary) │
│ ├─ REST API (gorilla/mux) │
│ ├─ WebSocket hub (gorilla/websocket) │
│ ├─ SPA file server │
│ └─ File-based JSON storage │
├──────────────────────────────────────────┤
│ React 19 + Vite 8 Frontend │
│ ├─ react-router-dom v7 (SPA) │
│ ├─ qrcode.react (QR generation) │
│ └─ Custom CSS (Bebas Neue + Barlow) │
├──────────────────────────────────────────┤
│ Data: flat JSON files + JSONL audit logs │
│ (no database required) │
└──────────────────────────────────────────┘
Features
- Tournament hub — home page with location, dates, teams, rules
- Schedule — round-grouped game schedule with status badges
- Live scoring — WebSocket-powered multiplayer scoreboard with +/- and SET controls
- Audit log — every score change persisted as JSONL per game
- Questionnaire — mobile-friendly survey with QR code, team selectors, spirit voting, custom questions
- Results — final standings table with spirit award highlight
- Past tournaments — archive of completed events
Quick Start
Docker (recommended)
docker compose up --build
# → http://localhost:8080
Data persists in a Docker volume. Seed data auto-copies on first run.
Local Development
Prerequisites: Go 1.26+, Node 22+
Terminal 1 — backend:
cd backend
go mod tidy
go run ./cmd/server -data ../data -static ../frontend/dist -port 8080
Terminal 2 — frontend (with hot reload + API proxy):
cd frontend
npm install
npm run dev
# → http://localhost:5173 (proxies /api and /ws to :8080)
Project Structure
├── backend/
│ ├── cmd/server/main.go # Entry point, router, SPA handler
│ └── internal/
│ ├── handlers/handlers.go # REST + WS HTTP handlers
│ ├── models/models.go # Domain types
│ ├── storage/storage.go # File-based persistence
│ └── websocket/hub.go # Per-game WS hub + broadcast
├── frontend/
│ ├── src/
│ │ ├── api.js # API client + WS factory
│ │ ├── App.jsx # Router
│ │ ├── main.jsx # Entry
│ │ ├── components/ # Header, Footer, Icons
│ │ ├── pages/ # All page components
│ │ └── styles/global.css # Full stylesheet
│ ├── vite.config.js # Dev proxy config
│ └── index.html
├── data/ # Seed data (JSON files)
│ ├── tournaments.json
│ └── tournaments/{id}/
│ ├── schedule.json
│ ├── questionnaire_config.json
│ ├── results.json
│ └── games/ # Score state + audit logs
├── Dockerfile # Multi-stage build
├── docker-compose.yml
└── Makefile
API Reference
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/tournaments |
List all tournaments |
| GET | /api/tournaments/{id} |
Tournament details |
| GET | /api/tournaments/{id}/schedule |
Game schedule |
| GET | /api/tournaments/{id}/games/{gid}/score |
Current score |
| POST | /api/tournaments/{id}/games/{gid}/score |
Update score (REST) |
| GET | /api/tournaments/{id}/games/{gid}/audit |
Audit log |
| WS | /ws/game/{id}/{gid}?user_id=x |
Live score WebSocket |
| GET | /api/tournaments/{id}/questionnaire |
Questionnaire config + teams |
| POST | /api/tournaments/{id}/questionnaire |
Submit response |
| GET | /api/tournaments/{id}/questionnaire/results |
All responses (admin) |
| GET | /api/tournaments/{id}/results |
Final standings |
WebSocket Protocol
Connect: ws://host/ws/game/{tourneyId}/{gameId}?user_id=alice
Send (client → server):
{"action": "increment", "team": "home"}
{"action": "decrement", "team": "away"}
{"action": "set", "team": "home", "value": 12}
Receive (server → all clients):
{
"type": "score_update",
"state": {"game_id": "g01", "home_score": 8, "away_score": 6, ...},
"audit": {"action": "increment", "team": "home", "old_home": 7, ...}
}
Configuration
All via flags or env vars:
| Flag | Env | Default | Description |
|---|---|---|---|
-port |
PORT |
8080 |
Listen port |
-data |
DATA_DIR |
./data |
Data directory |
-static |
— | ./static |
Frontend files |
Adding Tournament Data
Edit JSON files directly in data/:
tournaments.json— add/edit tournament objectstournaments/{id}/schedule.json— game scheduletournaments/{id}/questionnaire_config.json— custom survey questionstournaments/{id}/results.json— final standings
No admin UI yet — data is managed via files. Future: add admin panel.
Tech Stack
- Backend: Go 1.26, gorilla/mux 1.8.1, gorilla/websocket 1.5.3, rs/cors
- Frontend: React 19, Vite 8, react-router-dom 7, qrcode.react
- Storage: Flat JSON files + JSONL audit logs
- Container: Alpine 3.21, multi-stage Docker build
Documentation
- Technical Documentation — detailed API reference, data models, WebSocket protocol, deployment guides, troubleshooting
- Project Description — project overview, motivation, features, design philosophy, roadmap
Description
Languages
JavaScript
48.3%
Go
28.5%
CSS
19.8%
Makefile
1.4%
Dockerfile
1.3%
Other
0.7%