Created Turnpike, event attendee and volunteer management
Built after prototype, Traverse, an attendee and volunteer list maintainer.
This commit is contained in:
commit
d05b8dc7e0
59 changed files with 8663 additions and 0 deletions
167
handle_attendees.go
Normal file
167
handle_attendees.go
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (app *App) handleListAttendees(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
attendees, err := app.listAttendees(q.Get("search"), q.Get("ticket_type"), q.Get("checked_in"))
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
types, _ := app.attendeeTicketTypes()
|
||||
total, checkedIn, _ := app.attendeeCounts()
|
||||
writeJSON(w, map[string]any{
|
||||
"attendees": attendees,
|
||||
"ticket_types": types,
|
||||
"total": total,
|
||||
"checked_in": checkedIn,
|
||||
})
|
||||
}
|
||||
|
||||
func (app *App) handleCreateAttendee(w http.ResponseWriter, r *http.Request) {
|
||||
var a Attendee
|
||||
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
|
||||
writeError(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if a.Name == "" {
|
||||
writeError(w, "name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
created, err := app.createAttendee(a)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
writeJSON(w, created)
|
||||
}
|
||||
|
||||
func (app *App) handleGetAttendee(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeError(w, "invalid id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
a, err := app.getAttendee(id)
|
||||
if err != nil || a == nil {
|
||||
writeError(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
writeJSON(w, a)
|
||||
}
|
||||
|
||||
func (app *App) handleUpdateAttendee(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeError(w, "invalid id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var a Attendee
|
||||
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
|
||||
writeError(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if a.Name == "" {
|
||||
writeError(w, "name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
a.ID = id
|
||||
if err := app.updateAttendee(a); err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
updated, _ := app.getAttendee(id)
|
||||
writeJSON(w, updated)
|
||||
}
|
||||
|
||||
func (app *App) handleDeleteAttendee(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.deleteAttendee(id); err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// handleCheckInAttendee handles POST /api/attendees/:id/checkin.
|
||||
// Optional body: {"count": N, "also_volunteer": true}
|
||||
// Returns {"attendee": ..., "volunteer": ...} — volunteer is included if also_volunteer=true
|
||||
// and the attendee has a linked volunteer record.
|
||||
func (app *App) handleCheckInAttendee(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeError(w, "invalid id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Count int `json:"count"`
|
||||
AlsoVolunteer bool `json:"also_volunteer"`
|
||||
}
|
||||
body.Count = 1
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
if body.Count < 1 {
|
||||
body.Count = 1
|
||||
}
|
||||
|
||||
claims := claimsFromContext(r)
|
||||
a, err := app.checkInAttendee(id, claims.UserID, body.Count)
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]any{"attendee": a}
|
||||
|
||||
if body.AlsoVolunteer {
|
||||
v, _ := app.getVolunteerByAttendeeID(id)
|
||||
if v != nil {
|
||||
if !v.CheckedIn {
|
||||
if v2, err := app.checkInVolunteer(v.ID, claims.UserID); err == nil {
|
||||
result["volunteer"] = v2
|
||||
app.broker.publish("checkin", map[string]any{"type": "volunteer", "volunteer": v2})
|
||||
}
|
||||
} else {
|
||||
result["volunteer"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.broker.publish("checkin", map[string]any{"type": "attendee", "attendee": a})
|
||||
writeJSON(w, result)
|
||||
}
|
||||
|
||||
func (app *App) handleExportAttendees(w http.ResponseWriter, r *http.Request) {
|
||||
attendees, err := app.listAttendees("", "", "")
|
||||
if err != nil {
|
||||
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/csv")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="attendees.csv"`)
|
||||
wr := csv.NewWriter(w)
|
||||
wr.Write([]string{"name", "email", "phone", "ticket_id", "ticket_type", "party_size", "checked_in_count", "note", "checked_in"})
|
||||
for _, a := range attendees {
|
||||
ci := "no"
|
||||
if a.CheckedIn {
|
||||
ci = "yes"
|
||||
}
|
||||
wr.Write([]string{
|
||||
a.Name, a.Email, a.Phone, a.TicketID, a.TicketType,
|
||||
strconv.Itoa(a.PartySize), strconv.Itoa(a.CheckedInCount),
|
||||
a.Note, ci,
|
||||
})
|
||||
}
|
||||
wr.Flush()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue