CREATE TABLE IF NOT EXISTS nodes ( id SERIAL PRIMARY KEY, name TEXT UNIQUE NOT NULL, hostname TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS domains ( id SERIAL PRIMARY KEY, domain TEXT UNIQUE NOT NULL, node_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', first_seen_at TIMESTAMPTZ DEFAULT now(), last_seen_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS mailboxes ( id SERIAL PRIMARY KEY, email_address TEXT UNIQUE NOT NULL, domain TEXT NOT NULL, node_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', 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 SERIAL PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'super_admin', created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS audit_log ( id SERIAL PRIMARY KEY, actor_email TEXT, action TEXT NOT NULL, target TEXT, details JSONB, created_at TIMESTAMPTZ DEFAULT now() ); -- ============================================================ -- 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; 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. -- This must be guarded because older/newer MVP databases may not have last_usage_scan_at. DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'mailboxes' AND column_name = 'last_usage_scan_at' ) THEN UPDATE mailboxes SET usage_scanned_at = last_usage_scan_at WHERE usage_scanned_at IS NULL AND last_usage_scan_at IS NOT NULL; END IF; END $$; -- Backfill new audit target columns from old target column. -- This must also be guarded because some installations may already use target_id only. DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'audit_log' AND column_name = 'target' ) THEN UPDATE audit_log SET target_id = target WHERE target_id IS NULL AND target IS NOT NULL; END IF; END $$; 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);