Add organizer-only attendee list to event detail view (011)
New GET /events/{token}/attendees endpoint returns attendee names when
a valid organizer token is provided (403 otherwise). The frontend
conditionally renders the list below the attendee count for organizers,
silently degrading for visitors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
68
specs/011-view-attendee-list/research.md
Normal file
68
specs/011-view-attendee-list/research.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Research: View Attendee List (011)
|
||||
|
||||
**Date**: 2026-03-08 | **Status**: Complete
|
||||
|
||||
## 1. Organizer Token Verification Pattern
|
||||
|
||||
**Decision**: Query parameter `?organizerToken=<uuid>` on the new endpoint.
|
||||
|
||||
**Rationale**: The project uses token-based access control without persistent sessions. The organizer token is stored in localStorage on the client. Passing it as a query parameter is the simplest approach that fits the existing architecture. The `GET /events/{token}` endpoint already uses path-based token lookup; adding a query parameter for the organizer token keeps the two concerns separate.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Authorization header: More RESTful, but adds complexity without benefit — no auth framework in place, and query params are simpler for this single use case.
|
||||
- Embed attendees in existing `GET /events/{token}` response: Rejected per spec clarification — separate endpoint keeps concerns clean and avoids exposing attendee data in the public response.
|
||||
|
||||
## 2. Endpoint Design
|
||||
|
||||
**Decision**: `GET /events/{token}/attendees?organizerToken=<uuid>` returns `{ attendees: [{ name: string }] }`.
|
||||
|
||||
**Rationale**:
|
||||
- Nested under `/events/{token}` — resource hierarchy is clear.
|
||||
- Returns an object with an `attendees` array (not a raw array) — allows future extension (e.g., adding metadata) without breaking the contract.
|
||||
- Each attendee object contains only `name` — minimal data exposure per Privacy by Design.
|
||||
- HTTP 403 for invalid/missing organizer token (not 401 — no authentication scheme exists).
|
||||
- HTTP 404 if the event token is invalid (consistent with existing `GET /events/{token}`).
|
||||
|
||||
**Alternatives considered**:
|
||||
- Return `{ attendees: [...], count: N }`: Rejected — count is derivable from array length, and already available on the existing event detail endpoint. Avoids redundancy.
|
||||
- Include RSVP timestamp: Rejected — spec says chronological order but doesn't require displaying timestamps. Order is implicit in array position.
|
||||
|
||||
## 3. Backend Implementation Approach
|
||||
|
||||
**Decision**: New `GetAttendeesUseCase` inbound port, implemented by `RsvpService`. New `findByEventId` method on `RsvpRepository` outbound port.
|
||||
|
||||
**Rationale**: Follows the established hexagonal architecture pattern exactly. Each use case gets its own inbound port interface. The persistence layer already has `RsvpJpaRepository` with `countByEventId`; adding `findAllByEventIdOrderByIdAsc` is a natural extension (ID order = chronological insertion order).
|
||||
|
||||
**Alternatives considered**:
|
||||
- Add to `GetEventUseCase`: Rejected — violates single responsibility. The event detail endpoint is public; attendee retrieval is organizer-only.
|
||||
- Direct repository call in controller: Rejected — violates hexagonal architecture.
|
||||
|
||||
## 4. Frontend Integration Approach
|
||||
|
||||
**Decision**: New `AttendeeList.vue` component rendered conditionally in `EventDetailView.vue` when the viewer is the organizer. Fetches attendees via separate API call after event loads.
|
||||
|
||||
**Rationale**:
|
||||
- Separate component keeps EventDetailView manageable (it's already ~300 lines).
|
||||
- Separate API call (not bundled with event fetch) — the attendee list is organizer-only; non-organizers never trigger the request.
|
||||
- Component placed below attendee count, before RSVP form — matches spec FR-004.
|
||||
- Empty state handled within the component (FR-005).
|
||||
|
||||
**Alternatives considered**:
|
||||
- Inline in EventDetailView without separate component: Rejected — view is already complex. A dedicated component improves readability and testability.
|
||||
- Fetch attendees in the same call as event details: Not possible — separate endpoint by design.
|
||||
|
||||
## 5. Error Handling
|
||||
|
||||
**Decision**: Frontend silently degrades on 403 (does not render attendee list). No error toast or message shown.
|
||||
|
||||
**Rationale**: Per FR-007, the frontend "degrades gracefully by not rendering the list." If the organizer token is invalid (e.g., localStorage cleared on another device), the user sees the same view as a regular visitor. This is intentional — no confusing error states for edge cases that self-resolve.
|
||||
|
||||
**Alternatives considered**:
|
||||
- Show error message on 403: Rejected — would confuse users who aren't expecting organizer features.
|
||||
- Retry with different token: Not applicable — only one token per event in localStorage.
|
||||
|
||||
## 6. Accessibility Considerations
|
||||
|
||||
**Decision**: Attendee list rendered as semantic `<ul>` with `<li>` items. Section has a heading for screen readers. Count label uses singular/plural form.
|
||||
|
||||
**Rationale**: Constitution VI requires WCAG AA compliance, semantic HTML, and keyboard navigation. A list of names is naturally a `<ul>`. The heading provides structure for screen reader navigation.
|
||||
Reference in New Issue
Block a user