import { db } from './index.js'; db.exec('PRAGMA foreign_keys = ON;'); const createEventLogsTable = ` CREATE TABLE IF NOT EXISTS event_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, category TEXT NOT NULL, event_type TEXT, action TEXT, status TEXT, severity TEXT, entity_type TEXT, entity_id TEXT, entity_name TEXT, actor_type TEXT, actor_id TEXT, actor_name TEXT, source TEXT, context_type TEXT, context_id TEXT, context_label TEXT, message TEXT, metadata TEXT ); `; const createSettingsTable = ` CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ); `; const createUsersTable = ` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, email TEXT UNIQUE, password_hash TEXT NOT NULL, password_salt TEXT, avatar_color TEXT, is_active INTEGER NOT NULL DEFAULT 1, last_login DATETIME, security_phrase_content TEXT, security_phrase_iv TEXT, security_phrase_tag TEXT, security_phrase_downloaded_at DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `; const createUserGroupsTable = ` CREATE TABLE IF NOT EXISTS user_groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, description TEXT, avatar_color TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `; const createUserGroupMembershipsTable = ` CREATE TABLE IF NOT EXISTS user_group_memberships ( user_id INTEGER NOT NULL, group_id INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, group_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE ); `; const createPermissionsTable = ` CREATE TABLE IF NOT EXISTS permissions ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `; const createUserGroupPermissionsTable = ` CREATE TABLE IF NOT EXISTS user_group_permissions ( group_id INTEGER NOT NULL, permission_id INTEGER NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (group_id, permission_id), FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ); `; const createPermissionSectionsTable = ` CREATE TABLE IF NOT EXISTS permission_sections ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, title TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, has_navigation_flag INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `; const createPermissionGroupsTable = ` CREATE TABLE IF NOT EXISTS permission_groups ( id INTEGER PRIMARY KEY AUTOINCREMENT, section_id INTEGER NOT NULL, key TEXT NOT NULL, title TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(section_id, key), FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE ); `; const createPermissionItemsTable = ` CREATE TABLE IF NOT EXISTS permission_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, section_id INTEGER NOT NULL, group_id INTEGER, key TEXT NOT NULL UNIQUE, label TEXT NOT NULL, sort_order INTEGER NOT NULL DEFAULT 0, default_level TEXT NOT NULL, available_levels TEXT NOT NULL, is_required INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE, FOREIGN KEY (group_id) REFERENCES permission_groups(id) ON DELETE SET NULL ); `; const createPermissionDependenciesTable = ` CREATE TABLE IF NOT EXISTS permission_dependencies ( id INTEGER PRIMARY KEY AUTOINCREMENT, permission_id INTEGER NOT NULL, depends_on_permission_id INTEGER NOT NULL, required_level TEXT DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (permission_id) REFERENCES permission_items(id) ON DELETE CASCADE, FOREIGN KEY (depends_on_permission_id) REFERENCES permission_items(id) ON DELETE CASCADE, UNIQUE(permission_id, depends_on_permission_id) ); `; const createPermissionDependenciesIndex = ` CREATE INDEX IF NOT EXISTS idx_permission_dependencies_required ON permission_dependencies (permission_id, depends_on_permission_id); `; const createGroupPermissionValuesTable = ` CREATE TABLE IF NOT EXISTS group_permission_values ( group_id INTEGER NOT NULL, permission_id INTEGER NOT NULL, level TEXT NOT NULL, effective_level TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (group_id, permission_id), FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permission_items(id) ON DELETE CASCADE ); `; const createServersTable = ` CREATE TABLE IF NOT EXISTS servers ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL UNIQUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `; const createEndpointsTable = ` CREATE TABLE IF NOT EXISTS endpoints ( id INTEGER PRIMARY KEY AUTOINCREMENT, server_id INTEGER NOT NULL, name TEXT NOT NULL, external_id TEXT NOT NULL, is_default INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE, UNIQUE (server_id, external_id) ); `; const createUserServerPermissionOverridesTable = ` CREATE TABLE IF NOT EXISTS user_server_permission_overrides ( user_id INTEGER NOT NULL, server_id INTEGER NOT NULL, permission_id INTEGER NOT NULL, change_type TEXT NOT NULL CHECK (change_type IN ('ADD', 'REMOVE')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, server_id, permission_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ); `; const createUserEndpointPermissionOverridesTable = ` CREATE TABLE IF NOT EXISTS user_endpoint_permission_overrides ( user_id INTEGER NOT NULL, endpoint_id INTEGER NOT NULL, permission_id INTEGER NOT NULL, change_type TEXT NOT NULL CHECK (change_type IN ('ADD', 'REMOVE')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, endpoint_id, permission_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (endpoint_id) REFERENCES endpoints(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE ); `; const createUserServerPermissionOverridesIndex = ` CREATE INDEX IF NOT EXISTS idx_user_server_permission_overrides_user_server ON user_server_permission_overrides (user_id, server_id); `; const createUserEndpointPermissionOverridesIndex = ` CREATE INDEX IF NOT EXISTS idx_user_endpoint_permission_overrides_user_endpoint ON user_endpoint_permission_overrides (user_id, endpoint_id); `; const createServerApiKeysTable = ` CREATE TABLE IF NOT EXISTS server_api_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, server_id INTEGER NOT NULL UNIQUE, key_cipher TEXT NOT NULL, key_iv TEXT NOT NULL, key_tag TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE ); `; const createUserSettingsTable = ` CREATE TABLE IF NOT EXISTS user_settings ( user_id INTEGER NOT NULL, setting_key TEXT NOT NULL, setting_value TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id, setting_key), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); `; const tableExists = (tableName) => { return Boolean( db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = ?") .get(tableName) ); }; const serializeMetadata = (payload) => { if (!payload || typeof payload !== 'object' || !Object.keys(payload).length) { return null; } try { return JSON.stringify(payload); } catch (err) { console.error('⚠️ Konnte Migrations-Metadaten nicht serialisieren:', err.message); return null; } }; db.exec(createEventLogsTable); db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_timestamp ON event_logs (timestamp DESC);'); db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_category ON event_logs (category);'); db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_event_type ON event_logs (event_type);'); db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_entity ON event_logs (entity_type, entity_id);'); db.exec('CREATE INDEX IF NOT EXISTS idx_event_logs_context ON event_logs (context_type, context_id);'); console.log('✅ event_logs table ready'); if (tableExists('redeploy_logs')) { try { const legacyRows = db.prepare(` SELECT id, timestamp, stack_id, stack_name, status, message, endpoint, redeploy_type FROM redeploy_logs ORDER BY id ASC `).all(); if (legacyRows.length) { const insertEventLog = db.prepare(` INSERT INTO event_logs ( id, timestamp, category, event_type, action, status, entity_type, entity_id, entity_name, context_type, context_id, message, metadata ) VALUES ( @id, @timestamp, 'stack', @eventType, 'redeploy', @status, 'stack', @entityId, @entityName, @contextType, @contextId, @message, @metadata ) `); const migrate = db.transaction((rows) => { rows.forEach((row) => { const contextType = row.endpoint !== null && row.endpoint !== undefined ? 'endpoint' : null; const contextId = contextType ? String(row.endpoint) : null; const metadataPayload = { origin: 'redeploy_logs' }; if (row.redeploy_type) { metadataPayload.redeployType = row.redeploy_type; } if (contextId) { metadataPayload.endpointId = contextId; } insertEventLog.run({ id: row.id, timestamp: row.timestamp, eventType: row.redeploy_type ?? null, status: row.status ?? null, entityId: row.stack_id !== undefined && row.stack_id !== null ? String(row.stack_id) : null, entityName: row.stack_name ?? null, contextType, contextId, message: row.message ?? null, metadata: serializeMetadata(metadataPayload) }); }); }); migrate(legacyRows); console.log(`ℹ️ ${legacyRows.length} Einträge aus redeploy_logs migriert`); } db.exec('DROP TABLE redeploy_logs;'); console.log('ℹ️ Alte Tabelle redeploy_logs entfernt'); } catch (err) { console.error('⚠️ Migration von redeploy_logs fehlgeschlagen:', err.message); } } db.exec(createSettingsTable); console.log('✅ settings table ready'); db.exec(createUsersTable); console.log('✅ users table ready'); try { const userColumns = db.prepare('PRAGMA table_info(users)').all(); const hasAvatarColor = userColumns.some((column) => column.name === 'avatar_color'); if (!hasAvatarColor) { db.exec('ALTER TABLE users ADD COLUMN avatar_color TEXT'); console.log('ℹ️ avatar_color column in Tabelle users hinzugefügt'); } const userGroupColumns = db.prepare('PRAGMA table_info(user_groups)').all(); const hasAvatarColorGroup = userGroupColumns.some((column) => column.name === 'avatar_color'); if (!hasAvatarColorGroup) { db.exec('ALTER TABLE user_groups ADD COLUMN avatar_color TEXT'); console.log('ℹ️ avatar_color column in Tabelle user_groups hinzugefügt'); } const emailColumn = userColumns.find((column) => column.name === 'email'); if (emailColumn && emailColumn.notnull === 1) { try { db.exec('PRAGMA foreign_keys = OFF;'); db.exec('DROP TABLE IF EXISTS users_backup;'); db.exec('ALTER TABLE users RENAME TO users_backup;'); db.exec(` CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, email TEXT UNIQUE, password_hash TEXT NOT NULL, password_salt TEXT, avatar_color TEXT, is_active INTEGER NOT NULL DEFAULT 1, last_login DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); db.exec(` INSERT INTO users (id, username, email, password_hash, password_salt, avatar_color, is_active, last_login, created_at, updated_at) SELECT id, username, email, password_hash, password_salt, avatar_color, is_active, last_login, created_at, updated_at FROM users_backup; `); db.exec('DROP TABLE IF EXISTS users_backup;'); console.log('ℹ️ users.email Spalte erlaubt jetzt NULL-Werte'); } catch (migrationError) { console.error('⚠️ Umbau der users Tabelle fehlgeschlagen:', migrationError.message); try { db.exec('ALTER TABLE users_backup RENAME TO users;'); } catch (restoreError) { console.error('⚠️ Konnte ursprüngliche users Tabelle nicht wiederherstellen:', restoreError.message); } } finally { db.exec('PRAGMA foreign_keys = ON;'); } } } catch (err) { console.error('⚠️ Konnte avatar_color Spalte nicht prüfen/erstellen:', err.message); } db.exec(createUserGroupsTable); console.log('✅ user_groups table ready'); db.exec(createUserGroupMembershipsTable); console.log('✅ user_group_memberships table ready'); db.exec(createPermissionsTable); console.log('✅ permissions table ready'); db.exec(createUserGroupPermissionsTable); console.log('✅ user_group_permissions table ready'); db.exec(createPermissionSectionsTable); console.log('✅ permission_sections table ready'); db.exec(createPermissionGroupsTable); console.log('✅ permission_groups table ready'); db.exec(createPermissionItemsTable); console.log('✅ permission_items table ready'); db.exec(createPermissionDependenciesTable); db.exec(createPermissionDependenciesIndex); console.log('✅ permission_dependencies table ready'); db.exec(createGroupPermissionValuesTable); console.log('✅ group_permission_values table ready'); const permissionsSeeded = () => { try { const row = db.prepare('SELECT COUNT(*) as count FROM permission_sections').get(); return row?.count > 0; } catch (err) { return false; } }; const seedPermissions = () => { if (permissionsSeeded()) { console.log('ℹ️ permission structure already seeded'); return; } const insertSection = db.prepare(` INSERT INTO permission_sections (key, title, sort_order, has_navigation_flag) VALUES (@key, @title, @sort_order, @has_navigation_flag) `); const insertGroup = db.prepare(` INSERT INTO permission_groups (section_id, key, title, sort_order) VALUES (@section_id, @key, @title, @sort_order) `); const insertItem = db.prepare(` INSERT INTO permission_items ( section_id, group_id, key, label, sort_order, default_level, available_levels, is_required ) VALUES ( @section_id, @group_id, @key, @label, @sort_order, @default_level, @available_levels, @is_required ) `); const insertDependency = db.prepare(` INSERT INTO permission_dependencies (permission_id, depends_on_permission_id, required_level) VALUES (@permission_id, @depends_on_permission_id, @required_level) `); const sections = [ { key: 'stacks', title: 'Stacks', sortOrder: 0, hasNavigation: 0, groups: [], items: [ { key: 'stacks-redeploy-single', label: 'Redeploy einzeln', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [] }, { key: 'stacks-redeploy-selection', label: 'Redeploy Auswahl', sortOrder: 1, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [] }, { key: 'stacks-redeploy-all', label: 'Redeploy Alle', sortOrder: 2, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [] } ] }, { key: 'logs', title: 'Logs', sortOrder: 1, hasNavigation: 1, groups: [], items: [ { key: 'logs-access', label: 'Bereich & Navigation', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 1, dependencies: [] }, { key: 'logs-export', label: 'Logs Exportieren', sortOrder: 1, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'logs-access', requiredLevel: '!=none' } ] }, { key: 'logs-delete', label: 'Logs löschen', sortOrder: 2, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'logs-access', requiredLevel: '!=none' } ] } ] }, { key: 'users', title: 'Benutzer', sortOrder: 2, hasNavigation: 1, groups: [], items: [ { key: 'users-access', label: 'Bereich & Navigation', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 1, dependencies: [] }, { key: 'users-edit', label: 'Benutzer bearbeiten', sortOrder: 1, defaultLevel: 'read', levels: ['full', 'read'], isRequired: 0, dependencies: [ { dependsOnKey: 'users-access', requiredLevel: '!=none' } ] }, { key: 'users-delete', label: 'Benutzer löschen', sortOrder: 2, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'users-access', requiredLevel: '!=none' } ] }, { key: 'users-security-phrase', label: 'Sicherheitsschlüssel', sortOrder: 3, defaultLevel: 'none', levels: ['full', 'read', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'users-access', requiredLevel: '!=none' } ] } ] }, { key: 'user-groups', title: 'Benutzergruppen', sortOrder: 3, hasNavigation: 1, groups: [], items: [ { key: 'user-groups-access', label: 'Bereich & Navigation', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 1, dependencies: [] }, { key: 'user-groups-edit', label: 'Benutzergruppen bearbeiten', sortOrder: 1, defaultLevel: 'read', levels: ['full', 'read'], isRequired: 0, dependencies: [ { dependsOnKey: 'user-groups-access', requiredLevel: '!=none' } ] }, { key: 'user-groups-delete', label: 'Benutzergruppen löschen', sortOrder: 2, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'user-groups-access', requiredLevel: '!=none' } ] } ] }, { key: 'maintenance', title: 'Wartung', sortOrder: 4, hasNavigation: 1, groups: [ { key: 'maintenance-server-group', title: 'Server & Endpoints', sortOrder: 0, items: [ { key: 'maintenance-server-manage', label: 'Server/Endpoint-Sektion', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'read', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' } ] }, { key: 'maintenance-server-delete', label: 'Server/Endpoint löschen', sortOrder: 1, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }, { dependsOnKey: 'maintenance-server-manage', requiredLevel: '!=none' } ] } ] }, { key: 'maintenance-portainer-group', title: 'Portainer', sortOrder: 1, items: [ { key: 'maintenance-portainer', label: 'Portainer-Sektion', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' } ] }, { key: 'maintenance-ssh-update', label: 'SSH/Update-Skript', sortOrder: 1, defaultLevel: 'none', levels: ['full', 'read', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }, { dependsOnKey: 'maintenance-portainer', requiredLevel: '!=none' } ] }, { key: 'maintenance-update', label: 'Update durchführen', sortOrder: 2, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }, { dependsOnKey: 'maintenance-portainer', requiredLevel: '!=none' } ] } ] }, { key: 'maintenance-duplicates-group', title: 'Doppelte Stacks', sortOrder: 2, items: [ { key: 'maintenance-duplicates', label: 'Doppelte Stacks', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'read', 'none'], isRequired: 0, dependencies: [ { dependsOnKey: 'maintenance-access', requiredLevel: '!=none' } ] } ] } ], items: [ { key: 'maintenance-access', label: 'Bereich & Navigation', sortOrder: 0, defaultLevel: 'none', levels: ['full', 'none'], isRequired: 1, dependencies: [] } ] } ]; const itemKeyToId = new Map(); const insertPermissions = db.transaction(() => { sections.forEach((section) => { const sectionResult = insertSection.run({ key: section.key, title: section.title, sort_order: section.sortOrder, has_navigation_flag: section.hasNavigation ? 1 : 0 }); const sectionId = sectionResult.lastInsertRowid; const sectionItems = section.items ?? []; sectionItems.forEach((item, index) => { const itemResult = insertItem.run({ section_id: sectionId, group_id: null, key: item.key, label: item.label, sort_order: item.sortOrder ?? index, default_level: item.defaultLevel, available_levels: JSON.stringify(item.levels), is_required: item.isRequired ? 1 : 0 }); itemKeyToId.set(item.key, itemResult.lastInsertRowid); }); const groups = section.groups ?? []; groups.forEach((group, groupIdx) => { const groupResult = insertGroup.run({ section_id: sectionId, key: group.key, title: group.title, sort_order: group.sortOrder ?? groupIdx }); const groupId = groupResult.lastInsertRowid; const groupItems = group.items ?? []; groupItems.forEach((item, itemIdx) => { const itemResult = insertItem.run({ section_id: sectionId, group_id: groupId, key: item.key, label: item.label, sort_order: item.sortOrder ?? itemIdx, default_level: item.defaultLevel, available_levels: JSON.stringify(item.levels), is_required: item.isRequired ? 1 : 0 }); itemKeyToId.set(item.key, itemResult.lastInsertRowid); }); }); }); sections.forEach((section) => { const collectDependencies = (items = []) => { items.forEach((item) => { if (!item.dependencies || !item.dependencies.length) { return; } const permissionId = itemKeyToId.get(item.key); item.dependencies.forEach((dependency) => { const dependsId = itemKeyToId.get(dependency.dependsOnKey); if (!dependsId) { console.warn(`⚠️ dependency target ${dependency.dependsOnKey} not found for ${item.key}`); return; } insertDependency.run({ permission_id: permissionId, depends_on_permission_id: dependsId, required_level: dependency.requiredLevel ?? null }); }); }); }; collectDependencies(section.items); (section.groups ?? []).forEach((group) => collectDependencies(group.items)); }); }); insertPermissions(); console.log('✅ permission structure seeded'); }; seedPermissions(); const ensureUserSecurityPhraseColumns = () => { const columns = db.prepare('PRAGMA table_info(users)').all(); const columnNames = new Set(columns.map((column) => column.name)); const addColumnIfMissing = (name, sql) => { if (!columnNames.has(name)) { db.exec(sql); } }; addColumnIfMissing('security_phrase_content', 'ALTER TABLE users ADD COLUMN security_phrase_content TEXT'); addColumnIfMissing('security_phrase_iv', 'ALTER TABLE users ADD COLUMN security_phrase_iv TEXT'); addColumnIfMissing('security_phrase_tag', 'ALTER TABLE users ADD COLUMN security_phrase_tag TEXT'); addColumnIfMissing('security_phrase_downloaded_at', 'ALTER TABLE users ADD COLUMN security_phrase_downloaded_at DATETIME'); }; ensureUserSecurityPhraseColumns(); const ensurePermissionDefaultLevels = () => { const desiredDefaults = [ { key: 'stacks-redeploy-single', defaultLevel: 'none' }, { key: 'stacks-redeploy-selection', defaultLevel: 'none' }, { key: 'stacks-redeploy-all', defaultLevel: 'none' }, { key: 'logs-access', defaultLevel: 'none' }, { key: 'logs-export', defaultLevel: 'none' }, { key: 'logs-delete', defaultLevel: 'none' }, { key: 'users-access', defaultLevel: 'none' }, { key: 'users-edit', defaultLevel: 'read' }, { key: 'users-delete', defaultLevel: 'none' }, { key: 'users-security-phrase', defaultLevel: 'none' }, { key: 'user-groups-access', defaultLevel: 'none' }, { key: 'user-groups-edit', defaultLevel: 'read' }, { key: 'user-groups-delete', defaultLevel: 'none' }, { key: 'maintenance-access', defaultLevel: 'none' }, { key: 'maintenance-server-manage', defaultLevel: 'none' }, { key: 'maintenance-server-delete', defaultLevel: 'none' }, { key: 'maintenance-portainer', defaultLevel: 'none' }, { key: 'maintenance-ssh-update', defaultLevel: 'none' }, { key: 'maintenance-update', defaultLevel: 'none' }, { key: 'maintenance-duplicates', defaultLevel: 'none' } ]; const updateDefaultLevel = db.prepare(` UPDATE permission_items SET default_level = @default_level WHERE key = @key AND default_level != @default_level `); db.transaction(() => { desiredDefaults.forEach(({ key, defaultLevel }) => { updateDefaultLevel.run({ key, default_level: defaultLevel }); }); })(); }; ensurePermissionDefaultLevels(); db.exec( "DELETE FROM group_permission_values WHERE group_id IN (SELECT id FROM user_groups WHERE lower(name) = 'superuser')" ); db.exec(createServersTable); console.log('✅ servers table ready'); db.exec(createEndpointsTable); console.log('✅ endpoints table ready'); db.exec(createUserServerPermissionOverridesTable); console.log('✅ user_server_permission_overrides table ready'); db.exec(createUserEndpointPermissionOverridesTable); console.log('✅ user_endpoint_permission_overrides table ready'); db.exec(createUserServerPermissionOverridesIndex); db.exec(createUserEndpointPermissionOverridesIndex); db.exec(createServerApiKeysTable); console.log('✅ server_api_keys table ready'); db.exec(createUserSettingsTable); console.log('✅ user_settings table ready'); db.close();