2026-03-20 03:35:22 +11:00
|
|
|
-- Sprout — Declarative Postgres schema (managed by pgschema)
|
|
|
|
|
--
|
|
|
|
|
-- This file represents the desired state of the database schema.
|
|
|
|
|
-- Use `pgschema apply --file schema/schema.sql` to bring the database up to date.
|
|
|
|
|
|
|
|
|
|
-- ── Custom types ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TYPE channel_type AS ENUM ('stream', 'forum', 'dm', 'workflow');
|
|
|
|
|
CREATE TYPE channel_visibility AS ENUM ('open', 'private');
|
|
|
|
|
CREATE TYPE member_role AS ENUM ('owner', 'admin', 'member', 'guest', 'bot');
|
|
|
|
|
CREATE TYPE workflow_status AS ENUM ('active', 'disabled', 'archived');
|
|
|
|
|
CREATE TYPE run_status AS ENUM ('pending', 'running', 'waiting_approval', 'completed', 'failed', 'cancelled');
|
|
|
|
|
CREATE TYPE approval_status AS ENUM ('pending', 'granted', 'denied', 'expired');
|
|
|
|
|
CREATE TYPE delivery_method AS ENUM ('webhook', 'websocket');
|
|
|
|
|
CREATE TYPE subscription_status AS ENUM ('active', 'paused', 'deleted');
|
|
|
|
|
CREATE TYPE pause_reason AS ENUM ('user', 'system', 'rate_limit');
|
|
|
|
|
CREATE TYPE channel_add_policy AS ENUM ('anyone', 'owner_only', 'nobody');
|
|
|
|
|
|
|
|
|
|
-- ── Channels ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE channels (
|
|
|
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
channel_type channel_type NOT NULL DEFAULT 'stream',
|
|
|
|
|
visibility channel_visibility NOT NULL DEFAULT 'open',
|
|
|
|
|
description TEXT,
|
|
|
|
|
canvas TEXT,
|
|
|
|
|
created_by BYTEA NOT NULL,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
archived_at TIMESTAMPTZ,
|
|
|
|
|
deleted_at TIMESTAMPTZ,
|
|
|
|
|
nip29_group_id VARCHAR(255) UNIQUE,
|
|
|
|
|
topic_required BOOLEAN NOT NULL DEFAULT FALSE,
|
|
|
|
|
max_members INT,
|
|
|
|
|
topic TEXT,
|
|
|
|
|
topic_set_by BYTEA,
|
|
|
|
|
topic_set_at TIMESTAMPTZ,
|
|
|
|
|
purpose TEXT,
|
|
|
|
|
purpose_set_by BYTEA,
|
|
|
|
|
purpose_set_at TIMESTAMPTZ,
|
2026-04-07 11:37:51 -07:00
|
|
|
participant_hash BYTEA,
|
|
|
|
|
ttl_seconds INT,
|
2026-04-16 14:56:36 -04:00
|
|
|
ttl_deadline TIMESTAMPTZ,
|
|
|
|
|
CONSTRAINT chk_channels_id_not_nil CHECK (id <> '00000000-0000-0000-0000-000000000000'::uuid)
|
2026-03-20 03:35:22 +11:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_channels_type ON channels (channel_type);
|
|
|
|
|
CREATE INDEX idx_channels_visibility ON channels (visibility);
|
|
|
|
|
CREATE INDEX idx_channels_created_by ON channels (created_by);
|
|
|
|
|
CREATE UNIQUE INDEX idx_channels_dm_hash ON channels (participant_hash);
|
2026-04-07 11:37:51 -07:00
|
|
|
CREATE INDEX idx_channels_ttl_expiry ON channels (ttl_deadline)
|
|
|
|
|
WHERE ttl_seconds IS NOT NULL AND archived_at IS NULL AND deleted_at IS NULL;
|
2026-03-20 03:35:22 +11:00
|
|
|
|
|
|
|
|
-- ── Channel members ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE channel_members (
|
|
|
|
|
channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
|
|
|
|
pubkey BYTEA NOT NULL,
|
|
|
|
|
role member_role NOT NULL DEFAULT 'member',
|
|
|
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
invited_by BYTEA,
|
|
|
|
|
removed_at TIMESTAMPTZ,
|
|
|
|
|
removed_by BYTEA,
|
2026-03-22 09:00:45 -07:00
|
|
|
hidden_at TIMESTAMPTZ,
|
2026-03-20 03:35:22 +11:00
|
|
|
PRIMARY KEY (channel_id, pubkey)
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-29 09:41:57 -04:00
|
|
|
CREATE INDEX idx_channel_members_pubkey ON channel_members (pubkey)
|
|
|
|
|
WHERE removed_at IS NULL;
|
|
|
|
|
|
2026-03-20 03:35:22 +11:00
|
|
|
-- ── Users ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE users (
|
|
|
|
|
pubkey BYTEA PRIMARY KEY,
|
|
|
|
|
nip05_handle VARCHAR(255) UNIQUE,
|
|
|
|
|
display_name VARCHAR(255),
|
|
|
|
|
avatar_url TEXT,
|
|
|
|
|
about TEXT,
|
|
|
|
|
agent_type VARCHAR(255),
|
|
|
|
|
capabilities JSONB,
|
|
|
|
|
okta_user_id VARCHAR(255) UNIQUE,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
deactivated_at TIMESTAMPTZ,
|
|
|
|
|
metadata_event_id BYTEA,
|
|
|
|
|
agent_owner_pubkey BYTEA REFERENCES users(pubkey) ON DELETE SET NULL,
|
|
|
|
|
channel_add_policy channel_add_policy NOT NULL DEFAULT 'anyone',
|
|
|
|
|
CONSTRAINT chk_users_pubkey_len CHECK (LENGTH(pubkey) = 32)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ── Events (partitioned by month on created_at) ──────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE events (
|
|
|
|
|
id BYTEA NOT NULL,
|
|
|
|
|
pubkey BYTEA NOT NULL,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
kind INT NOT NULL,
|
|
|
|
|
tags JSONB NOT NULL,
|
|
|
|
|
content TEXT NOT NULL,
|
|
|
|
|
sig BYTEA NOT NULL,
|
|
|
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
channel_id UUID,
|
|
|
|
|
deleted_at TIMESTAMPTZ,
|
2026-04-06 21:15:01 -04:00
|
|
|
d_tag TEXT,
|
2026-03-20 03:35:22 +11:00
|
|
|
PRIMARY KEY (created_at, id)
|
|
|
|
|
) PARTITION BY RANGE (created_at);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE events_p_past PARTITION OF events
|
|
|
|
|
FOR VALUES FROM (MINVALUE) TO ('2026-01-01');
|
|
|
|
|
CREATE TABLE events_p2026_01 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
|
|
|
|
|
CREATE TABLE events_p2026_02 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
|
|
|
|
|
CREATE TABLE events_p2026_03 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
|
|
|
|
|
CREATE TABLE events_p2026_04 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
|
|
|
|
|
CREATE TABLE events_p2026_05 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
|
|
|
|
|
CREATE TABLE events_p2026_06 PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
|
|
|
|
|
CREATE TABLE events_p_future PARTITION OF events
|
|
|
|
|
FOR VALUES FROM ('2026-07-01') TO (MAXVALUE);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_events_pubkey_kind_created ON events (pubkey, kind, created_at);
|
|
|
|
|
CREATE INDEX idx_events_channel_created ON events (channel_id, created_at);
|
|
|
|
|
CREATE INDEX idx_events_kind_created ON events (kind, created_at);
|
|
|
|
|
CREATE INDEX idx_events_id ON events (id);
|
|
|
|
|
CREATE INDEX idx_events_deleted ON events (deleted_at);
|
|
|
|
|
CREATE INDEX idx_events_addressable ON events (kind, pubkey, channel_id, deleted_at);
|
2026-04-06 21:15:01 -04:00
|
|
|
CREATE INDEX idx_events_parameterized ON events (kind, pubkey, d_tag, deleted_at) WHERE d_tag IS NOT NULL;
|
2026-03-20 03:35:22 +11:00
|
|
|
|
|
|
|
|
-- ── Event mentions ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE event_mentions (
|
|
|
|
|
pubkey_hex VARCHAR(64) NOT NULL,
|
|
|
|
|
event_id BYTEA NOT NULL,
|
|
|
|
|
event_created_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
channel_id UUID,
|
|
|
|
|
event_kind INT,
|
|
|
|
|
PRIMARY KEY (pubkey_hex, event_id)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_event_mentions_pubkey_created ON event_mentions (pubkey_hex, event_created_at DESC);
|
|
|
|
|
CREATE INDEX idx_event_mentions_pubkey_kind_created ON event_mentions (pubkey_hex, event_kind, event_created_at DESC);
|
|
|
|
|
|
|
|
|
|
-- ── Subscriptions ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE subscriptions (
|
|
|
|
|
id VARCHAR(255) PRIMARY KEY,
|
|
|
|
|
owner_pubkey BYTEA NOT NULL REFERENCES users(pubkey),
|
|
|
|
|
filter_kinds JSONB,
|
|
|
|
|
filter_authors JSONB,
|
|
|
|
|
filter_channel_ids JSONB,
|
|
|
|
|
filter_since TIMESTAMPTZ,
|
|
|
|
|
filter_until TIMESTAMPTZ,
|
|
|
|
|
delivery_method delivery_method NOT NULL DEFAULT 'webhook',
|
|
|
|
|
delivery_url TEXT,
|
|
|
|
|
status subscription_status NOT NULL DEFAULT 'active',
|
|
|
|
|
pause_reason pause_reason,
|
|
|
|
|
delivered_count BIGINT NOT NULL DEFAULT 0,
|
|
|
|
|
error_count BIGINT NOT NULL DEFAULT 0,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ── Delivery log (partitioned by month on delivered_at) ──────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE delivery_log (
|
|
|
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY,
|
|
|
|
|
subscription_id VARCHAR(255),
|
|
|
|
|
event_id BYTEA,
|
|
|
|
|
method delivery_method,
|
|
|
|
|
delivered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
success BOOLEAN,
|
|
|
|
|
http_status INT,
|
|
|
|
|
error_message TEXT,
|
|
|
|
|
attempt_number INT DEFAULT 1,
|
|
|
|
|
PRIMARY KEY (delivered_at, id)
|
|
|
|
|
) PARTITION BY RANGE (delivered_at);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE delivery_log_p_past PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM (MINVALUE) TO ('2026-03-01');
|
|
|
|
|
CREATE TABLE delivery_log_p2026_03 PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
|
|
|
|
|
CREATE TABLE delivery_log_p2026_04 PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
|
|
|
|
|
CREATE TABLE delivery_log_p2026_05 PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');
|
|
|
|
|
CREATE TABLE delivery_log_p2026_06 PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');
|
|
|
|
|
CREATE TABLE delivery_log_p_future PARTITION OF delivery_log
|
|
|
|
|
FOR VALUES FROM ('2026-07-01') TO (MAXVALUE);
|
|
|
|
|
|
|
|
|
|
-- ── Workflows ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE workflows (
|
|
|
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
owner_pubkey BYTEA NOT NULL REFERENCES users(pubkey),
|
|
|
|
|
channel_id UUID REFERENCES channels(id),
|
|
|
|
|
definition JSONB NOT NULL,
|
|
|
|
|
definition_hash BYTEA NOT NULL,
|
|
|
|
|
status workflow_status NOT NULL DEFAULT 'active',
|
|
|
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_workflows_channel_active ON workflows (channel_id, status, enabled);
|
|
|
|
|
|
|
|
|
|
-- ── Workflow runs ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE workflow_runs (
|
|
|
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
|
workflow_id UUID NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
|
|
|
|
status run_status NOT NULL DEFAULT 'pending',
|
|
|
|
|
trigger_event_id BYTEA,
|
|
|
|
|
current_step INT NOT NULL DEFAULT 0,
|
|
|
|
|
execution_trace JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
trigger_context JSONB,
|
|
|
|
|
started_at TIMESTAMPTZ,
|
|
|
|
|
completed_at TIMESTAMPTZ,
|
|
|
|
|
error_message TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_workflow_runs_workflow ON workflow_runs (workflow_id);
|
|
|
|
|
CREATE INDEX idx_workflow_runs_status ON workflow_runs (status);
|
|
|
|
|
|
|
|
|
|
-- ── Workflow approvals ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE workflow_approvals (
|
|
|
|
|
token BYTEA PRIMARY KEY,
|
|
|
|
|
workflow_id UUID NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
|
|
|
|
run_id UUID NOT NULL REFERENCES workflow_runs(id) ON DELETE CASCADE,
|
|
|
|
|
step_id VARCHAR(64) NOT NULL,
|
|
|
|
|
step_index INT NOT NULL,
|
|
|
|
|
approver_spec TEXT NOT NULL,
|
|
|
|
|
status approval_status NOT NULL DEFAULT 'pending',
|
|
|
|
|
approver_pubkey BYTEA,
|
|
|
|
|
note TEXT,
|
|
|
|
|
granted_at TIMESTAMPTZ,
|
|
|
|
|
denied_at TIMESTAMPTZ,
|
|
|
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_workflow_approvals_workflow ON workflow_approvals (workflow_id);
|
|
|
|
|
CREATE INDEX idx_workflow_approvals_run ON workflow_approvals (run_id);
|
|
|
|
|
CREATE INDEX idx_workflow_approvals_status ON workflow_approvals (status);
|
|
|
|
|
|
|
|
|
|
-- ── API tokens ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE api_tokens (
|
|
|
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
|
token_hash BYTEA NOT NULL UNIQUE,
|
|
|
|
|
owner_pubkey BYTEA NOT NULL REFERENCES users(pubkey),
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
scopes JSONB NOT NULL,
|
|
|
|
|
channel_ids JSONB,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
|
last_used_at TIMESTAMPTZ,
|
|
|
|
|
revoked_at TIMESTAMPTZ,
|
|
|
|
|
revoked_by BYTEA,
|
|
|
|
|
created_by_self_mint BOOLEAN NOT NULL DEFAULT FALSE,
|
|
|
|
|
CONSTRAINT chk_api_tokens_hash_len CHECK (LENGTH(token_hash) = 32)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ── Rate limit violations ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE rate_limit_violations (
|
|
|
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
|
|
|
pubkey BYTEA,
|
|
|
|
|
violation_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
limit_type VARCHAR(64),
|
|
|
|
|
limit_value INT,
|
|
|
|
|
actual_value INT,
|
|
|
|
|
action_taken VARCHAR(64)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ── Thread metadata ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE thread_metadata (
|
|
|
|
|
event_created_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
event_id BYTEA NOT NULL,
|
|
|
|
|
channel_id UUID NOT NULL REFERENCES channels(id),
|
|
|
|
|
parent_event_id BYTEA,
|
|
|
|
|
parent_event_created_at TIMESTAMPTZ,
|
|
|
|
|
root_event_id BYTEA,
|
|
|
|
|
root_event_created_at TIMESTAMPTZ,
|
|
|
|
|
depth INT NOT NULL DEFAULT 0,
|
|
|
|
|
reply_count INT NOT NULL DEFAULT 0,
|
|
|
|
|
descendant_count INT NOT NULL DEFAULT 0,
|
|
|
|
|
last_reply_at TIMESTAMPTZ,
|
|
|
|
|
broadcast BOOLEAN NOT NULL DEFAULT FALSE,
|
|
|
|
|
PRIMARY KEY (event_created_at, event_id)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_thread_metadata_parent ON thread_metadata (parent_event_id);
|
|
|
|
|
CREATE INDEX idx_thread_metadata_root ON thread_metadata (root_event_id);
|
|
|
|
|
CREATE INDEX idx_thread_metadata_channel_depth ON thread_metadata (channel_id, depth, event_created_at);
|
|
|
|
|
CREATE INDEX idx_thread_metadata_event_id ON thread_metadata (event_id);
|
|
|
|
|
|
|
|
|
|
-- ── Reactions ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE reactions (
|
|
|
|
|
event_created_at TIMESTAMPTZ NOT NULL,
|
|
|
|
|
event_id BYTEA NOT NULL,
|
|
|
|
|
pubkey BYTEA NOT NULL,
|
|
|
|
|
emoji VARCHAR(64) NOT NULL,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
removed_at TIMESTAMPTZ,
|
|
|
|
|
reaction_event_id BYTEA,
|
|
|
|
|
PRIMARY KEY (event_created_at, event_id, pubkey, emoji)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX idx_reactions_event ON reactions (event_id, event_created_at);
|
|
|
|
|
CREATE INDEX idx_reactions_pubkey ON reactions (pubkey);
|
|
|
|
|
CREATE UNIQUE INDEX idx_reactions_source_event ON reactions (reaction_event_id);
|
|
|
|
|
|
|
|
|
|
-- ── Pubkey allowlist ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE pubkey_allowlist (
|
|
|
|
|
pubkey BYTEA PRIMARY KEY,
|
|
|
|
|
added_by BYTEA,
|
|
|
|
|
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
|
note TEXT
|
|
|
|
|
);
|
2026-05-01 19:11:32 -04:00
|
|
|
|
|
|
|
|
-- ── Relay members (NIP-43) ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS relay_members (
|
|
|
|
|
pubkey TEXT PRIMARY KEY,
|
|
|
|
|
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
|
|
|
|
|
added_by TEXT,
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_relay_members_role ON relay_members(role);
|
2026-05-23 12:03:38 -04:00
|
|
|
|
|
|
|
|
-- ── Archived identities (NIP-IA) ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS archived_identities (
|
|
|
|
|
pubkey TEXT PRIMARY KEY,
|
|
|
|
|
consent_path TEXT NOT NULL CHECK (consent_path IN ('self', 'owner', 'admin')),
|
|
|
|
|
actor TEXT NOT NULL,
|
|
|
|
|
reason TEXT,
|
|
|
|
|
replaced_by TEXT,
|
|
|
|
|
request_event_id TEXT NOT NULL,
|
|
|
|
|
archived_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
|
|
|
);
|