package main import ( "encoding/csv" "encoding/json" "net/http" "strconv" ) func (app *App) handleListParticipants(w http.ResponseWriter, r *http.Request) { search := r.URL.Query().Get("search") participants, err := app.listParticipants(search, "") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } total, checkedIn, _ := app.ticketCounts() types, _ := app.ticketTypes() writeJSON(w, map[string]any{ "participants": participants, "total": total, "checked_in": checkedIn, "ticket_types": types, }) } func (app *App) handleGetParticipant(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, "invalid id", http.StatusBadRequest) return } p, err := app.getParticipant(id) if err != nil || p == nil { writeError(w, "not found", http.StatusNotFound) return } tickets, _ := app.listTickets(&id, "") writeJSON(w, map[string]any{"participant": p, "tickets": tickets}) } func (app *App) handleCreateParticipant(w http.ResponseWriter, r *http.Request) { var p Participant if err := json.NewDecoder(r.Body).Decode(&p); err != nil { writeError(w, "invalid request", http.StatusBadRequest) return } if p.PreferredName == "" && p.Email == "" { writeError(w, "name or email is required", http.StatusBadRequest) return } created, err := app.createParticipant(p) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) writeJSON(w, created) } func (app *App) handleUpdateParticipant(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, "invalid id", http.StatusBadRequest) return } var p Participant if err := json.NewDecoder(r.Body).Decode(&p); err != nil { writeError(w, "invalid request", http.StatusBadRequest) return } p.ID = id if err := app.updateParticipant(p); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } updated, _ := app.getParticipant(id) writeJSON(w, updated) } func (app *App) handleDeleteParticipant(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, "invalid id", http.StatusBadRequest) return } if err := app.deleteParticipant(id); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // handleMergeParticipants reassigns all tickets and volunteers from otherID to // canonicalID, then soft-deletes the other participant. func (app *App) handleMergeParticipants(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, "invalid id", http.StatusBadRequest) return } otherID, err := strconv.Atoi(r.PathValue("other_id")) if err != nil { writeError(w, "invalid other_id", http.StatusBadRequest) return } if err := app.mergeParticipants(id, otherID); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } p, _ := app.getParticipant(id) tickets, _ := app.listTickets(&id, "") writeJSON(w, map[string]any{"participant": p, "tickets": tickets}) } func (app *App) handleExportParticipants(w http.ResponseWriter, r *http.Request) { participants, err := app.listParticipants("", "") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/csv") w.Header().Set("Content-Disposition", `attachment; filename="participants.csv"`) wr := csv.NewWriter(w) wr.Write([]string{"id", "email", "preferred_name", "phone", "pronouns", "note"}) for _, p := range participants { wr.Write([]string{ strconv.Itoa(p.ID), p.Email, p.PreferredName, p.Phone, p.Pronouns, p.Note, }) } wr.Flush() } func (app *App) handleCreateTicket(w http.ResponseWriter, r *http.Request) { var t Ticket if err := json.NewDecoder(r.Body).Decode(&t); err != nil { writeError(w, "invalid request", http.StatusBadRequest) return } if t.ParticipantID == nil { writeError(w, "participant_id is required", http.StatusBadRequest) return } if t.Source == "" { t.Source = "manual" } created, err := app.createTicket(t) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) writeJSON(w, created) } func (app *App) handleListTickets(w http.ResponseWriter, r *http.Request) { tickets, err := app.listTickets(nil, "") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]any{"tickets": tickets}) } func (app *App) handleCheckInTicket(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, "invalid id", http.StatusBadRequest) return } claims := claimsFromContext(r) tk, err := app.checkInTicket(id, claims.ParticipantID) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } app.broker.publish("checkin", map[string]any{"type": "ticket", "ticket": tk}) writeJSON(w, map[string]any{"ticket": tk}) }