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>
8.3 KiB
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.
- T001 Add
GET /events/{token}/attendeesendpoint,GetAttendeesResponse, andAttendeeschemas tobackend/src/main/resources/openapi/api.yamlpercontracts/api.md - T002 Regenerate backend Spring interfaces (
cd backend && ./mvnw compile) - 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.
- T004 [P] Create
GetAttendeesUseCaseinbound port interface inbackend/src/main/java/de/fete/domain/port/in/GetAttendeesUseCase.javawith methodList<String> getAttendeeNames(UUID eventToken, UUID organizerToken) - T005 [P] Add
List<Rsvp> findByEventId(Long eventId)method tobackend/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.
- T006 [P] [US1] Write unit tests for
RsvpService.getAttendeeNames(valid token, invalid token, event not found, no RSVPs) inbackend/src/test/java/de/fete/application/service/RsvpServiceTest.java - T007 [P] [US1] Write integration tests for
GET /events/{token}/attendees(200 with attendees, 200 empty, 403 invalid token, 404 event not found) inbackend/src/test/java/de/fete/adapter/in/web/EventControllerIntegrationTest.java - T008 [P] [US1] Write unit tests for
AttendeeList.vue(renders attendee names, empty state message, loading state) infrontend/src/components/__tests__/AttendeeList.spec.ts - T009 [P] [US1] Write unit tests for organizer-conditional rendering in
EventDetailView.vue(shows list for organizer, hides for visitor) infrontend/src/views/__tests__/EventDetailView.spec.ts - 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
- T011 [P] [US1] Add
findAllByEventIdOrderByIdAscquery method tobackend/src/main/java/de/fete/adapter/out/persistence/RsvpJpaRepository.java - T012 [P] [US1] Implement
findByEventIdinbackend/src/main/java/de/fete/adapter/out/persistence/RsvpPersistenceAdapter.java - T013 [US1] Implement
GetAttendeesUseCaseinbackend/src/main/java/de/fete/application/service/RsvpService.java— look up event by token, verify organizer token, return attendee names ordered by ID - T014 [US1] Add
getAttendeesendpoint handler tobackend/src/main/java/de/fete/adapter/in/web/EventController.java— map toGetAttendeesUseCase, return 200/403/404
Frontend Implementation for User Story 1
- T015 [US1] Create
AttendeeList.vuecomponent infrontend/src/components/AttendeeList.vue— accepts attendee names array as prop, renders semantic<ul>/<li>list, shows empty state message when no attendees - T016 [US1] Integrate
AttendeeList.vueintofrontend/src/views/EventDetailView.vue— fetchGET /events/{token}/attendeeswith 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.
- T017 [P] [US2] Write unit tests for count label in
AttendeeList.vue(plural "5 Attendees", singular "1 Attendee", zero "0 Attendees") infrontend/src/components/__tests__/AttendeeList.spec.ts
Implementation for User Story 2
- T018 [US2] Add count heading to
AttendeeList.vueinfrontend/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.
- T019 Run full backend verification (
cd backend && ./mvnw verify) — checkstyle, all tests green - T020 Run full frontend build and tests (
cd frontend && npm run build && npm run test:unit) - T021 Run E2E tests (
cd frontend && npx playwright test e2e/view-attendee-list.spec.ts) - T022 Verify WCAG AA contrast and semantic HTML (attendee list uses
<ul>/<li>, section has heading for screen readers) - 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.vuecomponent (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
# 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)
- Complete Phase 1: Setup (OpenAPI + type generation)
- Complete Phase 2: Foundational (domain ports)
- Complete Phase 3: User Story 1 (backend + frontend)
- STOP and VALIDATE: Test independently — organizer sees names, visitor sees count only
- Deploy/demo if ready
Incremental Delivery
- Setup + Foundational → API contract and ports ready
- Add User Story 1 → Test independently → Deploy (MVP!)
- Add User Story 2 → Test independently → Deploy (count label enhancement)
- Polish phase → Full verification, accessibility, edge cases