Files
MiragenFlow/server/migrations/0008_v1_plan_payment_events.sql
T

144 lines
5.8 KiB
SQL

-- Plan purchases use a separate payment-event table because the recharge
-- payment_events.order_id foreign key intentionally points at recharge_orders.
ALTER TABLE IF EXISTS plan_purchases ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT 'adapter';
ALTER TABLE IF EXISTS plan_purchases ADD COLUMN IF NOT EXISTS plan_snapshot JSONB;
ALTER TABLE IF EXISTS plan_purchases ADD COLUMN IF NOT EXISTS credit_bucket_id UUID;
ALTER TABLE IF EXISTS plan_purchases ADD COLUMN IF NOT EXISTS entitlement_id UUID;
ALTER TABLE IF EXISTS recharge_orders ADD COLUMN IF NOT EXISTS credit_bucket_id UUID;
ALTER TABLE IF EXISTS user_plan_entitlements ADD COLUMN IF NOT EXISTS snapshot JSONB;
CREATE TABLE IF NOT EXISTS plan_payment_events (
id UUID PRIMARY KEY,
provider TEXT NOT NULL,
external_event_id TEXT NOT NULL,
purchase_id UUID NOT NULL REFERENCES plan_purchases(id),
payload_digest TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
applied_at TIMESTAMPTZ,
UNIQUE(provider, external_event_id)
);
-- Runtime records that are part of the server's durable state but are not
-- user-facing business aggregates. They keep authentication challenges,
-- provider configuration and staging metadata relationally addressable while
-- preserving encrypted payloads and ownership boundaries.
ALTER TABLE IF EXISTS verification_codes ADD COLUMN IF NOT EXISTS record_key TEXT;
ALTER TABLE IF EXISTS verification_codes ADD COLUMN IF NOT EXISTS record_kind TEXT NOT NULL DEFAULT 'verification';
ALTER TABLE IF EXISTS verification_codes ADD COLUMN IF NOT EXISTS ip TEXT;
ALTER TABLE IF EXISTS verification_codes ADD COLUMN IF NOT EXISTS device_id TEXT;
ALTER TABLE IF EXISTS verification_codes ADD COLUMN IF NOT EXISTS max_attempts INTEGER NOT NULL DEFAULT 5;
CREATE INDEX IF NOT EXISTS verification_codes_record_key_idx ON verification_codes(record_kind, record_key);
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS mfa_secret_encrypted TEXT;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS mfa_recovery_code_hashes JSONB;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS mfa_last_totp_counter BIGINT;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS mfa_required BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS mfa_enabled BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS roles JSONB NOT NULL DEFAULT '[]'::jsonb;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS failed_login_count INTEGER NOT NULL DEFAULT 0;
ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS locked_until BIGINT;
ALTER TABLE IF EXISTS sessions ADD COLUMN IF NOT EXISTS replaced_by TEXT;
ALTER TABLE IF EXISTS sessions ADD COLUMN IF NOT EXISTS roles JSONB;
ALTER TABLE IF EXISTS sessions ADD COLUMN IF NOT EXISTS scopes JSONB;
CREATE TABLE IF NOT EXISTS captcha_challenges (
id TEXT PRIMARY KEY,
action TEXT NOT NULL,
answer_hash TEXT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 5,
consumed_at TIMESTAMPTZ,
ip TEXT NOT NULL,
device_id TEXT
);
CREATE TABLE IF NOT EXISTS mfa_challenges (
id TEXT PRIMARY KEY,
subject_id TEXT NOT NULL,
scope TEXT NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS session_families (
family_id TEXT PRIMARY KEY,
subject_id TEXT NOT NULL,
scope TEXT NOT NULL,
revoked_at TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS admin_accounts (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
mfa_secret_encrypted TEXT,
mfa_required BOOLEAN NOT NULL DEFAULT true,
recovery_code_hashes JSONB NOT NULL DEFAULT '[]'::jsonb,
mfa_last_totp_counter BIGINT
);
CREATE TABLE IF NOT EXISTS runtime_records (
record_type TEXT NOT NULL,
record_key TEXT NOT NULL,
payload JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (record_type, record_key)
);
CREATE TABLE IF NOT EXISTS message_providers (
id TEXT PRIMARY KEY,
snapshot JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS message_templates (
id TEXT PRIMARY KEY,
snapshot JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS payment_providers (
id TEXT PRIMARY KEY,
snapshot JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS webdav_files (
user_id TEXT NOT NULL,
path TEXT NOT NULL,
mime_type TEXT NOT NULL,
data TEXT NOT NULL DEFAULT '',
checksum TEXT NOT NULL,
etag TEXT NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
updated_at TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ,
PRIMARY KEY (user_id, path)
);
CREATE TABLE IF NOT EXISTS webdav_sync_jobs (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
operation TEXT NOT NULL,
path TEXT NOT NULL,
data TEXT,
mime_type TEXT,
if_match TEXT,
attempts INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
next_attempt_at TIMESTAMPTZ NOT NULL,
lease_expires_at TIMESTAMPTZ,
last_error TEXT,
created_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE IF NOT EXISTS stored_objects (
id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
mime_type TEXT NOT NULL,
data TEXT,
staging_key TEXT,
expires_at TIMESTAMPTZ NOT NULL,
task_id TEXT,
revoked BOOLEAN NOT NULL DEFAULT false
);
CREATE INDEX IF NOT EXISTS webdav_files_user_idx ON webdav_files(user_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS webdav_sync_jobs_user_idx ON webdav_sync_jobs(user_id, status, next_attempt_at);
CREATE INDEX IF NOT EXISTS stored_objects_owner_idx ON stored_objects(owner_id, expires_at);
ALTER TABLE IF EXISTS webdav_configs ADD COLUMN IF NOT EXISTS manifest_retention_expires_at TIMESTAMPTZ;
ALTER TABLE IF EXISTS webdav_configs ADD COLUMN IF NOT EXISTS manifest_extension_days INTEGER NOT NULL DEFAULT 0;