Add spec, plan, and tasks for 016-cancel-event feature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 18:50:46 +01:00
parent bf0f4ffb7f
commit 3908c89998
8 changed files with 674 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
# Tasks: Cancel Event
**Input**: Design documents from `/specs/016-cancel-event/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/patch-event-endpoint.yaml
**Tests**: Included — constitution mandates TDD (tests before implementation) and E2E for every frontend user story.
**Organization**: Tasks grouped by user story. Both stories are P1 but US2 depends on US1's backend work.
## 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)
- Include exact file paths in descriptions
---
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: OpenAPI spec, database migration, and domain model changes that both user stories depend on.
- [ ] T001 Update OpenAPI spec with PATCH endpoint on `/events/{eventToken}`, PatchEventRequest schema (`organizerToken`, `cancelled`, `cancellationReason`), and extend GetEventResponse with `cancelled`/`cancellationReason` fields in `backend/src/main/resources/openapi/api.yaml`
- [ ] T002 Add Liquibase changeset 004 adding `cancelled` (BOOLEAN NOT NULL DEFAULT FALSE) and `cancellation_reason` (VARCHAR 2000) columns to `events` table in `backend/src/main/resources/db/changelog/004-add-cancellation-columns.xml` and register it in `db.changelog-master.xml`
- [ ] T003 [P] Extend domain model `Event.java` with `cancelled`, `cancellationReason` fields and `cancel(String reason)` method (throws `EventAlreadyCancelledException`). Create `EventAlreadyCancelledException` in `backend/src/main/java/de/fete/application/service/` following the pattern of existing exceptions. Domain model: `backend/src/main/java/de/fete/domain/model/Event.java`
- [ ] T004 [P] Extend JPA entity `EventJpaEntity.java` with `cancelled` and `cancellation_reason` columns and update mapper in `backend/src/main/java/de/fete/adapter/out/persistence/EventJpaEntity.java`
- [ ] T005 Regenerate frontend TypeScript types from updated OpenAPI spec via `cd frontend && npm run generate-types` (or equivalent openapi-typescript command)
**Checkpoint**: Schema, migration, and domain model ready. Both user stories can now proceed.
---
## Phase 2: User Story 1 — Organizer Cancels an Event (Priority: P1) MVP
**Goal**: Organizer can cancel an event via a bottom sheet with optional reason. Cancellation is irreversible and persists on reload.
**Independent Test**: View event with organizer token, tap cancel, optionally enter reason, confirm. Event remains cancelled on reload.
### Tests for User Story 1
> **Write these tests FIRST — ensure they FAIL before implementation**
- [ ] T006 [P] [US1] Write unit test for `cancel()` domain method (happy path, already-cancelled throws) in `backend/src/test/java/de/fete/domain/model/EventTest.java`
- [ ] T007 [P] [US1] Write unit test for cancel use case in EventService (delegates to domain, saves, 403/404/409 cases) in `backend/src/test/java/de/fete/application/service/EventServiceCancelTest.java`
- [ ] T008 [P] [US1] Write integration test for `PATCH /events/{eventToken}` endpoint (204 success, 403 wrong token, 404 not found, 409 already cancelled) in `backend/src/test/java/de/fete/adapter/in/web/EventControllerCancelTest.java`
- [ ] T009 [P] [US1] Write E2E test: organizer opens cancel bottom sheet, enters reason, confirms — event shows as cancelled on reload in `frontend/e2e/cancel-event.spec.ts`
- [ ] T010 [P] [US1] Write E2E test: organizer cancels without reason — event shows as cancelled in `frontend/e2e/cancel-event.spec.ts`
- [ ] T011 [P] [US1] Write E2E test: cancel API fails — error displayed in bottom sheet, button re-enabled for retry in `frontend/e2e/cancel-event.spec.ts`
### Implementation for User Story 1
- [ ] T012 [US1] Create `CancelEventUseCase` interface (or add method to existing use case interface) in `backend/src/main/java/de/fete/domain/port/in/`
- [ ] T013 [US1] Implement cancel logic in `EventService.java` — load event, verify organizer token, call `event.cancel(reason)`, persist in `backend/src/main/java/de/fete/application/service/EventService.java`
- [ ] T014 [US1] Implement `cancelEvent` endpoint in `EventController.java` — PATCH handler, request body binding, error mapping (403/404/409) in `backend/src/main/java/de/fete/adapter/in/web/EventController.java`
- [ ] T015 [US1] Add cancel button (visible only when organizer token exists and event not cancelled — covers FR-012) and cancel bottom sheet (textarea with 2000 char limit + confirm button + inline error) to `frontend/src/views/EventDetailView.vue`
- [ ] T016 [US1] Wire cancel bottom sheet confirm action to `PATCH /events/{eventToken}` API call via openapi-fetch, handle success (reload event data) and error (show inline message, re-enable button) in `frontend/src/views/EventDetailView.vue`
**Checkpoint**: Organizer can cancel an event. All US1 acceptance scenarios pass.
---
## Phase 3: User Story 2 — Attendee Sees Cancelled Event (Priority: P1)
**Goal**: Visitors see a prominent red cancellation banner on cancelled events. RSVP buttons are hidden. All other event details remain visible.
**Independent Test**: View a cancelled event's detail page — banner visible (with reason if provided), RSVP buttons hidden, other details intact.
### Tests for User Story 2
> **Write these tests FIRST — ensure they FAIL before implementation**
- [ ] T017 [P] [US2] Write unit test for RSVP creation rejection on cancelled events (409 Conflict) in `backend/src/test/java/de/fete/application/service/RsvpServiceCancelledTest.java`
- [ ] T018 [P] [US2] Write integration test for `POST /events/{eventToken}/rsvps` returning 409 when event is cancelled in `backend/src/test/java/de/fete/adapter/in/web/RsvpControllerCancelledTest.java`
- [ ] T019 [P] [US2] Write E2E test: visitor sees red banner with cancellation reason on cancelled event in `frontend/e2e/cancelled-event-visitor.spec.ts`
- [ ] T020 [P] [US2] Write E2E test: visitor sees red banner without reason when no reason was provided in `frontend/e2e/cancelled-event-visitor.spec.ts`
- [ ] T021 [P] [US2] Write E2E test: RSVP buttons hidden on cancelled event, other details remain visible in `frontend/e2e/cancelled-event-visitor.spec.ts`
### Implementation for User Story 2
- [ ] T022 [US2] Add cancelled-event guard to RSVP creation — check `event.isCancelled()`, return 409 Conflict in `backend/src/main/java/de/fete/application/service/RsvpService.java`
- [ ] T023 [US2] Add cancellation banner component/section (red, prominent, includes reason if present, WCAG AA contrast) to `frontend/src/views/EventDetailView.vue`
- [ ] T024 [US2] Hide RSVP buttons (`RsvpBar` or equivalent) when `event.cancelled === true` in `frontend/src/views/EventDetailView.vue`
- [ ] ~~T025~~ Merged into T015 (cancel button v-if already handles FR-012)
**Checkpoint**: Both user stories fully functional. All acceptance scenarios pass.
---
## Phase 4: Polish & Cross-Cutting Concerns
**Purpose**: Validation, edge cases, and final cleanup.
- [ ] T026 Verify cancellationReason max length (2000 chars) is enforced at API level (OpenAPI `maxLength`), domain level in `Event.java`, and UI level (textarea maxlength/counter)
- [ ] T027 Run full backend test suite (`cd backend && ./mvnw verify`) and fix any failures
- [ ] T028 Run full frontend test suite (`cd frontend && npm run test:unit`) and fix any failures
- [ ] T029 Run E2E tests (`cd frontend && npx playwright test`) and fix any failures
- [ ] T030 Run backend checkstyle (`cd backend && ./mvnw checkstyle:check`) and fix violations
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies — start immediately
- **US1 (Phase 2)**: Depends on Setup completion
- **US2 (Phase 3)**: Depends on Setup completion; backend RSVP guard is independent of US1, but frontend banner/hiding can be built in parallel with US1
- **Polish (Phase 4)**: Depends on all user stories complete
### User Story Dependencies
- **US1 (P1)**: Can start after Phase 1 (Setup). No dependency on US2.
- **US2 (P1)**: Can start after Phase 1 (Setup). Backend RSVP guard is independent of US1. Frontend banner/hiding only needs the `cancelled`/`cancellationReason` fields in GetEventResponse (from Setup).
### Within Each User Story
- Tests MUST be written and FAIL before implementation
- Domain model before service layer
- Service layer before controller/endpoint
- Backend before frontend (API must exist before UI wires to it)
- Core implementation before error handling polish
### Parallel Opportunities
- T003 + T004 (domain model + JPA entity) can run in parallel
- All US1 test tasks (T006T011) can run in parallel
- All US2 test tasks (T017T021) can run in parallel
- US1 backend (T012T014) and US2 backend (T022) can run in parallel after Setup
- US1 frontend (T015T016) and US2 frontend (T023T025) can run in parallel if backend is ready
---
## Parallel Example: User Story 1
```bash
# Launch all US1 tests together (TDD - write first, expect failures):
Task: T006 "Unit test for cancel() domain method"
Task: T007 "Unit test for cancel use case in EventService"
Task: T008 "Integration test for PATCH /events/{eventToken}"
Task: T009 "E2E test: organizer cancels with reason"
Task: T010 "E2E test: organizer cancels without reason"
Task: T011 "E2E test: cancel API failure handling"
# Then implement sequentially:
Task: T012 "CancelEventUseCase interface"
Task: T013 "EventService cancel implementation"
Task: T014 "EventController cancelEvent endpoint"
Task: T015 "Cancel button + bottom sheet in EventDetailView"
Task: T016 "Wire cancel action to API"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup (OpenAPI, migration, domain model)
2. Complete Phase 2: US1 — Organizer cancels event
3. **STOP and VALIDATE**: All US1 acceptance scenarios pass
4. Deploy/demo if ready — cancellation works end-to-end
### Incremental Delivery
1. Setup → Shared infrastructure ready
2. US1 → Organizer can cancel → Test → Deploy (MVP!)
3. US2 → Attendees see cancellation + RSVP blocked → Test → Deploy
4. Polish → Full verification, checkstyle, edge cases