CREATE TABLE IF NOT EXISTS schema_version ( version INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS server_config ( id INTEGER PRIMARY KEY CHECK (id = 1), signing_key_enc BLOB, signing_key_nonce BLOB, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY, uuid TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE COLLATE NOCASE, account_type TEXT NOT NULL CHECK (account_type IN ('human','system')), password_hash TEXT, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','inactive','deleted')), totp_required INTEGER NOT NULL DEFAULT 0 CHECK (totp_required IN (0,1)), totp_secret_enc BLOB, totp_secret_nonce BLOB, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), deleted_at TEXT ); CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts (username); CREATE INDEX IF NOT EXISTS idx_accounts_uuid ON accounts (uuid); CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts (status); CREATE TABLE IF NOT EXISTS account_roles ( id INTEGER PRIMARY KEY, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, role TEXT NOT NULL, granted_by INTEGER REFERENCES accounts(id), granted_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), UNIQUE (account_id, role) ); CREATE INDEX IF NOT EXISTS idx_account_roles_account ON account_roles (account_id); CREATE TABLE IF NOT EXISTS token_revocation ( id INTEGER PRIMARY KEY, jti TEXT NOT NULL UNIQUE, account_id INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, expires_at TEXT NOT NULL, revoked_at TEXT, revoke_reason TEXT, issued_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); CREATE INDEX IF NOT EXISTS idx_token_jti ON token_revocation (jti); CREATE INDEX IF NOT EXISTS idx_token_account ON token_revocation (account_id); CREATE INDEX IF NOT EXISTS idx_token_expires ON token_revocation (expires_at); CREATE TABLE IF NOT EXISTS system_tokens ( id INTEGER PRIMARY KEY, account_id INTEGER NOT NULL UNIQUE REFERENCES accounts(id) ON DELETE CASCADE, jti TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); CREATE TABLE IF NOT EXISTS pg_credentials ( id INTEGER PRIMARY KEY, account_id INTEGER NOT NULL UNIQUE REFERENCES accounts(id) ON DELETE CASCADE, pg_host TEXT NOT NULL, pg_port INTEGER NOT NULL DEFAULT 5432, pg_database TEXT NOT NULL, pg_username TEXT NOT NULL, pg_password_enc BLOB NOT NULL, pg_password_nonce BLOB NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY, event_time TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')), event_type TEXT NOT NULL, actor_id INTEGER REFERENCES accounts(id), target_id INTEGER REFERENCES accounts(id), ip_address TEXT, details TEXT ); CREATE INDEX IF NOT EXISTS idx_audit_time ON audit_log (event_time); CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_log (actor_id); CREATE INDEX IF NOT EXISTS idx_audit_event ON audit_log (event_type);