DB-Skript Update
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+227
@@ -0,0 +1,227 @@
|
|||||||
|
-- Stackpulse database schema blueprint
|
||||||
|
-- This document captures the expected structure of all tables, indexes and core seed requirements.
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_event_logs_timestamp ON event_logs (timestamp DESC);
|
||||||
|
CREATE INDEX idx_event_logs_category ON event_logs (category);
|
||||||
|
CREATE INDEX idx_event_logs_event_type ON event_logs (event_type);
|
||||||
|
CREATE INDEX idx_event_logs_entity ON event_logs (entity_type, entity_id);
|
||||||
|
CREATE INDEX idx_event_logs_context ON event_logs (context_type, context_id);
|
||||||
|
|
||||||
|
CREATE TABLE settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT,
|
||||||
|
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_permission_dependencies_required
|
||||||
|
ON permission_dependencies (permission_id, depends_on_permission_id);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_user_server_permission_overrides_user_server
|
||||||
|
ON user_server_permission_overrides (user_id, server_id);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_user_endpoint_permission_overrides_user_endpoint
|
||||||
|
ON user_endpoint_permission_overrides (user_id, endpoint_id);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE 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
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Core seed expectations (keys only – maintained via schemaEnsure)
|
||||||
|
-- permission_sections: stacks, logs, users, user-groups, maintenance
|
||||||
|
-- permission_items: includes users-security-phrase (available_levels ["full","none"])
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ensureDatabaseSchema } from './schemaEnsure.js';
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
try {
|
||||||
|
ensureDatabaseSchema();
|
||||||
|
console.log('✅ Datenbankschema abgeglichen.');
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('⚠️ Schema-Abgleich fehlgeschlagen:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,710 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { db } from './index.js';
|
||||||
|
|
||||||
|
const BLUEPRINT_FILENAME = 'dbs';
|
||||||
|
const PERMISSION_BLUEPRINT = [
|
||||||
|
{
|
||||||
|
key: 'stacks',
|
||||||
|
title: 'Stacks',
|
||||||
|
sortOrder: 0,
|
||||||
|
hasNavigation: 0,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: 'stacks-redeploy-single',
|
||||||
|
label: 'Redeploy einzeln',
|
||||||
|
sortOrder: 0,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stacks-redeploy-selection',
|
||||||
|
label: 'Redeploy Auswahl',
|
||||||
|
sortOrder: 1,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'stacks-redeploy-all',
|
||||||
|
label: 'Redeploy Alle',
|
||||||
|
sortOrder: 2,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'logs',
|
||||||
|
title: 'Logs',
|
||||||
|
sortOrder: 1,
|
||||||
|
hasNavigation: 1,
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'logs-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'logs-delete',
|
||||||
|
label: 'Logs löschen',
|
||||||
|
sortOrder: 2,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'logs-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'users',
|
||||||
|
title: 'Benutzer',
|
||||||
|
sortOrder: 2,
|
||||||
|
hasNavigation: 1,
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'users-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'users-delete',
|
||||||
|
label: 'Benutzer löschen',
|
||||||
|
sortOrder: 2,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'users-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'users-security-phrase',
|
||||||
|
label: 'Sicherheitsschlüssel',
|
||||||
|
sortOrder: 3,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'users-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'user-groups',
|
||||||
|
title: 'Benutzergruppen',
|
||||||
|
sortOrder: 3,
|
||||||
|
hasNavigation: 1,
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'user-groups-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'user-groups-delete',
|
||||||
|
label: 'Benutzergruppen löschen',
|
||||||
|
sortOrder: 2,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'user-groups-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maintenance',
|
||||||
|
title: 'Wartung',
|
||||||
|
sortOrder: 4,
|
||||||
|
hasNavigation: 1,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
key: 'maintenance-access',
|
||||||
|
label: 'Bereich & Navigation',
|
||||||
|
sortOrder: 0,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
isRequired: 1,
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maintenance-server-delete',
|
||||||
|
label: 'Server/Endpoint löschen',
|
||||||
|
sortOrder: 1,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'none'],
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'maintenance-ssh-update',
|
||||||
|
label: 'SSH/Update-Skript',
|
||||||
|
sortOrder: 1,
|
||||||
|
defaultLevel: 'none',
|
||||||
|
levels: ['full', 'read', 'none'],
|
||||||
|
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'],
|
||||||
|
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'],
|
||||||
|
dependencies: [
|
||||||
|
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const TABLE_REGEX = /^CREATE TABLE\s+([^\s(]+)\s*\(/i;
|
||||||
|
const INDEX_REGEX = /^CREATE INDEX\s+([^\s(]+)\s+ON\s+([^\s(]+)\s*\(/i;
|
||||||
|
const COLUMN_SKIP_PREFIXES = ['PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE', 'CHECK', 'CONSTRAINT'];
|
||||||
|
|
||||||
|
const normalizeIdentifier = (value) => value.replace(/`|"/g, '').trim();
|
||||||
|
|
||||||
|
const loadBlueprintStatements = () => {
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const filePath = path.join(__dirname, BLUEPRINT_FILENAME);
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const cleanContent = content
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line && !line.startsWith('--'))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return cleanContent
|
||||||
|
.split(';')
|
||||||
|
.map((statement) => statement.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseTableDefinitions = (statements) => {
|
||||||
|
const tables = new Map();
|
||||||
|
|
||||||
|
statements.forEach((statement) => {
|
||||||
|
const match = statement.match(TABLE_REGEX);
|
||||||
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableName = normalizeIdentifier(match[1]);
|
||||||
|
const body = statement.slice(statement.indexOf('(') + 1, statement.lastIndexOf(')'));
|
||||||
|
const lines = body
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const columns = [];
|
||||||
|
lines.forEach((line) => {
|
||||||
|
const cleaned = line.replace(/,$/, '');
|
||||||
|
const upper = cleaned.toUpperCase();
|
||||||
|
if (COLUMN_SKIP_PREFIXES.some((prefix) => upper.startsWith(prefix))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [rawName, ...restParts] = cleaned.split(/\s+/);
|
||||||
|
if (!rawName || restParts.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = normalizeIdentifier(rawName);
|
||||||
|
const definition = restParts.join(' ');
|
||||||
|
columns.push({ name: columnName, definition });
|
||||||
|
});
|
||||||
|
|
||||||
|
tables.set(tableName, {
|
||||||
|
createStatement: statement,
|
||||||
|
columns
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseIndexStatements = (statements) => {
|
||||||
|
const indexes = [];
|
||||||
|
statements.forEach((statement) => {
|
||||||
|
const match = statement.match(INDEX_REGEX);
|
||||||
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const indexName = normalizeIdentifier(match[1]);
|
||||||
|
indexes.push({
|
||||||
|
name: indexName,
|
||||||
|
statement
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return indexes;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableExists = (tableName) => {
|
||||||
|
const result = db
|
||||||
|
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1`)
|
||||||
|
.get(tableName);
|
||||||
|
return Boolean(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExistingColumns = (tableName) => {
|
||||||
|
const pragma = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
||||||
|
return new Set(pragma.map((column) => column.name));
|
||||||
|
};
|
||||||
|
|
||||||
|
const indexExists = (indexName) => {
|
||||||
|
const result = db
|
||||||
|
.prepare(`SELECT name FROM sqlite_master WHERE type = 'index' AND name = ? LIMIT 1`)
|
||||||
|
.get(indexName);
|
||||||
|
return Boolean(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureTablesAndColumns = (tables) => {
|
||||||
|
tables.forEach(({ createStatement, columns }, tableName) => {
|
||||||
|
if (!tableExists(tableName)) {
|
||||||
|
const statementWithGuard = createStatement.replace(/^CREATE TABLE/i, 'CREATE TABLE IF NOT EXISTS');
|
||||||
|
db.exec(`${statementWithGuard};`);
|
||||||
|
console.log(`ℹ️ Tabelle ${tableName} angelegt`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingColumns = getExistingColumns(tableName);
|
||||||
|
const missingColumns = columns.filter((column) => !existingColumns.has(column.name));
|
||||||
|
missingColumns.forEach((column) => {
|
||||||
|
try {
|
||||||
|
db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${column.name} ${column.definition};`);
|
||||||
|
console.log(`ℹ️ Column ${column.name} added to ${tableName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`⚠️ Column ${column.name} could not be added to ${tableName}: ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureIndexes = (indexes) => {
|
||||||
|
indexes.forEach(({ name, statement }) => {
|
||||||
|
if (indexExists(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const statementWithGuard = statement.replace(/^CREATE INDEX/i, 'CREATE INDEX IF NOT EXISTS');
|
||||||
|
db.exec(`${statementWithGuard};`);
|
||||||
|
console.log(`ℹ️ Index ${name} angelegt`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensurePermissionSeeds = () => {
|
||||||
|
const selectSection = db.prepare(`
|
||||||
|
SELECT id, title, sort_order, has_navigation_flag
|
||||||
|
FROM permission_sections
|
||||||
|
WHERE key = ?
|
||||||
|
LIMIT 1
|
||||||
|
`);
|
||||||
|
const insertSection = db.prepare(`
|
||||||
|
INSERT INTO permission_sections (key, title, sort_order, has_navigation_flag, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
`);
|
||||||
|
const updateSection = db.prepare(`
|
||||||
|
UPDATE permission_sections
|
||||||
|
SET title = ?, sort_order = ?, has_navigation_flag = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
`);
|
||||||
|
|
||||||
|
const selectGroup = db.prepare(`
|
||||||
|
SELECT id, title, sort_order
|
||||||
|
FROM permission_groups
|
||||||
|
WHERE section_id = ? AND key = ?
|
||||||
|
LIMIT 1
|
||||||
|
`);
|
||||||
|
const insertGroup = db.prepare(`
|
||||||
|
INSERT INTO permission_groups (section_id, key, title, sort_order, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
`);
|
||||||
|
const updateGroup = db.prepare(`
|
||||||
|
UPDATE permission_groups
|
||||||
|
SET title = ?, sort_order = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
`);
|
||||||
|
|
||||||
|
const selectItem = db.prepare(`
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
section_id,
|
||||||
|
group_id,
|
||||||
|
label,
|
||||||
|
sort_order,
|
||||||
|
default_level,
|
||||||
|
available_levels,
|
||||||
|
is_required
|
||||||
|
FROM permission_items
|
||||||
|
WHERE key = ?
|
||||||
|
LIMIT 1
|
||||||
|
`);
|
||||||
|
const insertItem = db.prepare(`
|
||||||
|
INSERT INTO permission_items (
|
||||||
|
section_id,
|
||||||
|
group_id,
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
sort_order,
|
||||||
|
default_level,
|
||||||
|
available_levels,
|
||||||
|
is_required,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
const updateItem = db.prepare(`
|
||||||
|
UPDATE permission_items
|
||||||
|
SET
|
||||||
|
section_id = ?,
|
||||||
|
group_id = ?,
|
||||||
|
label = ?,
|
||||||
|
sort_order = ?,
|
||||||
|
default_level = ?,
|
||||||
|
available_levels = ?,
|
||||||
|
is_required = ?,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
`);
|
||||||
|
|
||||||
|
const selectDependency = db.prepare(`
|
||||||
|
SELECT id, required_level
|
||||||
|
FROM permission_dependencies
|
||||||
|
WHERE permission_id = ? AND depends_on_permission_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
`);
|
||||||
|
const insertDependency = db.prepare(`
|
||||||
|
INSERT INTO permission_dependencies (
|
||||||
|
permission_id,
|
||||||
|
depends_on_permission_id,
|
||||||
|
required_level,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
const updateDependency = db.prepare(`
|
||||||
|
UPDATE permission_dependencies
|
||||||
|
SET required_level = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
`);
|
||||||
|
|
||||||
|
const itemKeyToId = new Map();
|
||||||
|
|
||||||
|
PERMISSION_BLUEPRINT.forEach((section, sectionIndex) => {
|
||||||
|
const existingSection = selectSection.get(section.key);
|
||||||
|
let sectionId;
|
||||||
|
if (!existingSection) {
|
||||||
|
const result = insertSection.run(
|
||||||
|
section.key,
|
||||||
|
section.title,
|
||||||
|
section.sortOrder ?? sectionIndex,
|
||||||
|
section.hasNavigation ? 1 : 0
|
||||||
|
);
|
||||||
|
sectionId = Number(result.lastInsertRowid);
|
||||||
|
console.log(`ℹ️ Berechtigungs-Sektion ${section.key} angelegt`);
|
||||||
|
} else {
|
||||||
|
updateSection.run(
|
||||||
|
section.title,
|
||||||
|
section.sortOrder ?? sectionIndex,
|
||||||
|
section.hasNavigation ? 1 : 0,
|
||||||
|
existingSection.id
|
||||||
|
);
|
||||||
|
sectionId = existingSection.id;
|
||||||
|
if (
|
||||||
|
existingSection.title !== section.title ||
|
||||||
|
(existingSection.sort_order ?? sectionIndex) !== (section.sortOrder ?? sectionIndex) ||
|
||||||
|
Number(existingSection.has_navigation_flag) !== (section.hasNavigation ? 1 : 0)
|
||||||
|
) {
|
||||||
|
console.log(`ℹ️ Berechtigungs-Sektion ${section.key} aktualisiert`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
||||||
|
sectionItems.forEach((item, itemIdx) => {
|
||||||
|
const existingItem = selectItem.get(item.key);
|
||||||
|
const sortOrder = typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx;
|
||||||
|
const levelOptions = Array.isArray(item.levels) && item.levels.length
|
||||||
|
? item.levels
|
||||||
|
: ['full', 'read', 'none'];
|
||||||
|
const availableLevels = JSON.stringify(levelOptions);
|
||||||
|
const isRequired = item.isRequired ? 1 : 0;
|
||||||
|
if (!existingItem) {
|
||||||
|
const result = insertItem.run(
|
||||||
|
sectionId,
|
||||||
|
null,
|
||||||
|
item.key,
|
||||||
|
item.label,
|
||||||
|
sortOrder,
|
||||||
|
item.defaultLevel || 'none',
|
||||||
|
availableLevels,
|
||||||
|
isRequired
|
||||||
|
);
|
||||||
|
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
|
||||||
|
console.log(`ℹ️ Berechtigung ${item.key} angelegt`);
|
||||||
|
} else {
|
||||||
|
const hasChanges =
|
||||||
|
existingItem.section_id !== sectionId ||
|
||||||
|
existingItem.group_id !== null ||
|
||||||
|
existingItem.label !== item.label ||
|
||||||
|
(existingItem.sort_order ?? sortOrder) !== sortOrder ||
|
||||||
|
existingItem.default_level !== (item.defaultLevel || 'none') ||
|
||||||
|
existingItem.available_levels !== availableLevels ||
|
||||||
|
Number(existingItem.is_required) !== isRequired;
|
||||||
|
updateItem.run(
|
||||||
|
sectionId,
|
||||||
|
null,
|
||||||
|
item.label,
|
||||||
|
sortOrder,
|
||||||
|
item.defaultLevel || 'none',
|
||||||
|
availableLevels,
|
||||||
|
isRequired,
|
||||||
|
existingItem.id
|
||||||
|
);
|
||||||
|
itemKeyToId.set(item.key, existingItem.id);
|
||||||
|
if (hasChanges) {
|
||||||
|
console.log(`ℹ️ Berechtigung ${item.key} aktualisiert`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
|
||||||
|
sectionGroups.forEach((group, groupIdx) => {
|
||||||
|
const existingGroup = selectGroup.get(sectionId, group.key);
|
||||||
|
const sortOrder = typeof group.sortOrder === 'number' ? group.sortOrder : groupIdx;
|
||||||
|
let groupId;
|
||||||
|
if (!existingGroup) {
|
||||||
|
const result = insertGroup.run(sectionId, group.key, group.title, sortOrder);
|
||||||
|
groupId = Number(result.lastInsertRowid);
|
||||||
|
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${section.key} angelegt`);
|
||||||
|
} else {
|
||||||
|
const hasGroupChanges =
|
||||||
|
existingGroup.title !== group.title ||
|
||||||
|
(existingGroup.sort_order ?? sortOrder) !== sortOrder;
|
||||||
|
updateGroup.run(group.title, sortOrder, existingGroup.id);
|
||||||
|
groupId = existingGroup.id;
|
||||||
|
if (hasGroupChanges) {
|
||||||
|
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${section.key} aktualisiert`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const groupItems = Array.isArray(group.items) ? group.items : [];
|
||||||
|
groupItems.forEach((item, itemIdx) => {
|
||||||
|
const existingItem = selectItem.get(item.key);
|
||||||
|
const itemSortOrder = typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx;
|
||||||
|
const levelOptions = Array.isArray(item.levels) && item.levels.length
|
||||||
|
? item.levels
|
||||||
|
: ['full', 'read', 'none'];
|
||||||
|
const availableLevels = JSON.stringify(levelOptions);
|
||||||
|
const isRequired = item.isRequired ? 1 : 0;
|
||||||
|
if (!existingItem) {
|
||||||
|
const result = insertItem.run(
|
||||||
|
sectionId,
|
||||||
|
groupId,
|
||||||
|
item.key,
|
||||||
|
item.label,
|
||||||
|
itemSortOrder,
|
||||||
|
item.defaultLevel || 'none',
|
||||||
|
availableLevels,
|
||||||
|
isRequired
|
||||||
|
);
|
||||||
|
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
|
||||||
|
console.log(`ℹ️ Berechtigung ${item.key} in Gruppe ${group.key} angelegt`);
|
||||||
|
} else {
|
||||||
|
const hasChanges =
|
||||||
|
existingItem.section_id !== sectionId ||
|
||||||
|
existingItem.group_id !== groupId ||
|
||||||
|
existingItem.label !== item.label ||
|
||||||
|
(existingItem.sort_order ?? itemSortOrder) !== itemSortOrder ||
|
||||||
|
existingItem.default_level !== (item.defaultLevel || 'none') ||
|
||||||
|
existingItem.available_levels !== availableLevels ||
|
||||||
|
Number(existingItem.is_required) !== isRequired;
|
||||||
|
updateItem.run(
|
||||||
|
sectionId,
|
||||||
|
groupId,
|
||||||
|
item.label,
|
||||||
|
itemSortOrder,
|
||||||
|
item.defaultLevel || 'none',
|
||||||
|
availableLevels,
|
||||||
|
isRequired,
|
||||||
|
existingItem.id
|
||||||
|
);
|
||||||
|
itemKeyToId.set(item.key, existingItem.id);
|
||||||
|
if (hasChanges) {
|
||||||
|
console.log(`ℹ️ Berechtigung ${item.key} in Gruppe ${group.key} aktualisiert`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
PERMISSION_BLUEPRINT.forEach((section) => {
|
||||||
|
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
||||||
|
sectionItems.forEach((item) => {
|
||||||
|
const permissionId = itemKeyToId.get(item.key);
|
||||||
|
if (!permissionId) return;
|
||||||
|
(item.dependencies || []).forEach((dependency) => {
|
||||||
|
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
|
||||||
|
if (!dependsId) return;
|
||||||
|
const existing = selectDependency.get(permissionId, dependsId);
|
||||||
|
const requiredLevel = dependency.requiredLevel ?? null;
|
||||||
|
if (!existing) {
|
||||||
|
insertDependency.run(permissionId, dependsId, requiredLevel);
|
||||||
|
console.log(
|
||||||
|
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} angelegt`
|
||||||
|
);
|
||||||
|
} else if (existing.required_level !== requiredLevel) {
|
||||||
|
updateDependency.run(requiredLevel, existing.id);
|
||||||
|
console.log(
|
||||||
|
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} aktualisiert`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
|
||||||
|
sectionGroups.forEach((group) => {
|
||||||
|
const groupItems = Array.isArray(group.items) ? group.items : [];
|
||||||
|
groupItems.forEach((item) => {
|
||||||
|
const permissionId = itemKeyToId.get(item.key);
|
||||||
|
if (!permissionId) return;
|
||||||
|
(item.dependencies || []).forEach((dependency) => {
|
||||||
|
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
|
||||||
|
if (!dependsId) return;
|
||||||
|
const requiredLevel = dependency.requiredLevel ?? null;
|
||||||
|
const existing = selectDependency.get(permissionId, dependsId);
|
||||||
|
if (!existing) {
|
||||||
|
insertDependency.run(permissionId, dependsId, requiredLevel);
|
||||||
|
console.log(
|
||||||
|
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} angelegt`
|
||||||
|
);
|
||||||
|
} else if (existing.required_level !== requiredLevel) {
|
||||||
|
updateDependency.run(requiredLevel, existing.id);
|
||||||
|
console.log(
|
||||||
|
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} aktualisiert`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ensureDatabaseSchema = () => {
|
||||||
|
try {
|
||||||
|
const statements = loadBlueprintStatements();
|
||||||
|
const tableDefinitions = parseTableDefinitions(statements);
|
||||||
|
const indexDefinitions = parseIndexStatements(statements);
|
||||||
|
|
||||||
|
ensureTablesAndColumns(tableDefinitions);
|
||||||
|
ensureIndexes(indexDefinitions);
|
||||||
|
ensurePermissionSeeds();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('⚠️ Schema-Abgleich fehlgeschlagen:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "🔧 Starte Migration (idempotent)..."
|
echo "🔧 Überprüfe Datenbankschema..."
|
||||||
node db/migrate.js
|
node db/ensure.js
|
||||||
|
|
||||||
echo "✅ Migration abgeschlossen. Starte Anwendung..."
|
echo "✅ Schema-Abgleich abgeschlossen. Starte Anwendung..."
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import os from 'os';
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { db } from './db/index.js';
|
import { db } from './db/index.js';
|
||||||
|
import { ensureDatabaseSchema } from './db/schemaEnsure.js';
|
||||||
import {
|
import {
|
||||||
ensureSuperuserFromEnv,
|
ensureSuperuserFromEnv,
|
||||||
getSuperuserSummary,
|
getSuperuserSummary,
|
||||||
@@ -73,6 +74,7 @@ import {
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
ensureDatabaseSchema();
|
||||||
ensureSuperuserFromEnv();
|
ensureSuperuserFromEnv();
|
||||||
ensureDefaultsFromEnv();
|
ensureDefaultsFromEnv();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"migrate": "node db/migrate.js"
|
"migrate": "node db/ensure.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.0",
|
"axios": "^1.7.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user