package main import ( "flag" "log" "net/http" "os" "github.com/gorilla/mux" "github.com/rs/cors" "github.com/frisbee-tournament/backend/internal/handlers" "github.com/frisbee-tournament/backend/internal/storage" ws "github.com/frisbee-tournament/backend/internal/websocket" ) func main() { port := flag.String("port", "8080", "server port") dataDir := flag.String("data", "./data", "data directory") staticDir := flag.String("static", "./static", "frontend static files") flag.Parse() if envPort := os.Getenv("PORT"); envPort != "" { *port = envPort } if envData := os.Getenv("DATA_DIR"); envData != "" { *dataDir = envData } store := storage.New(*dataDir) hubMgr := ws.NewHubManager(store) h := handlers.New(store, hubMgr) r := mux.NewRouter() // API routes api := r.PathPrefix("/api").Subrouter() api.HandleFunc("/tournaments", h.ListTournaments).Methods("GET") api.HandleFunc("/tournaments/{id}", h.GetTournament).Methods("GET") api.HandleFunc("/tournaments/{id}/schedule", h.GetSchedule).Methods("GET") api.HandleFunc("/tournaments/{id}/games/{gid}/score", h.GetScore).Methods("GET") api.HandleFunc("/tournaments/{id}/games/{gid}/score", h.UpdateScore).Methods("POST") api.HandleFunc("/tournaments/{id}/games/{gid}/audit", h.GetAuditLog).Methods("GET") api.HandleFunc("/tournaments/{id}/questionnaire", h.GetQuestionnaire).Methods("GET") api.HandleFunc("/tournaments/{id}/questionnaire", h.SubmitQuestionnaire).Methods("POST") api.HandleFunc("/tournaments/{id}/questionnaire/results", h.GetQuestionnaireResults).Methods("GET") api.HandleFunc("/tournaments/{id}/results", h.GetResults).Methods("GET") // WebSocket r.HandleFunc("/ws/game/{id}/{gid}", h.WebSocketHandler) r.HandleFunc("/ws/tournament/{id}", h.TournamentWebSocketHandler) // Serve frontend (SPA fallback) spa := spaHandler{staticPath: *staticDir, indexPath: "index.html"} r.PathPrefix("/").Handler(spa) c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"*"}, AllowCredentials: true, }) srv := &http.Server{ Addr: ":" + *port, Handler: c.Handler(r), } log.Printf("🥏 Frisbee Tournament server starting on :%s", *port) log.Printf(" Data dir: %s", *dataDir) log.Printf(" Static dir: %s", *staticDir) if err := srv.ListenAndServe(); err != nil { log.Fatal(err) } } // spaHandler serves an SPA with history-mode fallback type spaHandler struct { staticPath string indexPath string } func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := h.staticPath + r.URL.Path _, err := os.Stat(path) if os.IsNotExist(err) { // SPA fallback: serve index.html http.ServeFile(w, r, h.staticPath+"/"+h.indexPath) return } else if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r) }