Legal — Data Model
Two tables. Defined in libs/db/src/lib/schema/legal.ts.
legal_documents
| Column | Type | Notes |
|---|---|---|
id | uuid PK | |
type | legal_document_type NOT NULL | terms_of_use, privacy_policy, fitness_waiver, cookie_policy, acceptable_use |
version | varchar(20) NOT NULL | Semver-ish string, e.g. '2.0.0'. Compared by published_at not by parsing this field |
locale | varchar(5) NOT NULL | he, en, ru |
url | text NOT NULL | Canonical URL on marketing site |
published_at | timestamptz NOT NULL | Source of truth for “latest” ordering |
created_at | timestamptz NOT NULL DEFAULT now |
Unique: (type, version, locale).
legal_consents
| Column | Type | Notes |
|---|---|---|
id | uuid PK | |
user_id | uuid NOT NULL | FK users(id) (no ON DELETE — see PII) |
organization_id | uuid | FK organizations(id) nullable — audit context only; consent is platform-level |
document_id | uuid NOT NULL | FK legal_documents(id) |
consent_context | consent_context NOT NULL | owner_onboarding, member_onboarding, reconsent, profile_update |
ip_address | varchar(45) | IPv6-capable |
user_agent | text | Full UA string |
consented_at | timestamptz NOT NULL DEFAULT now | |
revoked_at | timestamptz | NULL = active. No UI today |
Index: (user_id, document_id).
Relationships
legal_documents 1 ─── * legal_consents
users 1 ─── * legal_consents
organizations 1 ─── * legal_consents (audit context, optional)Multi-org isolation
Not org-scoped. Documents and consents are platform-level. The optional organization_id on a consent row is audit metadata only — it captures “which org context the user was in when they accepted” (e.g. signing up to a specific gym’s onboarding). It does not affect document selection.
PII handling
ip_addressanduser_agentare plaintext audit evidence.- No special encryption.
revoked_atexists for soft-revocation. No “right to be forgotten” cascade — deleting a user leaveslegal_consents.user_iddangling because there’s noON DELETEclause. Cleanup is a future ops concern.
Soft vs hard delete
- Documents: never deleted by app code.
- Consents: soft-revocable via
revoked_at. Active rows haverevoked_at IS NULL— every read filters on this.