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:
93
specs/008-rsvp/data-model.md
Normal file
93
specs/008-rsvp/data-model.md
Normal 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);
|
||||
```
|
||||
Reference in New Issue
Block a user