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:
167
specs/011-view-attendee-list/tasks.md
Normal file
167
specs/011-view-attendee-list/tasks.md
Normal 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
|
||||
Reference in New Issue
Block a user