Files
directus/.planning/phase-1-slice-1-schema/05-event-participation-collections.md
T
julian a8e808e71c Scaffold directus service planning structure
Initial commit. Establishes the .planning/ tree mirroring processor's
shape (ROADMAP.md as nav hub + per-phase folders with READMEs and
granular task files).

Six phases:

1. Slice 1 schema + deploy pipeline — what Rally Albania 2026 needs.
   Org catalog (orgs, users, vehicles, devices) + event participation
   (events, classes, entries, entry_crew, entry_devices). db-init/
   for the positions hypertable + faulty column. snapshot/apply
   tooling. Gitea CI dry-run. Dogfood seed of Rally Albania 2026.
   Nine task files with full Goal / Deliverables / Specification /
   Acceptance criteria / Risks / Done sections.

2. Course definition — stages, segments, geofences, waypoints, SLZs.
   PostGIS extension introduced here.

3. Timing & penalty tables — co-developed with processor Phase 2.
   entry_segment_starts, entry_crossings, entry_penalties,
   stage_results, penalty_formulas.

4. Permissions & policies — Directus 11 dynamic-filter Policies per
   logical role. Deployment-time work, deferred to keep early phases
   focused on the data model.

5. Custom extensions — TypeScript hooks/endpoints implementing the
   cross-plane workflows the schema implies (faulty-flag → Redis
   stream emit, stage-open materializer, etc.).

6. Future / optional — retroactivity preview UI, command-routing
   Flows, audit trails, federation rule import. Not committed.

Non-negotiable design rules captured in ROADMAP.md: schema authority
in Directus + snapshot-as-code + db-init for non-Directus DDL +
sequential idempotent migrations + entrypoint apply order + no
application logic in Flows + permissions deferred to Phase 4.

Architectural anchors point at the wiki at ../docs/wiki/ — the schema
draft, the Rally Albania 2025 source page, plus the existing
processor/postgres-timescaledb/live-channel pages. Each task file
calls out the wiki refs an implementing agent should read first.

README.md mirrors the processor service README structure: quick start,
local Docker test, prod/stage deployment notes, env vars, CI behavior.
2026-05-01 20:42:44 +02:00

6.5 KiB
Raw Blame History

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: 1199 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: registeredconfirmedstartedfinisheddnfdnsdqwithdrawn.
  • 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.)