This commit is contained in:
2026-04-26 16:05:04 -05:00
parent 00932aeaff
commit fdcec00bc9
6 changed files with 487 additions and 148 deletions

View File

@@ -1,63 +1,147 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS nodes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL,
hostname TEXT NOT NULL,
is_current BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS domains (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id SERIAL PRIMARY KEY,
domain TEXT UNIQUE NOT NULL,
current_node TEXT NOT NULL,
node_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ,
last_synced_at TIMESTAMPTZ,
notes TEXT DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
first_seen_at TIMESTAMPTZ DEFAULT now(),
last_seen_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS mailboxes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id SERIAL PRIMARY KEY,
email_address TEXT UNIQUE NOT NULL,
local_part TEXT NOT NULL,
domain TEXT NOT NULL REFERENCES domains(domain) ON DELETE CASCADE,
domain TEXT NOT NULL,
node_name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
used_bytes BIGINT NOT NULL DEFAULT 0,
usage_scanned_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
used_bytes BIGINT DEFAULT 0,
last_usage_scan_at TIMESTAMPTZ,
first_seen_at TIMESTAMPTZ DEFAULT now(),
last_seen_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS admin_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id SERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'super_admin',
allowed_domains TEXT[] NOT NULL DEFAULT '{}',
active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
id SERIAL PRIMARY KEY,
actor_email TEXT,
action TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id TEXT NOT NULL,
details JSONB NOT NULL DEFAULT '{}',
ip_address TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
target TEXT,
details JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_domains_node ON domains(current_node);
CREATE INDEX IF NOT EXISTS idx_mailboxes_domain ON mailboxes(domain);
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at DESC);
-- ============================================================
-- Upgrade existing MVP database
-- ============================================================
ALTER TABLE nodes
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
ALTER TABLE nodes
ADD COLUMN IF NOT EXISTS is_current BOOLEAN DEFAULT false;
ALTER TABLE domains
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
ALTER TABLE domains
ADD COLUMN IF NOT EXISTS last_synced_at TIMESTAMPTZ;
ALTER TABLE domains
ADD COLUMN IF NOT EXISTS notes TEXT DEFAULT '';
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS local_part TEXT;
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS quota_bytes BIGINT;
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS quota_percent NUMERIC(8,3);
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS message_count BIGINT;
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS message_limit BIGINT;
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS usage_scanned_at TIMESTAMPTZ;
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
ALTER TABLE mailboxes
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE admin_users
ADD COLUMN IF NOT EXISTS allowed_domains TEXT[] NOT NULL DEFAULT '{}';
ALTER TABLE admin_users
ADD COLUMN IF NOT EXISTS active BOOLEAN NOT NULL DEFAULT true;
ALTER TABLE admin_users
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();
ALTER TABLE audit_log
ADD COLUMN IF NOT EXISTS target_type TEXT;
ALTER TABLE audit_log
ADD COLUMN IF NOT EXISTS target_id TEXT;
ALTER TABLE audit_log
ADD COLUMN IF NOT EXISTS ip_address TEXT;
-- details existed already, but make it safer for newer code
ALTER TABLE audit_log
ALTER COLUMN details SET DEFAULT '{}';
-- Fill local_part for existing rows
UPDATE mailboxes
SET local_part = split_part(email_address, '@', 1)
WHERE local_part IS NULL
AND email_address LIKE '%@%';
-- Keep old and new usage timestamp columns in sync initially
UPDATE mailboxes
SET usage_scanned_at = last_usage_scan_at
WHERE usage_scanned_at IS NULL
AND last_usage_scan_at IS NOT NULL;
-- Backfill new audit target columns from old target column
UPDATE audit_log
SET target_id = target
WHERE target_id IS NULL
AND target IS NOT NULL;
UPDATE audit_log
SET target_type = 'unknown'
WHERE target_type IS NULL;
-- Useful indexes
CREATE INDEX IF NOT EXISTS idx_domains_node_name
ON domains(node_name);
CREATE INDEX IF NOT EXISTS idx_mailboxes_domain
ON mailboxes(domain);
CREATE INDEX IF NOT EXISTS idx_mailboxes_node_name
ON mailboxes(node_name);
CREATE INDEX IF NOT EXISTS idx_audit_created
ON audit_log(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_admin_users_allowed_domains
ON admin_users USING GIN(allowed_domains);