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:
82
specs/016-cancel-event/data-model.md
Normal file
82
specs/016-cancel-event/data-model.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 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
|
||||
<changeSet id="004-add-cancellation-columns" author="fete">
|
||||
<addColumn tableName="events">
|
||||
<column name="cancelled" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="cancellation_reason" type="VARCHAR(2000)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
```
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user