158 lines
4.5 KiB
Go
158 lines
4.5 KiB
Go
|
|
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) 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.UserID)
|
||
|
|
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})
|
||
|
|
}
|