You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Engine: PostgreSQL 15+ Development: PGlite (in-memory PostgreSQL via @electric-sql/pglite) Production: Azure PostgreSQL Flexible Server with Managed Identity authentication ORM: None — raw SQL with node-pg and parameterized queries
Connection Strategy
The application uses three connection modes (in apps/web/src/db/client.ts):
Mode
When
Auth
Managed Identity
PGHOST + AZURE_CLIENT_ID set
Azure Entra token via DefaultAzureCredential
PGlite
DATABASE_URL unset or "pglite"
File-backed local database
Standard PostgreSQL
DATABASE_URL set
Connection string
Migration System
Migrations live in apps/web/src/db/migrations/
Each migration is a plain SQL file, executed alphabetically
Tracked in a _migrations table to ensure idempotent execution
Run manually: npm run db:migrate -w @acroyoga/web
In tests: createTestDb() applies all migrations to a fresh PGlite instance
rsvps: Uses status = 'cancelled' + cancelled_at timestamp
GDPR Deletion
A sentinel user (id: 00000000-0000-0000-0000-000000000000, email: deleted@system.local) is seeded by migration 003. When a user deletes their account, their PII is replaced and their id is reassigned to the sentinel for referential integrity.
Partial Indexes
Many tables use partial indexes to optimize common query patterns:
idx_events_status — only WHERE status = 'published' (skip cancelled/draft)
idx_rsvps_unique — unique constraint only WHERE status != 'cancelled'
idx_grants_user_active — only WHERE revoked_at IS NULL
idx_profiles_directory_visible — only WHERE directory_visible = true
Geographic Scoping
Permissions use a hierarchical scope model:
Global → Continent → Country → City
The geography table provides denormalized lookup for scope resolution. The permission_grants table links users to roles at specific scope levels.
Audit Trail
The permission_audit_log table is append-only with no foreign keys (intentionally denormalized for durability). It tracks all permission operations: grant, revoke, check_denied, request_submitted, request_approved, request_rejected.