auth-yes/tasks/complete/2026-0826.07.gem.refactor.backend.attendee-relational-decoupling.2359.md

2.9 KiB

Phase 7: Attendee Relational Decoupling & Immutable Identity

1. Context & Rationale

Currently, guest attendee sessions are bound to their originating event via brittle string matching on users.username (e.g., WHERE username LIKE 'guest_' || ep.slug || '_%'). This creates two major architectural flaws:

  1. Rotation Breakage: If an event host rotates the event's ingress PIN/slug (e.g., deno-lab-1a2b to deno-lab-9x8z), the backend loses track of previously joined guests because their usernames retain the old slug suffix.
  2. Unwieldy Data: Guest usernames inherit the full event slug, leading to massive, unreadable usernames in logs and the database (e.g., guest_annual-cyber-security-training-workshop-4f8x_12).

2. Objective

Implement a robust "A+B Architecture" to permanently decouple guest session lifecycle from transient URL slugs:

  • Option A (Relational Integrity): Link guests directly to the Event UUID via a strict Foreign Key (event_pass_id).
  • Option B (Immutable Identity): Mint short, deterministic, and immutable usernames using the Event UUID prefix.

3. Scope & Acceptance Criteria

3.1 Database Migration (server/db.ts)

  • Add column: ALTER TABLE users ADD COLUMN IF NOT EXISTS event_pass_id UUID REFERENCES event_passes(id) ON DELETE CASCADE;
  • Ensure backward compatibility with existing standalone users (column should be nullable, as admins/owners won't have an event_pass_id).

3.2 Immutable Username Minting (server/routes/events.ts)

  • In POST /api/join: Change guest username generation from guest_${event.slug}_${seat} to guest_${event.id.split('-')[0]}_${seat}.
  • In POST /api/join: Insert event.id into the new event_pass_id column for the newly minted user.

3.3 Query Refactoring (Zero String Parsing)

  • GET /api/events/:id/attendees: Refactor the query to use WHERE u.event_pass_id = ${eventId}.
  • POST /api/events/:id/extend: Refactor the query to target sessions belonging to u.event_pass_id = ${eventId}.
  • POST /api/events/:id/end: Refactor the revocation query to target u.event_pass_id = ${eventId}.
  • DELETE /api/sessions/:id: Refactor the ownership check to verify u.event_pass_id = ${eventId} instead of string parsing.

3.4 Quality Gates

  • Run deno fmt, deno task lint, deno task check.
  • All 67+ backend tests must pass. (Update server/tests/events.test.ts to mock/handle the new event_pass_id column and short username generation).

4. Anti-Patterns to Avoid

  • No LIKE 'guest_%' queries anywhere in the backend logic.
  • No updating usernames on slug rotation. The POST /api/events/:id/rotate-pin endpoint should ONLY touch event_passes.slug and event_passes.pin_code, leaving attendees completely unaffected.