feat: use postgres
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
-- +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;
|
||||
Reference in New Issue
Block a user