package main import ( "encoding/csv" "fmt" "net/http" "strconv" "strings" ) // handleGenerateTokens creates codes for all tickets that don't have one. func (app *App) handleGenerateTokens(w http.ResponseWriter, r *http.Request) { count, err := app.generateCodesForAll() if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]any{"generated": count}) } // 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) { tickets, err := app.listTickets(nil, "") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } baseURL := app.baseURL if baseURL == "" { app.db.QueryRow(`SELECT value FROM config WHERE key = 'base_url'`).Scan(&baseURL) } baseURL = strings.TrimRight(baseURL, "/") w.Header().Set("Content-Type", "text/csv") 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 _, tk := range tickets { if tk.Code == nil || tk.ParticipantID == nil { continue } 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, *tk.Code) wr.Write([]string{p.Email, firstName, *tk.Code, link}) } wr.Flush() } // 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 } tk, err := app.getTicket(id) if err != nil || tk == nil { writeError(w, "not found", http.StatusNotFound) return } 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 tickets that have a code and participant email. func (app *App) handleEmailAllTokens(w http.ResponseWriter, r *http.Request) { tickets, err := app.listTickets(nil, "") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } var sent, skipped int var errors []string for _, tk := range tickets { if tk.Code == nil || tk.ParticipantID == nil { skipped++ continue } if err := app.sendTicketTokenEmail(tk); err != nil { errors = append(errors, fmt.Sprintf("ticket %d: %v", tk.ID, err)) skipped++ } else { sent++ } } if errors == nil { errors = []string{} } writeJSON(w, map[string]any{"sent": sent, "skipped": skipped, "errors": errors}) }