Files
fete/specs/016-cancel-event/tasks.md
2026-03-12 19:03:57 +01:00

10 KiB
Raw Blame History

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

# 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