Spec, research decisions, implementation plan, data model, API contract, and task breakdown for the RSVP feature. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.4 KiB
4.4 KiB
Data Model: RSVP to an Event (008)
Date: 2026-03-06
Entities
Rsvp (NEW)
| Field | Type | Required | Constraints | Notes |
|---|---|---|---|---|
| id | Long | yes | BIGSERIAL, PK | Internal only, never exposed |
| rsvpToken | RsvpToken | yes | UNIQUE, NOT NULL | Server-generated UUID, returned to client |
| eventId | Long | yes | FK -> events.id, NOT NULL | Which event this RSVP belongs to |
| name | String | yes | 1-100 chars, NOT NULL | Guest's display name |
Notes:
- No
attendingboolean — existence of an entry implies attendance (per spec). - No
createdAt— not required by the spec. Can be added later if needed (e.g. for guest list sorting in 009). - Duplicates from different devices or cleared localStorage are accepted (privacy trade-off).
Token Value Objects (NEW)
| Record | Field | Type | Notes |
|---|---|---|---|
EventToken |
value | UUID | Immutable, non-null. Java record wrapping UUID |
OrganizerToken |
value | UUID | Immutable, non-null. Java record wrapping UUID |
RsvpToken |
value | UUID | Immutable, non-null. Java record wrapping UUID |
Purpose: Type-safe wrappers preventing mix-ups between the three token types at compile time. All generated server-side via UUID.randomUUID(). JPA entities continue to use raw UUID columns — mapping happens in the persistence adapters.
Event (MODIFIED — token fields change type)
The Event domain model's eventToken and organizerToken fields change from raw UUID to their typed record wrappers. No database schema change — the JPA entity keeps raw UUID columns.
| Field | Old Type | New Type |
|---|---|---|
| eventToken | UUID | EventToken |
| organizerToken | UUID | OrganizerToken |
The attendeeCount was already added to the API response in 007-view-event — it now gets populated from a count query instead of returning 0.
StoredEvent (frontend localStorage — modified)
| Field | Type | Required | Notes |
|---|---|---|---|
| eventToken | string | yes | Existing |
| organizerToken | string | no | Existing (organizer flow) |
| title | string | yes | Existing |
| dateTime | string | yes | Existing |
| expiryDate | string | yes | Existing |
| rsvpToken | string | no | NEW — set after RSVP submission |
| rsvpName | string | no | NEW — guest's submitted name |
Validation Rules
name: required, 1-100 characters, trimmed. Blank or whitespace-only is rejected.rsvpToken: server-generated, never from client input on create.eventId: must reference an existing, non-expired event.
Relationships
Event 1 <---- * Rsvp
| |
eventToken rsvpToken (unique)
(public) (returned to client)
Type Mapping (full stack)
| Concept | Java | PostgreSQL | OpenAPI | TypeScript |
|---|---|---|---|---|
| RSVP ID | Long |
bigserial |
N/A (not exposed) | N/A |
| RSVP Token | RsvpToken |
uuid |
string uuid |
string |
| Event FK | Long |
bigint |
N/A (path param) | N/A |
| Guest name | String |
varchar(100) |
string |
string |
| Attendee cnt | long |
count(*) |
integer |
number |
Database Migration
New Liquibase changeset 003-create-rsvps-table.xml:
CREATE TABLE rsvps (
id BIGSERIAL PRIMARY KEY,
rsvp_token UUID NOT NULL UNIQUE,
event_id BIGINT NOT NULL REFERENCES events(id),
name VARCHAR(100) NOT NULL
);
CREATE INDEX idx_rsvps_event_id ON rsvps(event_id);
CREATE INDEX idx_rsvps_rsvp_token ON rsvps(rsvp_token);