Add organizer-only attendee list to event detail view (011)
All checks were successful
CI / backend-test (push) Successful in 59s
CI / frontend-test (push) Successful in 23s
CI / frontend-e2e (push) Successful in 1m11s
CI / build-and-publish (push) Has been skipped

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:
2026-03-08 18:34:27 +01:00
parent d7ed28e036
commit 763811fce6
24 changed files with 1307 additions and 3 deletions

View File

@@ -0,0 +1,167 @@
# Tasks: View Attendee List
**Input**: Design documents from `/specs/011-view-attendee-list/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/api.md
**Tests**: Included — TDD enforced per constitution principle II. E2E tests mandatory per plan.
**Organization**: Tasks grouped by user story for independent implementation and testing.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2)
- Exact file paths included in all descriptions
---
## Phase 1: Setup (API Contract)
**Purpose**: Define the API contract in OpenAPI and generate types for both backend and frontend.
- [x] T001 Add `GET /events/{token}/attendees` endpoint, `GetAttendeesResponse`, and `Attendee` schemas to `backend/src/main/resources/openapi/api.yaml` per `contracts/api.md`
- [x] T002 Regenerate backend Spring interfaces (`cd backend && ./mvnw compile`)
- [x] T003 Regenerate frontend TypeScript types (`cd frontend && npm run generate:api`)
**Checkpoint**: OpenAPI spec updated, generated types available in both backend and frontend.
---
## Phase 2: Foundational (Backend Domain Ports)
**Purpose**: Create the inbound port and extend the outbound port that all backend implementation depends on.
- [x] T004 [P] Create `GetAttendeesUseCase` inbound port interface in `backend/src/main/java/de/fete/domain/port/in/GetAttendeesUseCase.java` with method `List<String> getAttendeeNames(UUID eventToken, UUID organizerToken)`
- [x] T005 [P] Add `List<Rsvp> findByEventId(Long eventId)` method to `backend/src/main/java/de/fete/domain/port/out/RsvpRepository.java`
**Checkpoint**: Domain ports defined — service and adapter implementation can begin.
---
## Phase 3: User Story 1 — View Attendee List as Organizer (Priority: P1) 🎯 MVP
**Goal**: Organizer sees a list of attendee names on the event detail page. Non-organizers see only the count (existing behavior).
**Independent Test**: Create an event, submit RSVPs, then view the detail page with the organizer token. Attendee names should be listed.
### Tests for User Story 1 ⚠️
> **Write these tests FIRST — ensure they FAIL before implementation.**
- [x] T006 [P] [US1] Write unit tests for `RsvpService.getAttendeeNames` (valid token, invalid token, event not found, no RSVPs) in `backend/src/test/java/de/fete/application/service/RsvpServiceTest.java`
- [x] T007 [P] [US1] Write integration tests for `GET /events/{token}/attendees` (200 with attendees, 200 empty, 403 invalid token, 404 event not found) in `backend/src/test/java/de/fete/adapter/in/web/EventControllerIntegrationTest.java`
- [x] T008 [P] [US1] Write unit tests for `AttendeeList.vue` (renders attendee names, empty state message, loading state) in `frontend/src/components/__tests__/AttendeeList.spec.ts`
- [x] T009 [P] [US1] Write unit tests for organizer-conditional rendering in `EventDetailView.vue` (shows list for organizer, hides for visitor) in `frontend/src/views/__tests__/EventDetailView.spec.ts`
- [x] T010 [P] [US1] Write E2E test: organizer sees attendee names, visitor sees count only, in `frontend/e2e/view-attendee-list.spec.ts`
### Backend Implementation for User Story 1
- [x] T011 [P] [US1] Add `findAllByEventIdOrderByIdAsc` query method to `backend/src/main/java/de/fete/adapter/out/persistence/RsvpJpaRepository.java`
- [x] T012 [P] [US1] Implement `findByEventId` in `backend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java`
- [x] T013 [US1] Implement `GetAttendeesUseCase` in `backend/src/main/java/de/fete/application/service/RsvpService.java` — look up event by token, verify organizer token, return attendee names ordered by ID
- [x] T014 [US1] Add `getAttendees` endpoint handler to `backend/src/main/java/de/fete/adapter/in/web/EventController.java` — map to `GetAttendeesUseCase`, return 200/403/404
### Frontend Implementation for User Story 1
- [x] T015 [US1] Create `AttendeeList.vue` component in `frontend/src/components/AttendeeList.vue` — accepts attendee names array as prop, renders semantic `<ul>/<li>` list, shows empty state message when no attendees
- [x] T016 [US1] Integrate `AttendeeList.vue` into `frontend/src/views/EventDetailView.vue` — fetch `GET /events/{token}/attendees` with organizer token from localStorage, render below attendee count (before RSVP form), silently degrade on 403
**Checkpoint**: User Story 1 fully functional. Organizer sees attendee names; visitor sees count only. All tests pass.
---
## Phase 4: User Story 2 — Attendee Count Label (Priority: P2)
**Goal**: The attendee list section shows a count label ("5 Attendees" / "1 Attendee") alongside the names.
**Independent Test**: Verify the count label above the list matches the number of entries, and uses singular/plural correctly.
### Tests for User Story 2 ⚠️
> **Write these tests FIRST — ensure they FAIL before implementation.**
- [x] T017 [P] [US2] Write unit tests for count label in `AttendeeList.vue` (plural "5 Attendees", singular "1 Attendee", zero "0 Attendees") in `frontend/src/components/__tests__/AttendeeList.spec.ts`
### Implementation for User Story 2
- [x] T018 [US2] Add count heading to `AttendeeList.vue` in `frontend/src/components/AttendeeList.vue` — render `<h3>` with singular/plural label based on attendee array length
**Checkpoint**: User Story 2 complete. Count label renders correctly with singular/plural form. All tests pass.
---
## Phase 5: Polish & Cross-Cutting Concerns
**Purpose**: Verification across both stories, accessibility, edge cases.
- [x] T019 Run full backend verification (`cd backend && ./mvnw verify`) — checkstyle, all tests green
- [x] T020 Run full frontend build and tests (`cd frontend && npm run build && npm run test:unit`)
- [x] T021 Run E2E tests (`cd frontend && npx playwright test e2e/view-attendee-list.spec.ts`)
- [x] T022 Verify WCAG AA contrast and semantic HTML (attendee list uses `<ul>/<li>`, section has heading for screen readers)
- [x] T023 Verify edge cases: long names truncated visually, special characters escaped, large attendee list scrollable
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — start immediately
- **Foundational (Phase 2)**: Depends on T002 (generated backend interfaces)
- **User Story 1 (Phase 3)**: Depends on Phase 2 completion
- **User Story 2 (Phase 4)**: Depends on T015 (AttendeeList component exists)
- **Polish (Phase 5)**: Depends on Phase 3 + Phase 4 completion
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Phase 2 — no dependencies on other stories
- **User Story 2 (P2)**: Depends on US1's `AttendeeList.vue` component (T015) existing
### Within Each User Story
- Tests MUST be written and FAIL before implementation
- Ports/models before services
- Services before endpoints/controllers
- Backend before frontend (API must exist for frontend integration)
### Parallel Opportunities
- T004 + T005 can run in parallel (different files)
- T006 + T007 + T008 + T009 + T010 can all run in parallel (different test files)
- T011 + T012 can run in parallel (different persistence files)
---
## Parallel Example: User Story 1
```bash
# Launch all tests in parallel (TDD — write first):
Task: T006 "Unit tests for RsvpService.getAttendeeNames"
Task: T007 "Integration tests for GET /events/{token}/attendees"
Task: T008 "Unit tests for AttendeeList.vue"
Task: T009 "Unit tests for EventDetailView.vue organizer rendering"
Task: T010 "E2E test for attendee list"
# Launch persistence layer in parallel:
Task: T011 "Add findAllByEventIdOrderByIdAsc to RsvpJpaRepository"
Task: T012 "Implement findByEventId in RsvpPersistenceAdapter"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup (OpenAPI + type generation)
2. Complete Phase 2: Foundational (domain ports)
3. Complete Phase 3: User Story 1 (backend + frontend)
4. **STOP and VALIDATE**: Test independently — organizer sees names, visitor sees count only
5. Deploy/demo if ready
### Incremental Delivery
1. Setup + Foundational → API contract and ports ready
2. Add User Story 1 → Test independently → Deploy (MVP!)
3. Add User Story 2 → Test independently → Deploy (count label enhancement)
4. Polish phase → Full verification, accessibility, edge cases