Add yes/no RSVP answers with required login

RSVPs now carry a status ('yes'/'no'). When auth is enabled, answering
requires login for all events, not just capped ones. Answers are deduped
one per user and re-answering updates in place. A 'no' zeroes plus_one,
is excluded from Going counts and cap math, frees the person's slot
claims, and shows in a separate "Can't make it" list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 07:59:33 -04:00
parent 891b49b4c0
commit cf2ef77bb0
12 changed files with 341 additions and 107 deletions
+1
View File
@@ -37,6 +37,7 @@ type Rsvp struct {
Name string
Note string
PlusOne int64
Status string
UserID sql.NullInt64
CreatedAt time.Time
}
+8 -4
View File
@@ -64,12 +64,16 @@ DELETE FROM claims WHERE id = ?;
-- name: CountClaimsBySlot :one
SELECT COUNT(*) FROM claims WHERE slot_id = ?;
-- name: DeleteClaimsByEventAndName :exec
DELETE FROM claims WHERE slot_id IN (SELECT id FROM slots WHERE event_id = ?)
AND claims.name = ? COLLATE NOCASE;
-- name: ListRsvps :many
SELECT * FROM rsvps WHERE event_id = ? ORDER BY created_at;
-- name: CreateRsvp :one
INSERT INTO rsvps (event_id, name, note, plus_one, user_id)
VALUES (?, ?, ?, ?, ?)
INSERT INTO rsvps (event_id, name, note, plus_one, status, user_id)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING *;
-- name: DeleteRsvp :exec
@@ -85,7 +89,7 @@ SELECT * FROM rsvps WHERE event_id = ? AND name = ? COLLATE NOCASE LIMIT 1;
SELECT * FROM rsvps WHERE event_id = ? AND user_id = ? LIMIT 1;
-- name: CountGoing :one
SELECT CAST(COALESCE(SUM(1 + plus_one), 0) AS INTEGER) FROM rsvps WHERE event_id = ?;
SELECT CAST(COALESCE(SUM(1 + plus_one), 0) AS INTEGER) FROM rsvps WHERE event_id = ? AND status = 'yes';
-- name: DeleteDuplicateRsvps :exec
DELETE FROM rsvps WHERE id NOT IN (
@@ -140,7 +144,7 @@ UPDATE events SET user_id = ? WHERE id = ?;
SELECT * FROM rsvps WHERE id = ?;
-- name: UpdateRsvp :exec
UPDATE rsvps SET name = ?, note = ?, plus_one = ? WHERE id = ?;
UPDATE rsvps SET name = ?, note = ?, plus_one = ?, status = ? WHERE id = ?;
-- name: CreateSmsConsent :exec
INSERT INTO sms_consents (phone, ip_address, user_agent) VALUES (?, ?, ?);
+33 -9
View File
@@ -23,7 +23,7 @@ func (q *Queries) CountClaimsBySlot(ctx context.Context, slotID int64) (int64, e
}
const countGoing = `-- name: CountGoing :one
SELECT CAST(COALESCE(SUM(1 + plus_one), 0) AS INTEGER) FROM rsvps WHERE event_id = ?
SELECT CAST(COALESCE(SUM(1 + plus_one), 0) AS INTEGER) FROM rsvps WHERE event_id = ? AND status = 'yes'
`
func (q *Queries) CountGoing(ctx context.Context, eventID int64) (int64, error) {
@@ -115,9 +115,9 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event
}
const createRsvp = `-- name: CreateRsvp :one
INSERT INTO rsvps (event_id, name, note, plus_one, user_id)
VALUES (?, ?, ?, ?, ?)
RETURNING id, event_id, name, note, plus_one, user_id, created_at
INSERT INTO rsvps (event_id, name, note, plus_one, status, user_id)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, event_id, name, note, plus_one, status, user_id, created_at
`
type CreateRsvpParams struct {
@@ -125,6 +125,7 @@ type CreateRsvpParams struct {
Name string
Note string
PlusOne int64
Status string
UserID sql.NullInt64
}
@@ -134,6 +135,7 @@ func (q *Queries) CreateRsvp(ctx context.Context, arg CreateRsvpParams) (Rsvp, e
arg.Name,
arg.Note,
arg.PlusOne,
arg.Status,
arg.UserID,
)
var i Rsvp
@@ -143,6 +145,7 @@ func (q *Queries) CreateRsvp(ctx context.Context, arg CreateRsvpParams) (Rsvp, e
&i.Name,
&i.Note,
&i.PlusOne,
&i.Status,
&i.UserID,
&i.CreatedAt,
)
@@ -271,6 +274,21 @@ func (q *Queries) DeleteClaim(ctx context.Context, id int64) error {
return err
}
const deleteClaimsByEventAndName = `-- name: DeleteClaimsByEventAndName :exec
DELETE FROM claims WHERE slot_id IN (SELECT id FROM slots WHERE event_id = ?)
AND claims.name = ? COLLATE NOCASE
`
type DeleteClaimsByEventAndNameParams struct {
EventID int64
Name string
}
func (q *Queries) DeleteClaimsByEventAndName(ctx context.Context, arg DeleteClaimsByEventAndNameParams) error {
_, err := q.db.ExecContext(ctx, deleteClaimsByEventAndName, arg.EventID, arg.Name)
return err
}
const deleteDuplicateRsvps = `-- name: DeleteDuplicateRsvps :exec
DELETE FROM rsvps WHERE id NOT IN (
SELECT MIN(id) FROM rsvps GROUP BY event_id, name COLLATE NOCASE
@@ -391,7 +409,7 @@ func (q *Queries) GetEventBySlug(ctx context.Context, slug string) (Event, error
}
const getRsvp = `-- name: GetRsvp :one
SELECT id, event_id, name, note, plus_one, user_id, created_at FROM rsvps WHERE id = ?
SELECT id, event_id, name, note, plus_one, status, user_id, created_at FROM rsvps WHERE id = ?
`
func (q *Queries) GetRsvp(ctx context.Context, id int64) (Rsvp, error) {
@@ -403,6 +421,7 @@ func (q *Queries) GetRsvp(ctx context.Context, id int64) (Rsvp, error) {
&i.Name,
&i.Note,
&i.PlusOne,
&i.Status,
&i.UserID,
&i.CreatedAt,
)
@@ -410,7 +429,7 @@ func (q *Queries) GetRsvp(ctx context.Context, id int64) (Rsvp, error) {
}
const getRsvpByName = `-- name: GetRsvpByName :one
SELECT id, event_id, name, note, plus_one, user_id, created_at FROM rsvps WHERE event_id = ? AND name = ? COLLATE NOCASE LIMIT 1
SELECT id, event_id, name, note, plus_one, status, user_id, created_at FROM rsvps WHERE event_id = ? AND name = ? COLLATE NOCASE LIMIT 1
`
type GetRsvpByNameParams struct {
@@ -427,6 +446,7 @@ func (q *Queries) GetRsvpByName(ctx context.Context, arg GetRsvpByNameParams) (R
&i.Name,
&i.Note,
&i.PlusOne,
&i.Status,
&i.UserID,
&i.CreatedAt,
)
@@ -434,7 +454,7 @@ func (q *Queries) GetRsvpByName(ctx context.Context, arg GetRsvpByNameParams) (R
}
const getRsvpByUser = `-- name: GetRsvpByUser :one
SELECT id, event_id, name, note, plus_one, user_id, created_at FROM rsvps WHERE event_id = ? AND user_id = ? LIMIT 1
SELECT id, event_id, name, note, plus_one, status, user_id, created_at FROM rsvps WHERE event_id = ? AND user_id = ? LIMIT 1
`
type GetRsvpByUserParams struct {
@@ -451,6 +471,7 @@ func (q *Queries) GetRsvpByUser(ctx context.Context, arg GetRsvpByUserParams) (R
&i.Name,
&i.Note,
&i.PlusOne,
&i.Status,
&i.UserID,
&i.CreatedAt,
)
@@ -653,7 +674,7 @@ func (q *Queries) ListEventsByUser(ctx context.Context, userID sql.NullInt64) ([
}
const listRsvps = `-- name: ListRsvps :many
SELECT id, event_id, name, note, plus_one, user_id, created_at FROM rsvps WHERE event_id = ? ORDER BY created_at
SELECT id, event_id, name, note, plus_one, status, user_id, created_at FROM rsvps WHERE event_id = ? ORDER BY created_at
`
func (q *Queries) ListRsvps(ctx context.Context, eventID int64) ([]Rsvp, error) {
@@ -671,6 +692,7 @@ func (q *Queries) ListRsvps(ctx context.Context, eventID int64) ([]Rsvp, error)
&i.Name,
&i.Note,
&i.PlusOne,
&i.Status,
&i.UserID,
&i.CreatedAt,
); err != nil {
@@ -811,13 +833,14 @@ func (q *Queries) UpdateEventLocation(ctx context.Context, arg UpdateEventLocati
}
const updateRsvp = `-- name: UpdateRsvp :exec
UPDATE rsvps SET name = ?, note = ?, plus_one = ? WHERE id = ?
UPDATE rsvps SET name = ?, note = ?, plus_one = ?, status = ? WHERE id = ?
`
type UpdateRsvpParams struct {
Name string
Note string
PlusOne int64
Status string
ID int64
}
@@ -826,6 +849,7 @@ func (q *Queries) UpdateRsvp(ctx context.Context, arg UpdateRsvpParams) error {
arg.Name,
arg.Note,
arg.PlusOne,
arg.Status,
arg.ID,
)
return err