# Data Model: Cancel Event **Feature Branch**: `016-cancel-event` | **Date**: 2026-03-12 ## Entity Changes ### Event (extended) Two new fields added to the existing Event entity: | Field | Type | Constraints | Description | |--------------------|----------------|------------------------------|--------------------------------------------------| | cancelled | boolean | NOT NULL, DEFAULT false | Whether the event has been cancelled | | cancellationReason | String (2000) | Nullable | Optional reason provided by organizer | ### State Transition ``` ACTIVE ──cancel()──► CANCELLED ``` - One-way transition only. No path from CANCELLED back to ACTIVE. - `cancel()` sets `cancelled = true` and optionally sets `cancellationReason`. - Once cancelled, the event remains visible but RSVP creation is blocked. ### Validation Rules - `cancellationReason` max length: 2000 characters (matches description field). - `cancellationReason` is plain text only (no HTML/markdown). - `cancelled` can only transition from `false` to `true`, never back. - Existing RSVPs are preserved when an event is cancelled (no cascade). ## Database Migration (Liquibase Changeset 004) ```xml ``` ## Domain Model Impact ### Event.java (domain) Add fields: ```java private boolean cancelled; private String cancellationReason; ``` Add method: ```java public void cancel(String reason) { if (this.cancelled) { throw new EventAlreadyCancelledException(); } this.cancelled = true; this.cancellationReason = reason; } ``` ### EventJpaEntity.java (persistence) Add columns: ```java @Column(name = "cancelled", nullable = false) private boolean cancelled; @Column(name = "cancellation_reason", length = 2000) private String cancellationReason; ``` ## RSVP Impact - `POST /events/{eventToken}/rsvps` must check `event.isCancelled()` before accepting. - If cancelled → return `409 Conflict`. - Existing RSVPs remain untouched — no delete, no status change.