Files
stackpulse/backend/db/migrate.js
T
2025-11-06 11:07:57 +00:00

1014 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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', '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();