216 lines
6.2 KiB
Go
216 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func registerPilotRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /api/competitions/{id}/pilots", requireAuth(handleListPilots))
|
|
mux.HandleFunc("POST /api/competitions/{id}/pilots", requireAuth(handleCreatePilot))
|
|
mux.HandleFunc("PATCH /api/competitions/{id}/pilots/{pid}", requireAuth(handleUpdatePilot))
|
|
mux.HandleFunc("DELETE /api/competitions/{id}/pilots/{pid}", requireAuth(handleDeletePilot))
|
|
mux.HandleFunc("POST /api/competitions/{id}/pilots/import", requireAuth(handleImportPilots))
|
|
}
|
|
|
|
func requireChiefOrAdmin(r *http.Request, competitionID int64) bool {
|
|
u := userFromCtx(r)
|
|
role, ok := canAccessCompetition(u, competitionID)
|
|
if !ok {
|
|
return false
|
|
}
|
|
return role == "system_admin" || role == "chief_scorer"
|
|
}
|
|
|
|
func handleListPilots(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
u := userFromCtx(r)
|
|
if _, ok := canAccessCompetition(u, id); !ok {
|
|
writeError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
rows, err := db.Query("SELECT id,competition_id,number,last_name,first_name,country,balloon_id FROM pilots WHERE competition_id=? ORDER BY number", id)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "db_error")
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
out := []Pilot{}
|
|
for rows.Next() {
|
|
var p Pilot
|
|
rows.Scan(&p.ID, &p.CompetitionID, &p.Number, &p.LastName, &p.FirstName, &p.Country, &p.BalloonID)
|
|
out = append(out, p)
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
|
|
func handleCreatePilot(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
if !requireChiefOrAdmin(r, id) {
|
|
writeError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var p Pilot
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_body")
|
|
return
|
|
}
|
|
p.Number = strings.TrimSpace(p.Number)
|
|
if p.Number == "" || p.LastName == "" {
|
|
writeError(w, http.StatusBadRequest, "missing_fields")
|
|
return
|
|
}
|
|
if len(p.Number) > 32 || len(p.LastName) > 128 || len(p.FirstName) > 128 ||
|
|
len(p.Country) > 64 || len(p.BalloonID) > 64 {
|
|
writeError(w, http.StatusBadRequest, "too_long")
|
|
return
|
|
}
|
|
res, err := db.Exec(
|
|
"INSERT INTO pilots(competition_id,number,last_name,first_name,country,balloon_id) VALUES(?,?,?,?,?,?)",
|
|
id, p.Number, p.LastName, p.FirstName, p.Country, p.BalloonID,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusConflict, "duplicate_number")
|
|
return
|
|
}
|
|
p.ID, _ = res.LastInsertId()
|
|
p.CompetitionID = id
|
|
hub.broadcast(id, "pilot_changed", nil)
|
|
writeJSON(w, http.StatusCreated, p)
|
|
}
|
|
|
|
func handleUpdatePilot(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
pid, err := strconv.ParseInt(r.PathValue("pid"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
if !requireChiefOrAdmin(r, id) {
|
|
writeError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
var p Pilot
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_body")
|
|
return
|
|
}
|
|
_, err = db.Exec(
|
|
"UPDATE pilots SET number=?,last_name=?,first_name=?,country=?,balloon_id=? WHERE id=? AND competition_id=?",
|
|
p.Number, p.LastName, p.FirstName, p.Country, p.BalloonID, pid, id,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "db_error")
|
|
return
|
|
}
|
|
hub.broadcast(id, "pilot_changed", nil)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func handleDeletePilot(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
pid, err := strconv.ParseInt(r.PathValue("pid"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
if !requireChiefOrAdmin(r, id) {
|
|
writeError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
db.Exec("DELETE FROM pilots WHERE id=? AND competition_id=?", pid, id)
|
|
hub.broadcast(id, "pilot_changed", nil)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func handleImportPilots(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_id")
|
|
return
|
|
}
|
|
if !requireChiefOrAdmin(r, id) {
|
|
writeError(w, http.StatusForbidden, "forbidden")
|
|
return
|
|
}
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "read_error")
|
|
return
|
|
}
|
|
reader := csv.NewReader(strings.NewReader(string(body)))
|
|
reader.FieldsPerRecord = -1
|
|
records, err := reader.ReadAll()
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid_csv")
|
|
return
|
|
}
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "tx_error")
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
stmt, _ := tx.Prepare(`INSERT INTO pilots(competition_id,number,last_name,first_name,country,balloon_id)
|
|
VALUES(?,?,?,?,?,?)
|
|
ON CONFLICT(competition_id,number) DO UPDATE SET
|
|
last_name=excluded.last_name, first_name=excluded.first_name,
|
|
country=excluded.country, balloon_id=excluded.balloon_id`)
|
|
defer stmt.Close()
|
|
count := 0
|
|
for i, rec := range records {
|
|
if i == 0 {
|
|
lower := strings.ToLower(strings.TrimSpace(rec[0]))
|
|
if lower == "number" || lower == "nummer" || lower == "no" {
|
|
continue
|
|
}
|
|
}
|
|
if len(rec) < 3 {
|
|
continue
|
|
}
|
|
number := strings.TrimSpace(rec[0])
|
|
lastName := strings.TrimSpace(rec[1])
|
|
firstName := strings.TrimSpace(rec[2])
|
|
country := ""
|
|
balloon := ""
|
|
if len(rec) >= 4 {
|
|
country = strings.TrimSpace(rec[3])
|
|
}
|
|
if len(rec) >= 5 {
|
|
balloon = strings.TrimSpace(rec[4])
|
|
}
|
|
if number == "" {
|
|
continue
|
|
}
|
|
if _, err := stmt.Exec(id, number, lastName, firstName, country, balloon); err == nil {
|
|
count++
|
|
}
|
|
}
|
|
if err := tx.Commit(); err != nil {
|
|
writeError(w, http.StatusInternalServerError, "commit_error")
|
|
return
|
|
}
|
|
hub.broadcast(id, "pilot_changed", nil)
|
|
writeJSON(w, http.StatusOK, map[string]int{"imported": count})
|
|
}
|