import fs from 'node:fs'; import { path } from './utils/pathUtils.js'; import { DatabaseSync } from 'node:sqlite'; import bcrypt from 'bcryptjs'; import { nanoid } from 'nanoid'; import { config } from './config.js'; import { settingDefinitions } from './settingsRegistry.js'; fs.mkdirSync(path.dirname(config.databasePath), { recursive: true }); fs.mkdirSync(config.fileLockerDir, { recursive: true }); fs.mkdirSync(config.toolsDir, { recursive: true }); export const db = new DatabaseSync(config.databasePath); db.exec('PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;'); export function id(prefix) { return `${prefix}_${nanoid(12)}`; } export function now() { return new Date().toISOString(); } export function json(value, fallback = []) { if (value == null || value === '') return JSON.stringify(fallback); return JSON.stringify(value); } export function parseJson(value, fallback = []) { try { return value ? JSON.parse(value) : fallback; } catch { return fallback; } } function ensureColumn(table, column, definition) { const columns = db.prepare(`PRAGMA table_info(${table})`).all(); if (!columns.some((row) => row.name === column)) { db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`); } } export function migrate() { db.exec(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, password_hash TEXT, auth_provider TEXT NOT NULL DEFAULT 'local', role TEXT NOT NULL DEFAULT 'user', created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS groups ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS user_groups ( user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, group_id TEXT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, PRIMARY KEY (user_id, group_id) ); CREATE TABLE IF NOT EXISTS credentials ( id TEXT PRIMARY KEY, name TEXT NOT NULL, kind TEXT NOT NULL, username TEXT, secret_cipher TEXT NOT NULL, secret_iv TEXT NOT NULL, secret_tag TEXT NOT NULL, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS hosts ( id TEXT PRIMARY KEY, name TEXT NOT NULL, fqdn TEXT, address TEXT NOT NULL, os_family TEXT NOT NULL DEFAULT 'windows', transport TEXT NOT NULL DEFAULT 'winrm', port INTEGER, credential_id TEXT REFERENCES credentials(id) ON DELETE SET NULL, tags TEXT NOT NULL DEFAULT '[]', notes TEXT, source_type TEXT NOT NULL DEFAULT 'manual', source_connection_id TEXT, source_ref TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS vcenter_connections ( id TEXT PRIMARY KEY, name TEXT NOT NULL, base_url TEXT NOT NULL, username TEXT NOT NULL, secret_cipher TEXT NOT NULL, secret_iv TEXT NOT NULL, secret_tag TEXT NOT NULL, target_type TEXT NOT NULL DEFAULT 'vcenter', api_mode TEXT NOT NULL DEFAULT 'auto', request_timeout_ms INTEGER NOT NULL DEFAULT 20000, allow_untrusted_tls INTEGER NOT NULL DEFAULT 0, enabled INTEGER NOT NULL DEFAULT 1, last_test_status TEXT, last_test_message TEXT, last_test_at TEXT, visibility TEXT NOT NULL DEFAULT 'shared', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS host_groups ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, mode TEXT NOT NULL DEFAULT 'manual', match_mode TEXT NOT NULL DEFAULT 'all', rules TEXT NOT NULL DEFAULT '[]', visibility TEXT NOT NULL DEFAULT 'shared', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, last_synced_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS host_group_members ( host_group_id TEXT NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE, host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE, source TEXT NOT NULL DEFAULT 'manual', created_at TEXT NOT NULL, PRIMARY KEY (host_group_id, host_id) ); CREATE TABLE IF NOT EXISTS folders ( id TEXT PRIMARY KEY, parent_id TEXT REFERENCES folders(id) ON DELETE CASCADE, name TEXT NOT NULL, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS scripts ( id TEXT PRIMARY KEY, folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL, name TEXT NOT NULL, description TEXT, content TEXT NOT NULL, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS asset_folders ( id TEXT PRIMARY KEY, parent_id TEXT REFERENCES asset_folders(id) ON DELETE CASCADE, name TEXT NOT NULL, description TEXT, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS assets ( id TEXT PRIMARY KEY, folder_id TEXT REFERENCES asset_folders(id) ON DELETE SET NULL, name TEXT NOT NULL, original_name TEXT NOT NULL, description TEXT, kind TEXT NOT NULL DEFAULT 'generic', mime_type TEXT, size_bytes INTEGER NOT NULL DEFAULT 0, checksum TEXT NOT NULL, storage_path TEXT NOT NULL, package_path TEXT, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS asset_links ( id TEXT PRIMARY KEY, asset_id TEXT NOT NULL REFERENCES assets(id) ON DELETE CASCADE, target_type TEXT NOT NULL, target_id TEXT NOT NULL, link_role TEXT NOT NULL DEFAULT 'reference', package_path TEXT, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS custom_variables ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, category TEXT NOT NULL DEFAULT 'Custom', value_type TEXT NOT NULL DEFAULT 'string', secret_cipher TEXT NOT NULL, secret_iv TEXT NOT NULL, secret_tag TEXT NOT NULL, sensitive INTEGER NOT NULL DEFAULT 0, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS psadt_profiles ( id TEXT PRIMARY KEY, name TEXT NOT NULL, app_vendor TEXT, app_name TEXT NOT NULL, app_version TEXT, app_arch TEXT, app_lang TEXT, app_revision TEXT, toolkit_version TEXT NOT NULL DEFAULT 'v4', template_version TEXT NOT NULL DEFAULT 'v4', deployment_type TEXT NOT NULL DEFAULT 'Install', deploy_mode TEXT NOT NULL DEFAULT 'Interactive', require_admin INTEGER NOT NULL DEFAULT 1, zero_config INTEGER NOT NULL DEFAULT 0, suppress_reboot INTEGER NOT NULL DEFAULT 0, close_processes TEXT NOT NULL DEFAULT '[]', install_tasks TEXT NOT NULL DEFAULT '[]', ui_plan TEXT NOT NULL DEFAULT '{}', config_plan TEXT NOT NULL DEFAULT '{}', admx_plan TEXT NOT NULL DEFAULT '{}', visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS intune_deployments ( id TEXT PRIMARY KEY, name TEXT NOT NULL, profile_id TEXT REFERENCES psadt_profiles(id) ON DELETE SET NULL, script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL, source_folder TEXT, intunewin_file TEXT, app_type TEXT NOT NULL DEFAULT 'Windows app (Win32)', command_style TEXT NOT NULL DEFAULT 'v4', install_behavior TEXT NOT NULL DEFAULT 'system', restart_behavior TEXT NOT NULL DEFAULT 'return-code', ui_mode TEXT NOT NULL DEFAULT 'native-v4', requirements TEXT NOT NULL DEFAULT '{}', install_command TEXT NOT NULL, uninstall_command TEXT NOT NULL, detection_type TEXT NOT NULL DEFAULT 'custom-script', detection_rule TEXT NOT NULL DEFAULT '', return_codes TEXT NOT NULL DEFAULT '[]', assignments TEXT NOT NULL DEFAULT '[]', status TEXT NOT NULL DEFAULT 'draft', graph_app_id TEXT, notes TEXT, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS graph_connections ( id TEXT PRIMARY KEY, name TEXT NOT NULL, tenant_id TEXT NOT NULL, client_id TEXT NOT NULL, secret_cipher TEXT NOT NULL, secret_iv TEXT NOT NULL, secret_tag TEXT NOT NULL, cloud TEXT NOT NULL DEFAULT 'global', graph_base_url TEXT NOT NULL DEFAULT 'https://graph.microsoft.com', authority_host TEXT NOT NULL DEFAULT 'https://login.microsoftonline.com', default_api_version TEXT NOT NULL DEFAULT 'v1.0', enabled INTEGER NOT NULL DEFAULT 1, last_test_status TEXT, last_test_message TEXT, last_test_at TEXT, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS intune_deployment_statuses ( id TEXT PRIMARY KEY, deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE, connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL, graph_app_id TEXT NOT NULL, scope TEXT NOT NULL DEFAULT 'device', principal_id TEXT, principal_name TEXT, device_id TEXT, device_name TEXT, user_id TEXT, user_principal_name TEXT, install_state TEXT, install_state_detail TEXT, error_code TEXT, error_description TEXT, last_sync_datetime TEXT, raw_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS intune_deployment_sync_runs ( id TEXT PRIMARY KEY, deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE, connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL, graph_app_id TEXT NOT NULL, status TEXT NOT NULL, message TEXT, summary TEXT NOT NULL DEFAULT '{}', started_at TEXT NOT NULL, ended_at TEXT ); CREATE TABLE IF NOT EXISTS applications ( id TEXT PRIMARY KEY, name TEXT NOT NULL, vendor TEXT, publisher TEXT, current_version TEXT, latest_known_version TEXT, version_source TEXT NOT NULL DEFAULT 'manual', version_source_ref TEXT, graph_connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL, current_graph_app_id TEXT, auto_check INTEGER NOT NULL DEFAULT 0, last_checked_at TEXT, last_check_message TEXT, notes TEXT, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS change_requests ( id TEXT PRIMARY KEY, deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE CASCADE, connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL, action TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', requested_by TEXT REFERENCES users(id) ON DELETE SET NULL, requester_email TEXT, approver_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, approver_email TEXT, reason TEXT, decided_at TEXT, executed_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS graph_audit_log ( id TEXT PRIMARY KEY, connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL, deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE SET NULL, actor_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, actor_email TEXT, action TEXT NOT NULL, target_graph_id TEXT, status TEXT NOT NULL, message TEXT, request_json TEXT NOT NULL DEFAULT '{}', response_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS script_versions ( id TEXT PRIMARY KEY, script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE, version INTEGER NOT NULL, content TEXT NOT NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS runplans ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT, script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE, visibility TEXT NOT NULL DEFAULT 'personal', owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL, group_id TEXT REFERENCES groups(id) ON DELETE SET NULL, parallel INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS runplan_hosts ( runplan_id TEXT NOT NULL REFERENCES runplans(id) ON DELETE CASCADE, host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE, PRIMARY KEY (runplan_id, host_id) ); CREATE TABLE IF NOT EXISTS runplan_host_groups ( runplan_id TEXT NOT NULL REFERENCES runplans(id) ON DELETE CASCADE, host_group_id TEXT NOT NULL REFERENCES host_groups(id) ON DELETE CASCADE, PRIMARY KEY (runplan_id, host_group_id) ); CREATE TABLE IF NOT EXISTS jobs ( id TEXT PRIMARY KEY, runplan_id TEXT REFERENCES runplans(id) ON DELETE SET NULL, script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL, status TEXT NOT NULL, triggered_by TEXT REFERENCES users(id) ON DELETE SET NULL, started_at TEXT, ended_at TEXT, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS job_hosts ( id TEXT PRIMARY KEY, job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL, status TEXT NOT NULL, exit_code INTEGER, started_at TEXT, ended_at TEXT ); CREATE TABLE IF NOT EXISTS job_logs ( id TEXT PRIMARY KEY, job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL, stream TEXT NOT NULL, line TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, source TEXT NOT NULL DEFAULT 'db', updated_at TEXT NOT NULL ); `); ensureColumn('users', 'theme', "TEXT NOT NULL DEFAULT 'dashtreme'"); ensureColumn('users', 'job_title', 'TEXT'); ensureColumn('users', 'phone', 'TEXT'); ensureColumn('users', 'timezone', 'TEXT'); ensureColumn('users', 'avatar_url', 'TEXT'); ensureColumn('users', 'updated_at', 'TEXT'); ensureColumn('intune_deployments', 'source_folder', 'TEXT'); ensureColumn('intune_deployments', 'intunewin_file', 'TEXT'); ensureColumn('intune_deployments', 'command_style', "TEXT NOT NULL DEFAULT 'v4'"); ensureColumn('intune_deployments', 'install_behavior', "TEXT NOT NULL DEFAULT 'system'"); ensureColumn('intune_deployments', 'restart_behavior', "TEXT NOT NULL DEFAULT 'return-code'"); ensureColumn('intune_deployments', 'ui_mode', "TEXT NOT NULL DEFAULT 'native-v4'"); ensureColumn('intune_deployments', 'requirements', "TEXT NOT NULL DEFAULT '{}'"); ensureColumn('intune_deployments', 'graph_app_id', 'TEXT'); ensureColumn('intune_deployments', 'graph_connection_id', 'TEXT'); ensureColumn('intune_deployments', 'relationships', "TEXT NOT NULL DEFAULT '[]'"); ensureColumn('intune_deployments', 'application_id', 'TEXT REFERENCES applications(id) ON DELETE SET NULL'); // Auto-promote: after the soak window elapses, advance a rollout ring's intent. ensureColumn('intune_deployments', 'auto_promote_after_hours', 'INTEGER NOT NULL DEFAULT 0'); ensureColumn('intune_deployments', 'auto_promote_ring', 'TEXT'); ensureColumn('intune_deployments', 'auto_promote_intent', "TEXT NOT NULL DEFAULT 'required'"); ensureColumn('intune_deployments', 'soak_started_at', 'TEXT'); ensureColumn('intune_deployments', 'promoted_at', 'TEXT'); // Host visibility scoping. Existing rows default to 'shared' so prior global // hosts remain visible to everyone; new hosts can be personal/group-scoped. ensureColumn('hosts', 'visibility', "TEXT NOT NULL DEFAULT 'shared'"); ensureColumn('hosts', 'owner_user_id', 'TEXT REFERENCES users(id) ON DELETE SET NULL'); ensureColumn('hosts', 'group_id', 'TEXT REFERENCES groups(id) ON DELETE SET NULL'); // Host provenance is hidden from normal manual edit forms but used by // automation gates such as vCenter power-state checks before execution. ensureColumn('hosts', 'source_type', "TEXT NOT NULL DEFAULT 'manual'"); ensureColumn('hosts', 'source_connection_id', 'TEXT'); ensureColumn('hosts', 'source_ref', 'TEXT'); // VMware connection type separates vCenter inventory/power-state APIs from // standalone ESXi host checks that use the vSphere Web Services SOAP API. ensureColumn('vcenter_connections', 'target_type', "TEXT NOT NULL DEFAULT 'vcenter'"); // Tenant write-back is opt-in per Graph connection. Default 0 so existing // read-only connections can never be used to change a tenant by accident. ensureColumn('graph_connections', 'allow_write', 'INTEGER NOT NULL DEFAULT 0'); // When set, tenant-changing actions through this connection require an // approved change request before they run. ensureColumn('graph_connections', 'require_approval', 'INTEGER NOT NULL DEFAULT 0'); // Optional named-RBAC lists (JSON arrays of user ids). Empty = no restriction // beyond the allow_write/admin gates. ensureColumn('graph_connections', 'publisher_ids', "TEXT NOT NULL DEFAULT '[]'"); ensureColumn('graph_connections', 'approver_ids', "TEXT NOT NULL DEFAULT '[]'"); } export function seed() { const userCount = db.prepare('SELECT COUNT(*) AS count FROM users').get().count; if (userCount === 0) { const adminId = id('usr'); db.prepare(` INSERT INTO users (id, email, display_name, password_hash, auth_provider, role, created_at) VALUES (?, ?, ?, ?, 'local', 'admin', ?) `).run( adminId, config.defaultAdminEmail.toLowerCase(), config.defaultAdminName, bcrypt.hashSync(config.defaultAdminPassword, 12), now() ); const groupId = id('grp'); db.prepare('INSERT INTO groups (id, name, description, created_at) VALUES (?, ?, ?, ?)').run( groupId, 'Operations', 'Default shared operations group', now() ); db.prepare('INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)').run(adminId, groupId); const folderId = id('fld'); db.prepare(` INSERT INTO folders (id, parent_id, name, visibility, owner_user_id, group_id, created_at, updated_at) VALUES (?, NULL, 'Examples', 'shared', ?, NULL, ?, ?) `).run(folderId, adminId, now(), now()); const scriptId = id('scr'); const sample = [ 'Write-Output "POSHManager sample run"', 'Write-Output "Target host: $env:POSHM_HOST_NAME"', 'Get-Date' ].join('\n'); db.prepare(` INSERT INTO scripts (id, folder_id, name, description, content, visibility, owner_user_id, group_id, version, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'shared', ?, NULL, 1, ?, ?) `).run(scriptId, folderId, 'Sample Health Check.ps1', 'Simple smoke-test script for local execution.', sample, adminId, now(), now()); db.prepare(` INSERT INTO script_versions (id, script_id, version, content, created_by, created_at) VALUES (?, ?, 1, ?, ?, ?) `).run(id('ver'), scriptId, sample, adminId, now()); } // Env-pinned settings are refreshed from the environment on every boot and // marked source='env' (locked in the UI). Unpinned settings are seeded once // as editable defaults so admin edits in the Config screen survive restarts. const pinned = db.prepare(` INSERT INTO settings (key, value, source, updated_at) VALUES (?, ?, 'env', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, source = 'env', updated_at = excluded.updated_at `); const defaulted = db.prepare(` INSERT INTO settings (key, value, source, updated_at) VALUES (?, ?, 'default', ?) ON CONFLICT(key) DO NOTHING `); for (const def of settingDefinitions()) { if (def.pinned) pinned.run(def.key, def.value, now()); else defaulted.run(def.key, def.value, now()); } } export function visibleClause(alias = '') { const p = alias ? `${alias}.` : ''; return `(${p}visibility = 'shared' OR ${p}owner_user_id = ? OR ${p}group_id IN (SELECT group_id FROM user_groups WHERE user_id = ?))`; }