170 lines
6.4 KiB
SQL
170 lines
6.4 KiB
SQL
-- +goose Up
|
|
-- accounts holds the editable source-of-truth user-account state.
|
|
-- email and user_name remain UNIQUE for both live and soft-deleted records:
|
|
-- emails are never reassigned to a fresh user_id after DeleteUser, and
|
|
-- user_name is immutable for the lifetime of the account.
|
|
CREATE TABLE accounts (
|
|
user_id text PRIMARY KEY,
|
|
email text NOT NULL,
|
|
user_name text NOT NULL,
|
|
display_name text NOT NULL DEFAULT '',
|
|
preferred_language text NOT NULL,
|
|
time_zone text NOT NULL,
|
|
declared_country text,
|
|
created_at timestamptz NOT NULL,
|
|
updated_at timestamptz NOT NULL,
|
|
deleted_at timestamptz,
|
|
CONSTRAINT accounts_email_unique UNIQUE (email),
|
|
CONSTRAINT accounts_user_name_unique UNIQUE (user_name)
|
|
);
|
|
|
|
-- Newest-first listing index used by the trusted admin user-list surface.
|
|
CREATE INDEX accounts_listing_idx
|
|
ON accounts (created_at DESC, user_id DESC);
|
|
|
|
-- Reverse-lookup index for the optional declared-country filter; the partial
|
|
-- predicate keeps the index small while declared_country is mostly NULL.
|
|
CREATE INDEX accounts_declared_country_idx
|
|
ON accounts (declared_country)
|
|
WHERE declared_country IS NOT NULL;
|
|
|
|
-- blocked_emails persists pre-user blocked-email subjects that may exist
|
|
-- before any user account exists, plus the blocked subjects produced by
|
|
-- BlockByUserID/BlockByEmail. resolved_user_id is populated when the block
|
|
-- corresponds to an existing or formerly existing account.
|
|
CREATE TABLE blocked_emails (
|
|
email text PRIMARY KEY,
|
|
reason_code text NOT NULL,
|
|
blocked_at timestamptz NOT NULL,
|
|
actor_type text,
|
|
actor_id text,
|
|
resolved_user_id text
|
|
);
|
|
|
|
-- entitlement_records stores the immutable history of entitlement periods.
|
|
-- Each row represents one segment that was current at some point; closed
|
|
-- segments carry closed_* metadata.
|
|
CREATE TABLE entitlement_records (
|
|
record_id text PRIMARY KEY,
|
|
user_id text NOT NULL REFERENCES accounts(user_id),
|
|
plan_code text NOT NULL,
|
|
source text NOT NULL,
|
|
actor_type text NOT NULL,
|
|
actor_id text,
|
|
reason_code text NOT NULL,
|
|
starts_at timestamptz NOT NULL,
|
|
ends_at timestamptz,
|
|
created_at timestamptz NOT NULL,
|
|
closed_at timestamptz,
|
|
closed_by_type text,
|
|
closed_by_id text,
|
|
closed_reason_code text
|
|
);
|
|
|
|
CREATE INDEX entitlement_records_user_idx
|
|
ON entitlement_records (user_id, created_at DESC);
|
|
|
|
-- entitlement_snapshots stores the read-optimized current entitlement state.
|
|
-- Exactly one row per user_id; updated atomically together with history rows
|
|
-- by EntitlementLifecycleStore operations.
|
|
CREATE TABLE entitlement_snapshots (
|
|
user_id text PRIMARY KEY REFERENCES accounts(user_id),
|
|
plan_code text NOT NULL,
|
|
is_paid boolean NOT NULL,
|
|
starts_at timestamptz NOT NULL,
|
|
ends_at timestamptz,
|
|
source text NOT NULL,
|
|
actor_type text NOT NULL,
|
|
actor_id text,
|
|
reason_code text NOT NULL,
|
|
updated_at timestamptz NOT NULL
|
|
);
|
|
|
|
-- Coarse free-versus-paid filter used by the admin listing surface.
|
|
CREATE INDEX entitlement_snapshots_paid_state_idx
|
|
ON entitlement_snapshots (is_paid, plan_code);
|
|
|
|
-- Finite paid-expiry filter; partial predicate keeps the index limited to
|
|
-- finite paid plans (paid_monthly, paid_yearly).
|
|
CREATE INDEX entitlement_snapshots_paid_expiry_idx
|
|
ON entitlement_snapshots (ends_at)
|
|
WHERE is_paid AND ends_at IS NOT NULL;
|
|
|
|
-- sanction_records stores the immutable history of sanction mutations.
|
|
-- A row may carry removed_at + removed_* fields once the sanction is lifted.
|
|
CREATE TABLE sanction_records (
|
|
record_id text PRIMARY KEY,
|
|
user_id text NOT NULL REFERENCES accounts(user_id),
|
|
sanction_code text NOT NULL,
|
|
scope text NOT NULL,
|
|
reason_code text NOT NULL,
|
|
actor_type text NOT NULL,
|
|
actor_id text,
|
|
applied_at timestamptz NOT NULL,
|
|
expires_at timestamptz,
|
|
removed_at timestamptz,
|
|
removed_by_type text,
|
|
removed_by_id text,
|
|
removed_reason_code text
|
|
);
|
|
|
|
CREATE INDEX sanction_records_user_idx
|
|
ON sanction_records (user_id, applied_at DESC);
|
|
|
|
-- sanction_active stores the at-most-one active record per (user_id,
|
|
-- sanction_code). It is maintained by PolicyLifecycleStore in the same
|
|
-- transaction as the corresponding sanction_records mutation.
|
|
CREATE TABLE sanction_active (
|
|
user_id text NOT NULL REFERENCES accounts(user_id),
|
|
sanction_code text NOT NULL,
|
|
record_id text NOT NULL REFERENCES sanction_records(record_id),
|
|
PRIMARY KEY (user_id, sanction_code)
|
|
);
|
|
|
|
CREATE INDEX sanction_active_code_idx
|
|
ON sanction_active (sanction_code);
|
|
|
|
-- limit_records mirrors sanction_records for user-specific limit overrides.
|
|
CREATE TABLE limit_records (
|
|
record_id text PRIMARY KEY,
|
|
user_id text NOT NULL REFERENCES accounts(user_id),
|
|
limit_code text NOT NULL,
|
|
value integer NOT NULL,
|
|
reason_code text NOT NULL,
|
|
actor_type text NOT NULL,
|
|
actor_id text,
|
|
applied_at timestamptz NOT NULL,
|
|
expires_at timestamptz,
|
|
removed_at timestamptz,
|
|
removed_by_type text,
|
|
removed_by_id text,
|
|
removed_reason_code text
|
|
);
|
|
|
|
CREATE INDEX limit_records_user_idx
|
|
ON limit_records (user_id, applied_at DESC);
|
|
|
|
-- limit_active mirrors sanction_active for user-specific limits. value is
|
|
-- denormalised so the admin listing predicate can read it without joining
|
|
-- the full history.
|
|
CREATE TABLE limit_active (
|
|
user_id text NOT NULL REFERENCES accounts(user_id),
|
|
limit_code text NOT NULL,
|
|
record_id text NOT NULL REFERENCES limit_records(record_id),
|
|
value integer NOT NULL,
|
|
PRIMARY KEY (user_id, limit_code)
|
|
);
|
|
|
|
CREATE INDEX limit_active_code_idx
|
|
ON limit_active (limit_code);
|
|
|
|
-- +goose Down
|
|
DROP TABLE IF EXISTS limit_active;
|
|
DROP TABLE IF EXISTS limit_records;
|
|
DROP TABLE IF EXISTS sanction_active;
|
|
DROP TABLE IF EXISTS sanction_records;
|
|
DROP TABLE IF EXISTS entitlement_snapshots;
|
|
DROP TABLE IF EXISTS entitlement_records;
|
|
DROP TABLE IF EXISTS blocked_emails;
|
|
DROP TABLE IF EXISTS accounts;
|