Add 008-rsvp feature spec and design artifacts

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>
This commit is contained in:
2026-03-08 11:48:00 +01:00
parent 9a78ebd9b0
commit 4828d06aba
8 changed files with 731 additions and 35 deletions

View File

@@ -0,0 +1,93 @@
# 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 `attending` boolean — 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`:
```sql
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);
```