# Implementation Plan: RSVP to an Event **Branch**: `008-rsvp` | **Date**: 2026-03-06 | **Spec**: [spec.md](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) ```text 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) ```text 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.