Some checks failed
Build and Push / build (push) Failing after 8s
Full-stack tournament management app with real-time scoring: - Go 1.26 backend with REST API and WebSocket live scoring - React 19 + Vite 8 frontend with mobile-first design - File-based JSON storage with JSONL audit logs - Multi-stage Docker build with Gitea CI/CD pipeline - Post-tournament questionnaire with spirit voting - Technical documentation and project description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
gorillaWs "github.com/gorilla/websocket"
|
|
|
|
"github.com/frisbee-tournament/backend/internal/models"
|
|
"github.com/frisbee-tournament/backend/internal/storage"
|
|
ws "github.com/frisbee-tournament/backend/internal/websocket"
|
|
)
|
|
|
|
type Handler struct {
|
|
store *storage.Store
|
|
hubMgr *ws.HubManager
|
|
}
|
|
|
|
func New(store *storage.Store, hubMgr *ws.HubManager) *Handler {
|
|
return &Handler{store: store, hubMgr: hubMgr}
|
|
}
|
|
|
|
var upgrader = gorillaWs.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
return true // trust-based, no auth
|
|
},
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(v)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, msg string) {
|
|
writeJSON(w, status, map[string]string{"error": msg})
|
|
}
|
|
|
|
// --- Tournament endpoints ---
|
|
|
|
func (h *Handler) ListTournaments(w http.ResponseWriter, r *http.Request) {
|
|
ts, err := h.store.GetTournaments()
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, ts)
|
|
}
|
|
|
|
func (h *Handler) GetTournament(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
t, err := h.store.GetTournament(id)
|
|
if err != nil {
|
|
writeError(w, 404, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, t)
|
|
}
|
|
|
|
// --- Schedule ---
|
|
|
|
func (h *Handler) GetSchedule(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
sched, err := h.store.GetSchedule(id)
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
// Merge persisted scores into schedule games
|
|
for i := range sched.Games {
|
|
g := &sched.Games[i]
|
|
state, err := h.store.GetScore(id, g.ID)
|
|
if err == nil && state.Status != "" {
|
|
g.HomeScore = state.HomeScore
|
|
g.AwayScore = state.AwayScore
|
|
g.Status = state.Status
|
|
}
|
|
}
|
|
writeJSON(w, 200, sched)
|
|
}
|
|
|
|
// --- Scoring ---
|
|
|
|
func (h *Handler) GetScore(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
state, err := h.store.GetScore(vars["id"], vars["gid"])
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, state)
|
|
}
|
|
|
|
func (h *Handler) UpdateScore(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
var update models.ScoreUpdate
|
|
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
|
|
writeError(w, 400, "invalid request body")
|
|
return
|
|
}
|
|
hub := h.hubMgr.GetOrCreateHub(vars["id"], vars["gid"])
|
|
state, err := hub.HandleScoreUpdate(update)
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, state)
|
|
}
|
|
|
|
func (h *Handler) GetAuditLog(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
entries, err := h.store.GetAuditLog(vars["id"], vars["gid"])
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
if entries == nil {
|
|
entries = []models.AuditEntry{}
|
|
}
|
|
writeJSON(w, 200, entries)
|
|
}
|
|
|
|
// --- WebSocket ---
|
|
|
|
func (h *Handler) WebSocketHandler(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Printf("ws upgrade error: %v", err)
|
|
return
|
|
}
|
|
userID := r.URL.Query().Get("user_id")
|
|
if userID == "" {
|
|
userID = "anonymous"
|
|
}
|
|
hub := h.hubMgr.GetOrCreateHub(vars["id"], vars["gid"])
|
|
client := hub.RegisterClient(conn, userID)
|
|
go client.WritePump()
|
|
go client.ReadPump()
|
|
}
|
|
|
|
func (h *Handler) TournamentWebSocketHandler(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
log.Printf("ws upgrade error: %v", err)
|
|
return
|
|
}
|
|
th := h.hubMgr.GetOrCreateTournamentHub(vars["id"])
|
|
client := th.RegisterClient(conn)
|
|
go client.WritePump()
|
|
go client.ReadPumpTournament(th)
|
|
}
|
|
|
|
// --- Questionnaire ---
|
|
|
|
func (h *Handler) GetQuestionnaire(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
cfg, err := h.store.GetQuestionnaireConfig(id)
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
t, _ := h.store.GetTournament(id)
|
|
resp := map[string]any{
|
|
"config": cfg,
|
|
}
|
|
if t != nil {
|
|
resp["teams"] = t.Teams
|
|
}
|
|
writeJSON(w, 200, resp)
|
|
}
|
|
|
|
func (h *Handler) SubmitQuestionnaire(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
var resp models.QuestionnaireResponse
|
|
if err := json.NewDecoder(r.Body).Decode(&resp); err != nil {
|
|
writeError(w, 400, "invalid request body")
|
|
return
|
|
}
|
|
resp.TourneyID = id
|
|
if err := h.store.SaveQuestionnaireResponse(id, &resp); err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 201, map[string]string{"status": "ok", "id": resp.ID})
|
|
}
|
|
|
|
func (h *Handler) GetQuestionnaireResults(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
resps, err := h.store.GetQuestionnaireResponses(id)
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
if resps == nil {
|
|
resps = []models.QuestionnaireResponse{}
|
|
}
|
|
writeJSON(w, 200, resps)
|
|
}
|
|
|
|
// --- Results ---
|
|
|
|
func (h *Handler) GetResults(w http.ResponseWriter, r *http.Request) {
|
|
id := mux.Vars(r)["id"]
|
|
res, err := h.store.GetResults(id)
|
|
if err != nil {
|
|
writeError(w, 500, err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, 200, res)
|
|
}
|