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() }