import db from './database'; /** * Creates all required tables if they do not already exist. * Safe to call on every startup — uses IF NOT EXISTS throughout. */ export function initDatabase(log = true): void { db.exec(` CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', project_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE IF NOT EXISTS installed_packages (app_id TEXT PRIMARY KEY, package_json TEXT NOT NULL, digest TEXT NOT NULL); CREATE TABLE IF NOT EXISTS app_connections (app_id TEXT PRIMARY KEY, encrypted_value TEXT NOT NULL, iv TEXT NOT NULL, auth_tag TEXT NOT NULL); CREATE TABLE IF NOT EXISTS app_icons ( id TEXT PRIMARY KEY, png BLOB NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS included_apps ( id TEXT PRIMARY KEY, manifest_json TEXT NOT NULL, registered_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE IF NOT EXISTS secrets ( id TEXT PRIMARY KEY, name TEXT NOT NULL, authentication_type TEXT NOT NULL, encrypted_value TEXT NOT NULL, iv TEXT NOT NULL, auth_tag TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE IF NOT EXISTS executions ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), project_id TEXT, project_name TEXT, action_id TEXT, action_name TEXT, method TEXT NOT NULL, sanitized_url TEXT NOT NULL, status INTEGER, duration_ms INTEGER NOT NULL, outcome TEXT NOT NULL, error_code TEXT, error_message TEXT, preview TEXT, response_bytes INTEGER, preview_truncated INTEGER NOT NULL DEFAULT 0, content_type TEXT ,published_app_id TEXT, published_version INTEGER, user_id TEXT ); CREATE INDEX IF NOT EXISTS idx_executions_action ON executions(action_id, id DESC); CREATE INDEX IF NOT EXISTS idx_executions_outcome ON executions(outcome, id DESC); CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL, normalized_username TEXT NOT NULL UNIQUE, display_name TEXT, email TEXT, password_hash TEXT, role TEXT NOT NULL CHECK (role IN ('admin', 'user')), enabled INTEGER NOT NULL DEFAULT 1, provider TEXT NOT NULL DEFAULT 'local', provider_subject TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), last_login_at TEXT ); CREATE UNIQUE INDEX IF NOT EXISTS idx_users_provider_subject ON users(provider, provider_subject) WHERE provider_subject IS NOT NULL; CREATE TABLE IF NOT EXISTS sessions ( id_hash TEXT PRIMARY KEY, csrf_hash TEXT NOT NULL, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, issued_at TEXT NOT NULL, expires_at TEXT NOT NULL, revoked_at TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id, expires_at); CREATE TABLE IF NOT EXISTS published_apps ( id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, source_project_id INTEGER NOT NULL REFERENCES projects(id), display_name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', visibility TEXT NOT NULL CHECK (visibility IN ('public', 'authenticated')), snapshot_json TEXT NOT NULL, schema_version TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, active INTEGER NOT NULL DEFAULT 1, published_by TEXT NOT NULL REFERENCES users(id), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE INDEX IF NOT EXISTS idx_published_apps_source ON published_apps(source_project_id, active); `); const executionColumns = new Set((db.pragma('table_info(executions)') as Array<{name:string}>).map((column) => column.name)); if (!executionColumns.has('published_app_id')) db.exec('ALTER TABLE executions ADD COLUMN published_app_id TEXT'); if (!executionColumns.has('published_version')) db.exec('ALTER TABLE executions ADD COLUMN published_version INTEGER'); if (!executionColumns.has('user_id')) db.exec('ALTER TABLE executions ADD COLUMN user_id TEXT'); const publicationColumns = new Set((db.pragma('table_info(published_apps)') as Array<{name:string}>).map(column => column.name)); if (!publicationColumns.has('icon_path')) db.exec('ALTER TABLE published_apps ADD COLUMN icon_path TEXT'); const userColumns = new Set((db.pragma('table_info(users)') as Array<{name:string}>).map((column) => column.name)); if (!userColumns.has('display_name')) db.exec('ALTER TABLE users ADD COLUMN display_name TEXT'); if (!userColumns.has('email')) db.exec('ALTER TABLE users ADD COLUMN email TEXT'); if (log) console.log('Database initialised'); }