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>
7.5 KiB
Implementation Plan: RSVP to an Event
Branch: 008-rsvp | Date: 2026-03-06 | Spec: spec.md
Input: Feature specification from /specs/008-rsvp/spec.md
Summary
Add RSVP functionality to the event detail page. Backend: new POST /api/events/{eventToken}/rsvps endpoint that persists an RSVP (guest name) and returns an rsvpToken. Populates the existing attendeeCount field with real data from a count query. Rejects RSVPs on expired events (409). Frontend: fullscreen event presentation with sticky bottom bar (RSVP CTA or status), bottom sheet with RSVP form (name + submit). localStorage stores rsvpToken and name per event. No account required.
Technical Context
Language/Version: Java 25 (backend), TypeScript 5.9 (frontend) Primary Dependencies: Spring Boot 3.5.x, Vue 3, Vue Router 5, openapi-fetch, openapi-typescript Storage: PostgreSQL (JPA via Spring Data, Liquibase migrations) Testing: JUnit (backend), Vitest (frontend unit), Playwright + MSW (frontend E2E) Target Platform: Self-hosted web application (Docker) Project Type: Web service + SPA Performance Goals: N/A (single-user scale, self-hosted) Constraints: No external resources (CDNs, fonts, tracking), WCAG AA, privacy-first, no PII logging Scale/Scope: New RSVP domain (model + service + controller + persistence), new frontend components (bottom sheet, sticky bar), modified event detail view
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| I. Privacy by Design | PASS | No PII logged. Only guest-entered name stored. No IP logging. No tracking. Attendee names not exposed publicly (count only). Unprotected endpoint is a conscious privacy trade-off per spec. |
| II. Test-Driven Methodology | PASS | TDD enforced: backend unit + integration tests, frontend unit tests, E2E tests. Tests written before implementation. |
| III. API-First Development | PASS | OpenAPI spec updated first. New endpoint + schemas with example: fields. Types generated before implementation. |
| IV. Simplicity & Quality | PASS | Minimal scope: one POST endpoint, one domain entity, one bottom sheet component. No CAPTCHA, no rate limiting, no edit/withdraw (deferred). Cancelled event guard deferred to US-18. |
| V. Dependency Discipline | PASS | No new dependencies. Bottom sheet is CSS + Vue (~50 lines). No UI library. |
| VI. Accessibility | PASS | Bottom sheet uses dialog role + aria-modal. Focus trap. ESC to close. Keyboard navigable. WCAG AA contrast via design system. |
Post-Phase-1 re-check: All gates still pass. Three token value objects (EventToken, OrganizerToken, RsvpToken) introduced uniformly — justified by spec requirement for type-safe tokens. Refactoring existing Event model to use typed tokens is a mechanical change well-covered by existing tests.
Project Structure
Documentation (this feature)
specs/008-rsvp/
├── plan.md # This file
├── spec.md # Feature specification
├── research.md # Phase 0: research decisions (R-1 through R-8)
├── data-model.md # Phase 1: Rsvp entity, RsvpToken value object
├── quickstart.md # Phase 1: implementation overview
├── contracts/
│ └── create-rsvp.yaml # Phase 1: POST endpoint contract
└── tasks.md # Phase 2: implementation tasks (via /speckit.tasks)
Source Code (repository root)
backend/
├── src/main/java/de/fete/
│ ├── domain/
│ │ ├── model/
│ │ │ ├── Event.java # MODIFIED: UUID → EventToken/OrganizerToken
│ │ │ ├── EventToken.java # NEW: typed token record
│ │ │ ├── OrganizerToken.java # NEW: typed token record
│ │ │ ├── Rsvp.java # NEW: RSVP domain entity
│ │ │ └── RsvpToken.java # NEW: typed token record
│ │ └── port/
│ │ ├── in/CreateRsvpUseCase.java # NEW: inbound port
│ │ └── out/RsvpRepository.java # NEW: outbound port
│ ├── application/service/
│ │ ├── EventService.java # MODIFIED: use typed tokens
│ │ └── RsvpService.java # NEW: RSVP business logic
│ ├── adapter/
│ │ ├── in/web/
│ │ │ ├── EventController.java # MODIFIED: typed tokens + attendee count + createRsvp()
│ │ │ └── GlobalExceptionHandler.java # MODIFIED: handle EventExpiredException
│ │ └── out/persistence/
│ │ ├── EventPersistenceAdapter.java # MODIFIED: map typed tokens
│ │ ├── RsvpJpaEntity.java # NEW: JPA entity
│ │ ├── RsvpJpaRepository.java # NEW: Spring Data interface
│ │ └── RsvpPersistenceAdapter.java # NEW: port implementation
├── src/main/resources/
│ ├── openapi/api.yaml # MODIFIED: add RSVP endpoint + schemas
│ └── db/changelog/
│ ├── db.changelog-master.xml # MODIFIED: include 003
│ └── 003-create-rsvps-table.xml # NEW: rsvps table
└── src/test/java/de/fete/
├── application/service/
│ ├── EventServiceTest.java # MODIFIED: use typed tokens
│ └── RsvpServiceTest.java # NEW: unit tests
└── adapter/in/web/
└── EventControllerIntegrationTest.java # MODIFIED: typed tokens + RSVP integration tests
frontend/
├── src/
│ ├── api/schema.d.ts # REGENERATED from OpenAPI
│ ├── components/
│ │ ├── BottomSheet.vue # NEW: reusable bottom sheet
│ │ └── RsvpBar.vue # NEW: sticky bottom bar
│ ├── views/EventDetailView.vue # MODIFIED: integrate RSVP bar + sheet
│ ├── composables/useEventStorage.ts # MODIFIED: add rsvpToken/rsvpName
│ └── assets/main.css # MODIFIED: bottom sheet + bar styles
├── src/views/__tests__/EventDetailView.spec.ts # MODIFIED: RSVP integration tests
├── src/components/__tests__/
│ ├── BottomSheet.spec.ts # NEW: unit tests
│ └── RsvpBar.spec.ts # NEW: unit tests
├── src/composables/__tests__/useEventStorage.spec.ts # MODIFIED: test new fields
└── e2e/
└── event-rsvp.spec.ts # NEW: E2E tests
Structure Decision: Extends the existing web application structure (backend + frontend). Adds a new RSVP domain following the same hexagonal architecture pattern established in 006-create-event and 007-view-event. Cross-cutting refactoring introduces typed token value objects (EventToken, OrganizerToken, RsvpToken) across all layers. Two new frontend components (BottomSheet, RsvpBar) are the first entries in src/components/ — justified because they're reusable UI primitives, not view-specific markup.
Complexity Tracking
No constitution violations. No entries needed.