Task 1.5 — Event-participation collections

Five collections + 10 relations + 5 composite unique constraints,
captured into snapshots/schema.yaml (now 105 KB, up from 53 KB).

Collections:
- events       — 11 fields incl. organization_id M2O, discipline enum
                  (rally / time-trial / regatta / trail-run / hike),
                  starts_at/ends_at required.
- classes      — 8 fields incl. event_id M2O, code unique within event.
- entries      — 11 fields incl. event_id/vehicle_id (nullable for foot
                  races) /class_id M2O, race_number, status enum with
                  8 lifecycle values, archive on `withdrawn`.
                  team_id deliberately omitted (Phase 2+).
- entry_crew   — junction with role enum
                  (pilot/co-pilot/navigator/mechanic/rider/runner/hiker).
- entry_devices — junction with optional assigned_user_id (panic button
                  body-wear); ON DELETE SET NULL on that field since
                  user removal shouldn't block the device record.

10 M2O relations wired, all ON DELETE RESTRICT except
entry_devices.assigned_user_id (SET NULL).

db-init/005_event_participation_unique_constraints.sql adds composite
UNIQUE on:
  events (organization_id, slug)
  classes (event_id, code)
  entries (event_id, race_number)
  entry_crew (entry_id, user_id)
  entry_devices (entry_id, device_id)

---

Destructive-apply incident (recovered):

First attempt at this task hit a real foot-gun. After creating the 5
collections via MCP, we ran `compose build && up -d`. The image rebuild
baked in the snapshot from task 1.4 (only 7 collections). Boot's
schema-apply step ran `directus schema apply --yes` against that stale
snapshot — saw the 5 new collections in the DB but not in the snapshot
— DELETED THEM, taking the constraints with them.

Recovery: re-created the 5 collections + 10 relations via MCP, ran the
ALTER TABLE statements directly via psql to restore the constraints,
ran schema:snapshot BEFORE any further restart so the YAML reflects
the live state. Documented the operator rule (never rebuild with
uncommitted schema changes) inline in the task spec and in the
directus wiki entity page (separate commit in trm/docs).

Phase 3 hardening on the radar: DIRECTUS_SCHEMA_APPLY_MODE env var
with auto/dry-run/skip modes so dev environments default to non-
destructive behavior.

ROADMAP marks 1.5 done. Phase 1 progress: 7/9 tasks complete (1.1–1.7);
1.8, 1.9 remain.
This commit is contained in:
2026-05-02 09:55:10 +02:00
parent 6f376a479f
commit 52524eb72d
4 changed files with 2246 additions and 3 deletions
@@ -0,0 +1,65 @@
-- 005_event_participation_unique_constraints.sql
-- Composite UNIQUE constraints on the event-participation collections.
--
-- Same rationale as 004: Directus's `is_unique` flag is single-column only;
-- composite uniqueness lives in db-init/ because the snapshot YAML format
-- does not capture multi-column unique constraints.
--
-- Owned by: task 1.5 (event-participation collections).
ALTER TABLE events
ADD CONSTRAINT events_org_slug_unique
UNIQUE (organization_id, slug);
ALTER TABLE classes
ADD CONSTRAINT classes_event_code_unique
UNIQUE (event_id, code);
ALTER TABLE entries
ADD CONSTRAINT entries_event_race_number_unique
UNIQUE (event_id, race_number);
ALTER TABLE entry_crew
ADD CONSTRAINT entry_crew_entry_user_unique
UNIQUE (entry_id, user_id);
ALTER TABLE entry_devices
ADD CONSTRAINT entry_devices_entry_device_unique
UNIQUE (entry_id, device_id);
-- -------------------------------------------------------------------------
-- Assertion block: verify all five constraints landed.
-- -------------------------------------------------------------------------
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'events_org_slug_unique'
) THEN
RAISE EXCEPTION 'events composite unique constraint (org, slug) missing';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'classes_event_code_unique'
) THEN
RAISE EXCEPTION 'classes composite unique constraint (event, code) missing';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'entries_event_race_number_unique'
) THEN
RAISE EXCEPTION 'entries composite unique constraint (event, race_number) missing';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'entry_crew_entry_user_unique'
) THEN
RAISE EXCEPTION 'entry_crew composite unique constraint (entry, user) missing';
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'entry_devices_entry_device_unique'
) THEN
RAISE EXCEPTION 'entry_devices composite unique constraint (entry, device) missing';
END IF;
END $$;