Established Participants and Tickets model. Migrated concepts.

This commit is contained in:
Pen Anderson 2026-03-04 10:53:42 -06:00
parent 0df93e1886
commit cd8e1e3b3b
22 changed files with 1345 additions and 191 deletions

View file

@ -8,9 +8,9 @@ import (
"strings"
)
// handleGenerateTokens creates volunteer_token values for all attendees that don't have one.
// handleGenerateTokens creates codes for all tickets that don't have one.
func (app *App) handleGenerateTokens(w http.ResponseWriter, r *http.Request) {
count, err := app.generateTokensForAll()
count, err := app.generateCodesForAll()
if err != nil {
writeError(w, err.Error(), http.StatusInternalServerError)
return
@ -21,7 +21,7 @@ func (app *App) handleGenerateTokens(w http.ResponseWriter, r *http.Request) {
// handleExportTokenLinks streams a CSV download with token signup links,
// compatible with MailChimp / Zeffy bulk-send workflows.
func (app *App) handleExportTokenLinks(w http.ResponseWriter, r *http.Request) {
attendees, err := app.listAttendees("", "", "")
tickets, err := app.listTickets(nil, "")
if err != nil {
writeError(w, err.Error(), http.StatusInternalServerError)
return
@ -37,55 +37,62 @@ func (app *App) handleExportTokenLinks(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Disposition", `attachment; filename="volunteer-tokens.csv"`)
wr := csv.NewWriter(w)
wr.Write([]string{"Email Address", "First Name", "Token", "Signup Link"})
for _, a := range attendees {
if a.VolunteerToken == nil {
for _, tk := range tickets {
if tk.Code == nil || tk.ParticipantID == nil {
continue
}
firstName := a.Name
if parts := strings.Fields(a.Name); len(parts) > 0 {
p, _ := app.getParticipant(*tk.ParticipantID)
if p == nil || p.Email == "" {
continue
}
firstName := p.PreferredName
if firstName == "" {
firstName = tk.Name
}
if parts := strings.Fields(firstName); len(parts) > 0 {
firstName = parts[0]
}
link := fmt.Sprintf("%s/v/%s", baseURL, *a.VolunteerToken)
wr.Write([]string{a.Email, firstName, *a.VolunteerToken, link})
link := fmt.Sprintf("%s/v/%s", baseURL, *tk.Code)
wr.Write([]string{p.Email, firstName, *tk.Code, link})
}
wr.Flush()
}
// handleEmailToken sends a token email to a single attendee.
// handleEmailToken sends a token email to a single ticket's participant.
func (app *App) handleEmailToken(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 {
tk, err := app.getTicket(id)
if err != nil || tk == nil {
writeError(w, "not found", http.StatusNotFound)
return
}
if err := app.sendTokenEmail(*a); err != nil {
if err := app.sendTicketTokenEmail(*tk); err != nil {
writeError(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]any{"ok": true})
}
// handleEmailAllTokens bulk-sends token emails to all attendees that have both a token and email.
// handleEmailAllTokens bulk-sends token emails to all tickets that have a code and participant email.
func (app *App) handleEmailAllTokens(w http.ResponseWriter, r *http.Request) {
attendees, err := app.listAttendees("", "", "")
tickets, err := app.listTickets(nil, "")
if err != nil {
writeError(w, err.Error(), http.StatusInternalServerError)
return
}
var sent, skipped int
var errors []string
for _, a := range attendees {
if a.Email == "" || a.VolunteerToken == nil {
for _, tk := range tickets {
if tk.Code == nil || tk.ParticipantID == nil {
skipped++
continue
}
if err := app.sendTokenEmail(a); err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", a.Name, err))
if err := app.sendTicketTokenEmail(tk); err != nil {
errors = append(errors, fmt.Sprintf("ticket %d: %v", tk.ID, err))
skipped++
} else {
sent++