Add PATCH /events/{eventToken} endpoint for organizers to cancel events,
cancellation banner for visitors, and RSVP rejection on cancelled events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.2 KiB
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}(organizerToken as query param), PatchEventRequest schema (cancelled,cancellationReason), and extend GetEventResponse withcancelled/cancellationReasonfields inbackend/src/main/resources/openapi/api.yaml - T002 Add Liquibase changeset 004 adding
cancelled(BOOLEAN NOT NULL DEFAULT FALSE) andcancellation_reason(VARCHAR 2000) columns toeventstable inbackend/src/main/resources/db/changelog/004-add-cancellation-columns.xmland register it indb.changelog-master.xml - T003 [P] Extend domain model
Event.javawithcancelled,cancellationReasonfields andcancel(String reason)method (throwsEventAlreadyCancelledException). CreateEventAlreadyCancelledExceptioninbackend/src/main/java/de/fete/domain/model/. Domain model:backend/src/main/java/de/fete/domain/model/Event.java - T004 [P] Extend JPA entity
EventJpaEntity.javawithcancelledandcancellation_reasoncolumns and update mapper inbackend/src/main/java/de/fete/adapter/out/persistence/EventPersistenceAdapter.java - T005 Regenerate frontend TypeScript types from updated OpenAPI spec via
cd frontend && npm run generate:api
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
T006Removed (EventTest.java unnecessary — cancel() tested via service/integration tests)- 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 tests for
PATCH /events/{eventToken}endpoint (204 success, 403 wrong token, 404 not found, 409 already cancelled) inbackend/src/test/java/de/fete/adapter/in/web/EventControllerIntegrationTest.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
UpdateEventUseCaseinterface inbackend/src/main/java/de/fete/domain/port/in/UpdateEventUseCase.java - T013 [US1] Implement cancel logic in
EventService.java— load event, verify organizer token, callevent.cancel(reason), persist inbackend/src/main/java/de/fete/application/service/EventService.java - T014 [US1] Implement
patchEventendpoint inEventController.java— PATCH handler, query param organizerToken, request body binding, error mapping (403/404/409) inbackend/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) infrontend/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
T017Removed (RsvpServiceCancelledTest unnecessary — covered by integration test)- T018 [P] [US2] Write integration test for
POST /events/{eventToken}/rsvpsreturning 409 when event is cancelled inbackend/src/test/java/de/fete/adapter/in/web/EventControllerIntegrationTest.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 inbackend/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 (
RsvpBaror equivalent) whenevent.cancelled === trueinfrontend/src/views/EventDetailView.vue T025Merged 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 inEvent.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