120 lines
5.4 KiB
TypeScript

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');
}