diff --git a/db.go b/db.go index 9129489..c7a1089 100644 --- a/db.go +++ b/db.go @@ -201,6 +201,7 @@ func migrateV2(db *sql.DB) error { // and links volunteers to participants via participant_id. func migrateV3(db *sql.DB) error { addColumnIfMissing(db, "volunteers", "participant_id INTEGER REFERENCES participants(id)") + addColumnIfMissing(db, "participants", "ticket_name TEXT NOT NULL DEFAULT ''") // Seed participants from volunteers first (better name data: preferred_name). db.Exec(` @@ -401,7 +402,6 @@ type Volunteer struct { AttendeeID *int `json:"attendee_id,omitempty"` // deprecated; kept for migration compat Name string `json:"name"` PreferredName string `json:"preferred_name"` - TicketName string `json:"ticket_name"` Email string `json:"email"` Phone string `json:"phone"` Pronouns string `json:"pronouns"` @@ -424,6 +424,7 @@ type Participant struct { ID int `json:"id"` Email string `json:"email"` PreferredName string `json:"preferred_name"` + TicketName string `json:"ticket_name"` Phone string `json:"phone"` Pronouns string `json:"pronouns"` Note string `json:"note"` @@ -860,7 +861,7 @@ func (app *App) attendeeCounts() (total, checkedIn int, err error) { // --- Participants --- -const participantCols = `id, email, preferred_name, phone, pronouns, note, created_at, updated_at, deleted_at` +const participantCols = `id, email, preferred_name, ticket_name, phone, pronouns, note, created_at, updated_at, deleted_at` func (app *App) listParticipants(search, since string) ([]Participant, error) { var q string @@ -900,8 +901,8 @@ func (app *App) getParticipantByEmail(email string) (*Participant, error) { func (app *App) createParticipant(p Participant) (*Participant, error) { res, err := app.db.Exec( - `INSERT INTO participants (email, preferred_name, phone, pronouns, note, updated_at) VALUES (?, ?, ?, ?, ?, ?)`, - strings.ToLower(p.Email), p.PreferredName, p.Phone, p.Pronouns, p.Note, now(), + `INSERT INTO participants (email, preferred_name, ticket_name, phone, pronouns, note, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, + strings.ToLower(p.Email), p.PreferredName, p.TicketName, p.Phone, p.Pronouns, p.Note, now(), ) if err != nil { return nil, err @@ -912,9 +913,9 @@ func (app *App) createParticipant(p Participant) (*Participant, error) { func (app *App) updateParticipant(p Participant) error { _, err := app.db.Exec( - `UPDATE participants SET email=?, preferred_name=?, phone=?, pronouns=?, note=?, updated_at=? + `UPDATE participants SET email=?, preferred_name=?, ticket_name=?, phone=?, pronouns=?, note=?, updated_at=? WHERE id=? AND deleted_at IS NULL`, - strings.ToLower(p.Email), p.PreferredName, p.Phone, p.Pronouns, p.Note, now(), p.ID, + strings.ToLower(p.Email), p.PreferredName, p.TicketName, p.Phone, p.Pronouns, p.Note, now(), p.ID, ) return err } @@ -957,7 +958,7 @@ func queryParticipants(db *sql.DB, q string, args ...any) ([]Participant, error) for rows.Next() { var p Participant if err := rows.Scan( - &p.ID, &p.Email, &p.PreferredName, &p.Phone, &p.Pronouns, &p.Note, + &p.ID, &p.Email, &p.PreferredName, &p.TicketName, &p.Phone, &p.Pronouns, &p.Note, &p.CreatedAt, &p.UpdatedAt, &p.DeletedAt, ); err != nil { return nil, err @@ -1199,7 +1200,6 @@ func queryDepartments(db *sql.DB, q string, args ...any) ([]Department, error) { const volunteerSelect = `v.id, v.participant_id, v.attendee_id, COALESCE(NULLIF(p.preferred_name,''), NULLIF(v.preferred_name,''), v.name), COALESCE(NULLIF(p.preferred_name,''), NULLIF(v.preferred_name,''), v.name), - v.ticket_name, COALESCE(NULLIF(p.email,''), v.email), COALESCE(NULLIF(p.phone,''), v.phone), COALESCE(NULLIF(p.pronouns,''), v.pronouns), @@ -1210,7 +1210,7 @@ const volunteerSelect = `v.id, v.participant_id, v.attendee_id, const volunteerFrom = `FROM volunteers v LEFT JOIN participants p ON p.id = v.participant_id` // volunteerCols is kept for backward-compat references that expect unqualified column names. -const volunteerCols = `id, attendee_id, name, preferred_name, ticket_name, email, phone, pronouns, department_id, is_lead, checked_in, checked_in_at, email_confirmed, confirmation_token, note, created_at, updated_at, deleted_at` +const volunteerCols = `id, attendee_id, name, preferred_name, email, phone, pronouns, department_id, is_lead, checked_in, checked_in_at, email_confirmed, confirmation_token, note, created_at, updated_at, deleted_at` func (app *App) listVolunteers(search string, deptID *int, since string) ([]Volunteer, error) { q := `SELECT ` + volunteerSelect + ` ` + volunteerFrom + ` WHERE 1=1` @@ -1263,9 +1263,9 @@ func (app *App) getVolunteerByParticipantID(participantID int) (*Volunteer, erro func (app *App) createVolunteer(v Volunteer) (*Volunteer, error) { res, err := app.db.Exec( - `INSERT INTO volunteers (participant_id, attendee_id, name, preferred_name, ticket_name, email, phone, pronouns, department_id, is_lead, email_confirmed, confirmation_token, note, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - v.ParticipantID, v.AttendeeID, v.Name, v.PreferredName, v.TicketName, v.Email, v.Phone, v.Pronouns, + `INSERT INTO volunteers (participant_id, attendee_id, name, preferred_name, email, phone, pronouns, department_id, is_lead, email_confirmed, confirmation_token, note, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + v.ParticipantID, v.AttendeeID, v.Name, v.PreferredName, v.Email, v.Phone, v.Pronouns, v.DepartmentID, boolInt(v.IsLead), boolInt(v.EmailConfirmed), v.ConfirmationToken, v.Note, now(), ) if err != nil { @@ -1277,9 +1277,9 @@ func (app *App) createVolunteer(v Volunteer) (*Volunteer, error) { func (app *App) updateVolunteer(v Volunteer) error { _, err := app.db.Exec( - `UPDATE volunteers SET participant_id=?, attendee_id=?, name=?, preferred_name=?, ticket_name=?, email=?, phone=?, pronouns=?, department_id=?, is_lead=?, note=?, updated_at=? + `UPDATE volunteers SET participant_id=?, attendee_id=?, name=?, preferred_name=?, email=?, phone=?, pronouns=?, department_id=?, is_lead=?, note=?, updated_at=? WHERE id=? AND deleted_at IS NULL`, - v.ParticipantID, v.AttendeeID, v.Name, v.PreferredName, v.TicketName, v.Email, v.Phone, v.Pronouns, + v.ParticipantID, v.AttendeeID, v.Name, v.PreferredName, v.Email, v.Phone, v.Pronouns, v.DepartmentID, boolInt(v.IsLead), v.Note, now(), v.ID, ) return err @@ -1340,7 +1340,7 @@ func queryVolunteers(db *sql.DB, q string, args ...any) ([]Volunteer, error) { var isLead, checkedIn, confirmed, emailConfirmed int var confirmationToken, confirmedAt, kioskCode sql.NullString if err := rows.Scan( - &v.ID, &participantID, &attendeeID, &v.Name, &v.PreferredName, &v.TicketName, + &v.ID, &participantID, &attendeeID, &v.Name, &v.PreferredName, &v.Email, &v.Phone, &v.Pronouns, &deptID, &isLead, &checkedIn, &v.CheckedInAt, &confirmed, &confirmedAt, diff --git a/frontend/src/pages/Participants.svelte b/frontend/src/pages/Participants.svelte index 55714a6..2867958 100644 --- a/frontend/src/pages/Participants.svelte +++ b/frontend/src/pages/Participants.svelte @@ -63,6 +63,7 @@ return ($allTickets ?? []).filter(t => t.participant_id === participantId) } + function checkedInCount(participantId) { return ticketsFor(participantId).filter(t => t.checked_in_at).length } @@ -367,6 +368,9 @@ {#if p.pronouns} ยท {p.pronouns} {/if} + {#if p.ticket_name && p.ticket_name !== p.preferred_name} +
Ticket: {p.ticket_name}
+ {/if} {#if p.note}
{p.note}
{/if} diff --git a/frontend/src/pages/Volunteers.svelte b/frontend/src/pages/Volunteers.svelte index c8521e1..441e415 100644 --- a/frontend/src/pages/Volunteers.svelte +++ b/frontend/src/pages/Volunteers.svelte @@ -299,9 +299,6 @@ {:else if !participantHasTickets(v.participant_id)} No ticket {/if} - {#if v.ticket_name && v.ticket_name !== v.name} -
Ticket: {v.ticket_name}
- {/if} {#if v.email}
{v.email}
{/if} diff --git a/handle_signup.go b/handle_signup.go index bd9f091..77a7c63 100644 --- a/handle_signup.go +++ b/handle_signup.go @@ -75,12 +75,13 @@ func (app *App) handlePublicSignup(w http.ResponseWriter, r *http.Request) { return } // Update participant's personal details if they signed up with more info. - if body.Phone != "" || body.Pronouns != "" { + if body.Phone != "" || body.Pronouns != "" || body.TicketName != "" { app.db.Exec(`UPDATE participants SET - phone = CASE WHEN phone = '' THEN ? ELSE phone END, - pronouns = CASE WHEN pronouns = '' THEN ? ELSE pronouns END, + phone = CASE WHEN phone = '' THEN ? ELSE phone END, + pronouns = CASE WHEN pronouns = '' THEN ? ELSE pronouns END, + ticket_name = CASE WHEN ticket_name = '' THEN ? ELSE ticket_name END, updated_at = ? - WHERE id = ?`, body.Phone, body.Pronouns, now(), participant.ID) + WHERE id = ?`, body.Phone, body.Pronouns, body.TicketName, now(), participant.ID) } confirmToken, err := generateConfirmationToken() @@ -93,7 +94,6 @@ func (app *App) handlePublicSignup(w http.ResponseWriter, r *http.Request) { ParticipantID: &participant.ID, Name: body.PreferredName, PreferredName: body.PreferredName, - TicketName: body.TicketName, Email: body.Email, Phone: body.Phone, Pronouns: body.Pronouns, diff --git a/handle_signup_test.go b/handle_signup_test.go index c240596..2b63c16 100644 --- a/handle_signup_test.go +++ b/handle_signup_test.go @@ -337,8 +337,8 @@ func TestPublicSignupTicketNameDoesNotOverwritePreferredName(t *testing.T) { if p.PreferredName != "Titania" { t.Errorf("participant preferred_name = %q, want %q (not ticket_name)", p.PreferredName, "Titania") } - if vol.TicketName != "Titania Fairweather" { - t.Errorf("vol.TicketName = %q, want %q", vol.TicketName, "Titania Fairweather") + if p.TicketName != "Titania Fairweather" { + t.Errorf("participant.TicketName = %q, want %q", p.TicketName, "Titania Fairweather") } } @@ -349,12 +349,11 @@ func TestConfirmEmailAssignsKioskCode(t *testing.T) { app.db.Exec(`INSERT OR REPLACE INTO config (key, value) VALUES ('shift_signups_open', 'true')`) app.baseURL = "https://example.com" - participant, _ := app.createParticipant(Participant{PreferredName: "Titania", Email: "titania@example.com"}) + participant, _ := app.createParticipant(Participant{PreferredName: "Titania", TicketName: "Titania Fairweather", Email: "titania@example.com"}) token := "abc123def456" app.createVolunteer(Volunteer{ Name: "Titania", PreferredName: "Titania", - TicketName: "Titania Fairweather", Email: "titania@example.com", ParticipantID: &participant.ID, ConfirmationToken: &token,