Set up Unconfirmed -> Registered -> Confirmed -> Ready flow for Volunteers

This commit is contained in:
Pen Anderson 2026-03-05 15:52:40 -06:00
parent 62b3dece84
commit 72b245d6d6
7 changed files with 95 additions and 20 deletions

26
db.go
View file

@ -174,6 +174,8 @@ func migrateV2(db *sql.DB) error {
addColumnIfMissing(db, "volunteers", "pronouns TEXT NOT NULL DEFAULT ''")
addColumnIfMissing(db, "volunteers", "email_confirmed INTEGER NOT NULL DEFAULT 0")
addColumnIfMissing(db, "volunteers", "confirmation_token TEXT")
addColumnIfMissing(db, "volunteers", "confirmed INTEGER NOT NULL DEFAULT 0")
addColumnIfMissing(db, "volunteers", "confirmed_at TEXT")
// Widen the uniqueness constraint from name-only to (name, ticket_id).
db.Exec(`DROP INDEX IF EXISTS idx_attendees_name`)
db.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_attendees_name_ticket ON attendees(name, ticket_id) WHERE deleted_at IS NULL`)
@ -392,6 +394,8 @@ type Volunteer struct {
IsLead bool `json:"is_lead"`
CheckedIn bool `json:"checked_in"`
CheckedInAt *string `json:"checked_in_at,omitempty"`
Confirmed bool `json:"confirmed"`
ConfirmedAt *string `json:"confirmed_at,omitempty"`
EmailConfirmed bool `json:"email_confirmed"`
ConfirmationToken *string `json:"-"`
Note string `json:"note"`
@ -1184,6 +1188,7 @@ const volunteerSelect = `v.id, v.participant_id, v.attendee_id,
COALESCE(NULLIF(p.phone,''), v.phone),
COALESCE(NULLIF(p.pronouns,''), v.pronouns),
v.department_id, v.is_lead, v.checked_in, v.checked_in_at,
v.confirmed, v.confirmed_at,
v.email_confirmed, v.confirmation_token, v.note,
v.created_at, v.updated_at, v.deleted_at`
const volunteerFrom = `FROM volunteers v LEFT JOIN participants p ON p.id = v.participant_id`
@ -1293,6 +1298,19 @@ func (app *App) checkInVolunteer(id, userID int) (*Volunteer, error) {
return v, nil
}
func (app *App) confirmVolunteer(id int) (*Volunteer, error) {
t := now()
_, err := app.db.Exec(
`UPDATE volunteers SET confirmed=1, confirmed_at=?, updated_at=?
WHERE id=? AND deleted_at IS NULL AND confirmed=0`,
t, t, id,
)
if err != nil {
return nil, err
}
return app.getVolunteer(id)
}
func queryVolunteers(db *sql.DB, q string, args ...any) ([]Volunteer, error) {
rows, err := db.Query(q, args...)
if err != nil {
@ -1303,12 +1321,14 @@ func queryVolunteers(db *sql.DB, q string, args ...any) ([]Volunteer, error) {
for rows.Next() {
var v Volunteer
var participantID, attendeeID, deptID sql.NullInt64
var isLead, checkedIn, emailConfirmed int
var isLead, checkedIn, confirmed, emailConfirmed int
var confirmationToken sql.NullString
var confirmedAt sql.NullString
if err := rows.Scan(
&v.ID, &participantID, &attendeeID, &v.Name, &v.PreferredName, &v.TicketName,
&v.Email, &v.Phone, &v.Pronouns, &deptID,
&isLead, &checkedIn, &v.CheckedInAt,
&confirmed, &confirmedAt,
&emailConfirmed, &confirmationToken, &v.Note,
&v.CreatedAt, &v.UpdatedAt, &v.DeletedAt,
); err != nil {
@ -1329,8 +1349,12 @@ func queryVolunteers(db *sql.DB, q string, args ...any) ([]Volunteer, error) {
if confirmationToken.Valid {
v.ConfirmationToken = &confirmationToken.String
}
if confirmedAt.Valid {
v.ConfirmedAt = &confirmedAt.String
}
v.IsLead = isLead == 1
v.CheckedIn = checkedIn == 1
v.Confirmed = confirmed == 1
v.EmailConfirmed = emailConfirmed == 1
result = append(result, v)
}