5.0 KiB
Research: Cancel Event
Feature Branch: 016-cancel-event | Date: 2026-03-12
Decision 1: API Endpoint Design
Decision: Use PATCH /events/{eventToken} with organizer token and cancellation fields in request body.
Rationale:
- PATCH is standard REST for partial resource updates — cancellation is a state change on the event resource.
- The event is not removed, so DELETE is not appropriate. The event remains visible with a cancellation banner.
- The organizer token is sent in the request body to keep it out of URL/query strings and server access logs.
- Request body:
{ "organizerToken": "uuid", "cancelled": true, "cancellationReason": "optional string" }. - Response:
204 No Contenton success. - Error responses:
404if event not found,403if organizer token is wrong,409if already cancelled. - Currently the only supported PATCH operation is cancellation. The endpoint validates that
cancelledistrueand rejects requests that attempt to set other fields.
Alternatives considered:
POST /events/{eventToken}/cancel— rejected because a dedicated sub-resource endpoint is RPC-style, not RESTful. PATCH on the resource itself is the standard approach.DELETE /events/{eventToken}— rejected because the event is not deleted, it remains visible with a cancellation banner.
Decision 2: Database Schema Extension
Decision: Add two columns to the events table: cancelled BOOLEAN NOT NULL DEFAULT FALSE and cancellation_reason VARCHAR(2000).
Rationale:
- Boolean flag is the simplest representation of the cancelled state.
- 2000 chars matches the existing description field limit — consistent and generous.
- DEFAULT FALSE ensures backward compatibility with existing rows.
- A Liquibase changeset (003) adds both columns.
Alternatives considered:
- Enum status field (
ACTIVE,CANCELLED) — rejected as over-engineering for a binary state with no other planned transitions. - Separate cancellation table — rejected as unnecessary complexity for two columns.
Decision 3: RSVP Blocking on Cancelled Events
Decision: The RSVP creation endpoint (POST /events/{eventToken}/rsvps) checks the event's cancelled flag and returns 409 Conflict if the event is cancelled.
Rationale:
- Server-side enforcement is required (FR-006) — frontend hiding the button is not sufficient.
- 409 Conflict is semantically correct: the request conflicts with the current state of the resource.
- Existing RSVPs are preserved (FR-007) — no cascade or cleanup needed.
Alternatives considered:
- 400 Bad Request — rejected because the request itself is well-formed; the conflict is with resource state.
- 422 Unprocessable Entity — rejected because the issue is not validation but state conflict.
Decision 4: Frontend Cancel Bottom Sheet
Decision: Reuse the existing BottomSheet.vue component. Add cancel-specific content (textarea + confirm button) directly in EventDetailView.vue, similar to how the RSVP form is embedded.
Rationale:
- The spec explicitly requires the bottom sheet pattern consistent with RSVP flow (FR-002).
BottomSheet.vueis already a generic, accessible, glassmorphism-styled container.- No need for a separate component — the cancel form is simple (textarea + button + error message).
- Error handling follows the same pattern as RSVP: inline error in the sheet, button re-enabled.
Alternatives considered:
- Separate
CancelBottomSheet.vuecomponent — rejected as unnecessary extraction for a simple form. - ConfirmDialog instead of BottomSheet — rejected because spec explicitly requires bottom sheet.
Decision 5: Organizer Token Authorization
Decision: The cancel endpoint receives the organizer token in the request body. The frontend retrieves it from localStorage via useEventStorage.getOrganizerToken().
Rationale:
- Consistent with how organizer identity works throughout the app — token-based, no auth system.
- The organizer token is already stored in localStorage when the event is created.
- Body parameter keeps the token out of URL/query strings and server access logs.
Alternatives considered:
- Authorization header — rejected because there's no auth system; the organizer token is not a session token.
- Query parameter — rejected to keep token out of server logs (same reason the attendee endpoint should eventually be migrated away from query params).
Decision 6: GetEventResponse Extension
Decision: Add cancelled: boolean and cancellationReason: string | null to the GetEventResponse schema.
Rationale:
- The frontend needs to know whether an event is cancelled to show the banner and hide RSVP buttons.
- Both fields are always returned (no separate endpoint needed).
cancelleddefaults tofalsefor existing events.
Alternatives considered:
- Separate endpoint for cancellation status — rejected as unnecessary network overhead.
- Only return cancellation info for cancelled events — rejected because the frontend needs the boolean regardless to decide UI state.