// Web Scoreboard -- main (finished). // // Run from this folder, because the template and the data file are relative to // it: // cd projects/web-scoreboard/finished // go run . package main import ( "bytes" "errors" "fmt" "html/template" "net/http" "strconv" "webscoreboard/scoreboard" ) const templateFile = "templates/board.html" const dataFile = "scores.json" // boardData is everything the template needs. type boardData struct { Error string Players []scoreboard.Player } var boardTemplate *template.Template var board *scoreboard.Board // showBoard builds the page in memory, and sends it only if that worked. func showBoard(w http.ResponseWriter, status int, message string) { data := boardData{Error: message, Players: board.Players()} var page bytes.Buffer err := boardTemplate.Execute(&page, data) if err != nil { fmt.Println("Template error:", err) http.Error(w, "The page could not be built.", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) page.WriteTo(w) } // finish turns the result of a change into a response: a redirect on success, // and otherwise the status that fits the error. func finish(w http.ResponseWriter, r *http.Request, err error) { if err == nil { http.Redirect(w, r, "/", http.StatusSeeOther) return } if errors.Is(err, scoreboard.ErrNotSaved) { fmt.Println("Save error:", err) http.Error(w, "The change could not be saved.", http.StatusInternalServerError) return } if errors.Is(err, scoreboard.ErrUnknownPlayer) { showBoard(w, http.StatusNotFound, err.Error()) return } showBoard(w, http.StatusBadRequest, err.Error()) } func handleHome(w http.ResponseWriter, r *http.Request) { showBoard(w, http.StatusOK, "") } func handleAddPlayer(w http.ResponseWriter, r *http.Request) { err := board.AddPlayer(r.FormValue("name")) finish(w, r, err) } func handleAddPoints(w http.ResponseWriter, r *http.Request) { points, err := strconv.Atoi(r.FormValue("points")) if err != nil { showBoard(w, http.StatusBadRequest, "Points must be a whole number.") return } err = board.AddPoints(r.FormValue("name"), points) finish(w, r, err) } func main() { parsed, err := template.ParseFiles(templateFile) if err != nil { fmt.Println("Could not read the template:", err) fmt.Println("Run this program from its own folder, and check the template.") return } boardTemplate = parsed opened, err := scoreboard.Open(dataFile) if err != nil { fmt.Println("Could not load the scores:", err) fmt.Println("Fix or remove the file, then start the server again.") return } board = opened http.HandleFunc("GET /{$}", handleHome) http.HandleFunc("POST /players", handleAddPlayer) http.HandleFunc("POST /points", handleAddPoints) fmt.Println("Scoreboard running at http://localhost:8080") fmt.Println("Press Ctrl+C to stop it.") err = http.ListenAndServe("localhost:8080", nil) if err != nil { fmt.Println("Server stopped:", err) } }