# Task 1.5 — Event-participation collections **Phase:** 1 — Slice 1 schema + deploy pipeline **Status:** ⬜ Not started **Depends on:** 1.4 **Wiki refs:** `docs/wiki/synthesis/directus-schema-draft.md` (Event-level participation section), `docs/wiki/sources/rally-albania-regulations-2025.md` (§2.2–§2.5 for class taxonomy reference) ## Goal Create the per-event participation collections in the Directus admin UI: `events`, `classes`, `entries`, `entry_crew`, `entry_devices`. These are scoped to a single event and form the unit of timing. ## Deliverables Create the following collections via the admin UI. Field shapes per [[directus-schema-draft]]. ### `events` | Field | Type | Notes | |---|---|---| | `id` * | UUID | | | `organization_id` * | M2O → organizations | event lives in exactly one org | | `name` * | string | "Rally Albania 2026" | | `slug` * | string | unique within an org | | `discipline` * | string (dropdown) | enum: `rally`, `time-trial`, `regatta`, `trail-run`, `hike` — drives validation | | `starts_at` * | timestamp | event window begin | | `ends_at` * | timestamp | event window end | | `regulation_doc_url` | string | external URL to the rulebook PDF/page (e.g. `wiki/sources/rally-albania-regulations-2025.md`) | | `notes` | text | | Unique constraint: `(organization_id, slug)`. ### `classes` | Field | Type | Notes | |---|---|---| | `id` * | UUID | | | `event_id` * | M2O → events | classes are per-event | | `code` * | string | "M-1", "C-2", "S-1", … | | `name` * | string | human-readable | | `description` | text | eligibility rules in plain text | | `sort_order` | integer | for display ordering | Unique constraint: `(event_id, code)`. ### `entries` The unit of timing. One row per (vehicle or solo participant) registered for an event. | Field | Type | Notes | |---|---|---| | `id` * | UUID | | | `event_id` * | M2O → events | | | `vehicle_id` | M2O → vehicles | nullable — null for foot races (trail-run, hike) | | `team_id` | M2O → teams | nullable — for now, no `teams` collection in Phase 1, leave the field nullable and unwired (`teams` collection is Phase 2 territory if needed; per the schema draft, teams are an org-level catalog item) | | `class_id` * | M2O → classes | required: every entry has a class | | `race_number` * | integer | per Rally Albania §5: 1–199 moto, 2xx quad, 3xx car, 4xx SSV | | `status` * | string (dropdown) | enum: `registered`, `confirmed`, `started`, `finished`, `dnf`, `dns`, `dq`, `withdrawn` | | `registered_at` | timestamp | default `now()` | | `notes` | text | | Unique constraint: `(event_id, race_number)` — no two entries share a race number in the same event. > **Status enum semantics** (from the schema draft): > - `registered` — paid, not yet confirmed at scrutineering > - `confirmed` — passed scrutineering, eligible to start > - `started` — has begun the first stage > - `finished` — completed all stages within MTA > - `dnf` — did not finish (started but couldn't complete) > - `dns` — did not start (confirmed but absent at start) > - `dq` — disqualified (rule violation, see Rally Albania §12.13) > - `withdrawn` — voluntary withdraw (Rally Albania §12.15 — MTA penalty for remaining stages) > **`teams` deferred:** Phase 1 doesn't define a `teams` collection. The `team_id` field on `entries` is nullable and the FK target is intentionally unwired in Phase 1. Drop the field entirely if it complicates the snapshot — re-add in Phase 2 if a real team relationship is needed. ### `entry_crew` (junction) | Field | Type | Notes | |---|---|---| | `id` * | UUID | | | `entry_id` * | M2O → entries | | | `user_id` * | M2O → directus_users | | | `role` * | string (dropdown) | enum: `pilot`, `co-pilot`, `navigator`, `mechanic`, `rider`, `runner`, `hiker` | Unique constraint: `(entry_id, user_id)` — a user can't appear twice in the same entry's crew. ### `entry_devices` (junction) | Field | Type | Notes | |---|---|---| | `id` * | UUID | | | `entry_id` * | M2O → entries | | | `device_id` * | M2O → devices | | | `assigned_user_id` | M2O → directus_users | nullable. null = vehicle-mounted; set = body-worn on this crew member | | `mount_position` | string | optional free text: "panic_button_pilot", "hardwired_dash", "backup_chassis" | Unique constraint: `(entry_id, device_id)` — a device can't appear twice in the same entry. ## Specification - **All M2O `ON DELETE`:** `RESTRICT` by default. Cascading from event → entries is appealing but risky for audit/historical purposes — leave `RESTRICT` and require explicit operator action. - **`status` enum order matters for display.** Set the dropdown's option order to match the lifecycle: `registered` → `confirmed` → `started` → `finished` → `dnf` → `dns` → `dq` → `withdrawn`. - **`race_number` is integer**, not string. Plate background color (white/yellow/green/red per Rally Albania §5.5) is derivable from the number range; not a stored field. - **No permission policies yet** — Phase 4 territory. Admin-only access. - **No `team_id` field if it adds complexity** — the schema draft leaves teams as an org-level catalog item that's not yet defined. Phase 1 ships entries without team support. ## Acceptance criteria - [ ] All five collections exist in the admin UI with the fields listed above. - [ ] Required fields flagged required. - [ ] Unique constraints enforced. - [ ] M2O relations work in the admin UI. - [ ] `entries.status` dropdown shows all eight values in lifecycle order. - [ ] Manually walk through the registration: create an event → create classes → create one entry referencing a vehicle, class, and race number → add two `entry_crew` rows (pilot + co-pilot) → add three `entry_devices` rows (one with `assigned_user_id` set, two with null). All FKs resolve. - [ ] Try to create a second entry with the same `race_number` in the same event → error. - [ ] `pnpm run schema:snapshot` produces a snapshot containing the new collections. - [ ] Cross-checked against the schema draft: every field that should exist does, every nullable field is nullable, every unique constraint is in place. ## Risks / open questions - **`assigned_user_id` on entry_devices** — Directus represents this as an M2O. Verify the snapshot encodes the nullable / non-required nature correctly. - **Cascading deletes vs RESTRICT** — RESTRICT is the safe default but may make admin UX painful (you can't delete an event without first deleting all its entries, etc.). Phase 4 / Phase 5 may revisit with custom Flows that walk the dependency graph. ## Done (Fill in commit SHA + one-line note when this lands.)