Initial commit: IT Nexus Web-App

This commit is contained in:
2026-06-01 20:49:07 +02:00
commit 8023765e6c
387 changed files with 106900 additions and 0 deletions

341
backend/src/db/schema.sql Normal file
View File

@@ -0,0 +1,341 @@
-- FIDO-Key Management System - Database Schema
-- ============================================================================
-- ROLES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- ============================================================================
-- USERS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(100) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255),
role_id INTEGER NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
is_active BOOLEAN DEFAULT 1,
must_change_password BOOLEAN DEFAULT 1,
email_notifications INTEGER NOT NULL DEFAULT 1,
last_login DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- ============================================================================
-- FIDO KEYS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS fido_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
serial_number VARCHAR(255) UNIQUE NOT NULL,
status VARCHAR(20) NOT NULL CHECK(status IN ('aktiv', 'inaktiv')),
description TEXT,
assigned_to_user_id INTEGER,
created_by_user_id INTEGER NOT NULL,
updated_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id),
FOREIGN KEY (updated_by_user_id) REFERENCES users(id)
);
-- ============================================================================
-- ASSETS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS assets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL CHECK(type IN ('Notebook', 'Monitor', 'Headset', 'Other')),
serial_number VARCHAR(255) UNIQUE NOT NULL,
model VARCHAR(255),
status VARCHAR(20) NOT NULL CHECK(status IN ('verfuegbar', 'zugewiesen', 'inaktiv', 'beschaedigt')),
purchase_date DATE,
description TEXT,
teamviewer_id TEXT,
assigned_to_user_id INTEGER,
created_by_user_id INTEGER NOT NULL,
updated_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id),
FOREIGN KEY (updated_by_user_id) REFERENCES users(id)
);
-- ============================================================================
-- ASSET ASSIGNMENTS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS asset_assignments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
asset_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
assigned_by_user_id INTEGER NOT NULL,
assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
returned_at DATETIME,
notes TEXT,
onboarding_protocol_id INTEGER,
offboarding_protocol_id INTEGER,
FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (assigned_by_user_id) REFERENCES users(id)
);
-- ============================================================================
-- ONBOARDING PROTOCOLS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS onboarding_protocols (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_user_id INTEGER NOT NULL,
status VARCHAR(20) NOT NULL CHECK(status IN ('pending', 'in_progress', 'completed')),
start_date DATE NOT NULL,
completion_date DATE,
checklist_data TEXT,
notes TEXT,
pdf_file_path VARCHAR(500),
created_by_user_id INTEGER NOT NULL,
updated_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (employee_user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (created_by_user_id) REFERENCES users(id),
FOREIGN KEY (updated_by_user_id) REFERENCES users(id)
);
-- ============================================================================
-- OFFBOARDING PROTOCOLS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS offboarding_protocols (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_user_id INTEGER NOT NULL,
status VARCHAR(20) NOT NULL CHECK(status IN ('pending', 'in_progress', 'completed')),
exit_date DATE NOT NULL,
completion_date DATE,
checklist_data TEXT,
notes TEXT,
pdf_file_path VARCHAR(500),
created_by_user_id INTEGER NOT NULL,
updated_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (employee_user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (created_by_user_id) REFERENCES users(id),
FOREIGN KEY (updated_by_user_id) REFERENCES users(id)
);
-- ============================================================================
-- AUDIT LOG TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
action VARCHAR(50) NOT NULL,
entity_type VARCHAR(50) NOT NULL,
entity_id INTEGER NOT NULL,
old_value TEXT,
new_value TEXT,
ip_address VARCHAR(45),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- ============================================================================
-- INDEXES FOR PERFORMANCE
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_users_role_id ON users(role_id);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
CREATE INDEX IF NOT EXISTS idx_fido_keys_serial ON fido_keys(serial_number);
CREATE INDEX IF NOT EXISTS idx_fido_keys_status ON fido_keys(status);
CREATE INDEX IF NOT EXISTS idx_fido_keys_assigned_to ON fido_keys(assigned_to_user_id);
CREATE INDEX IF NOT EXISTS idx_fido_keys_created_by ON fido_keys(created_by_user_id);
CREATE INDEX IF NOT EXISTS idx_assets_serial ON assets(serial_number);
CREATE INDEX IF NOT EXISTS idx_assets_type ON assets(type);
CREATE INDEX IF NOT EXISTS idx_assets_status ON assets(status);
CREATE INDEX IF NOT EXISTS idx_assets_assigned_to ON assets(assigned_to_user_id);
CREATE INDEX IF NOT EXISTS idx_asset_assignments_asset ON asset_assignments(asset_id);
CREATE INDEX IF NOT EXISTS idx_asset_assignments_user ON asset_assignments(user_id);
CREATE INDEX IF NOT EXISTS idx_asset_assignments_dates ON asset_assignments(assigned_at, returned_at);
CREATE INDEX IF NOT EXISTS idx_onboarding_employee ON onboarding_protocols(employee_user_id);
CREATE INDEX IF NOT EXISTS idx_onboarding_status ON onboarding_protocols(status);
CREATE INDEX IF NOT EXISTS idx_onboarding_dates ON onboarding_protocols(start_date, completion_date);
CREATE INDEX IF NOT EXISTS idx_offboarding_employee ON offboarding_protocols(employee_user_id);
CREATE INDEX IF NOT EXISTS idx_offboarding_status ON offboarding_protocols(status);
CREATE INDEX IF NOT EXISTS idx_offboarding_dates ON offboarding_protocols(exit_date, completion_date);
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_log_entity ON audit_log(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at);
-- ============================================================================
-- TICKETS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_number TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
description TEXT,
status VARCHAR(30) NOT NULL DEFAULT 'offen'
CHECK(status IN ('offen','in_bearbeitung','warten_auf_mitarbeiter','warten_auf_support','geschlossen')),
priority VARCHAR(20) NOT NULL DEFAULT 'mittel'
CHECK(priority IN ('niedrig','mittel','hoch','kritisch')),
category VARCHAR(50) NOT NULL DEFAULT 'Allgemein'
CHECK(category IN ('Software','Allgemein','SelectLine','Hardware')),
source VARCHAR(10) NOT NULL DEFAULT 'web'
CHECK(source IN ('web','email')),
requester_name TEXT,
requester_email TEXT,
assigned_to_user_id INTEGER,
created_by_user_id INTEGER,
asset_id INTEGER,
email_message_id TEXT,
resolved_at DATETIME,
closed_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE SET NULL
);
-- ============================================================================
-- TICKET COMMENTS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS ticket_comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
user_id INTEGER,
comment TEXT NOT NULL,
is_internal INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
-- ============================================================================
-- LICENSES TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS licenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
vendor TEXT,
license_type TEXT NOT NULL DEFAULT 'subscription'
CHECK(license_type IN ('subscription', 'oem', 'volume', 'perpetual')),
product_key TEXT,
seats INTEGER,
purchase_date DATE,
expiry_date DATE,
cost REAL,
notes TEXT,
sku_id TEXT,
created_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL
);
CREATE INDEX IF NOT EXISTS idx_licenses_expiry ON licenses(expiry_date);
CREATE INDEX IF NOT EXISTS idx_licenses_type ON licenses(license_type);
-- ============================================================================
-- INDEXES FOR TICKETS
-- ============================================================================
CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status);
CREATE INDEX IF NOT EXISTS idx_tickets_priority ON tickets(priority);
CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to_user_id);
CREATE INDEX IF NOT EXISTS idx_tickets_created_by ON tickets(created_by_user_id);
CREATE INDEX IF NOT EXISTS idx_tickets_asset ON tickets(asset_id);
CREATE INDEX IF NOT EXISTS idx_ticket_comments_ticket ON ticket_comments(ticket_id);
-- ============================================================================
-- MONITORING AGENTS TABLE
-- ============================================================================
CREATE TABLE IF NOT EXISTS monitoring_agents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname VARCHAR(255) UNIQUE NOT NULL,
ip_address VARCHAR(50),
mac_address VARCHAR(100),
os_name VARCHAR(255),
os_version VARCHAR(255),
cpu_model VARCHAR(255),
cpu_cores INTEGER,
cpu_usage_percent REAL,
ram_total_gb REAL,
ram_used_gb REAL,
disk_total_gb REAL,
disk_free_gb REAL,
last_user VARCHAR(255),
uptime_hours REAL,
domain VARCHAR(255),
agent_version VARCHAR(50),
installed_software TEXT,
windows_updates_pending INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'online',
last_checkin DATETIME DEFAULT CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_monitoring_agents_hostname ON monitoring_agents(hostname);
CREATE INDEX IF NOT EXISTS idx_monitoring_agents_status ON monitoring_agents(status);
CREATE INDEX IF NOT EXISTS idx_monitoring_agents_last_checkin ON monitoring_agents(last_checkin);
-- ============================================================================
-- NEXUS SCANNER TABLES
-- ============================================================================
CREATE TABLE IF NOT EXISTS scanner_sites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id VARCHAR(10) NOT NULL UNIQUE,
last_seen DATETIME,
scanner_version VARCHAR(50),
host_count INTEGER DEFAULT 0,
check_count INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS scanner_assets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id VARCHAR(10) NOT NULL,
ip VARCHAR(50) NOT NULL,
mac VARCHAR(100),
hostname VARCHAR(255),
vendor VARCHAR(255),
status VARCHAR(20) DEFAULT 'online',
first_seen DATETIME,
last_seen DATETIME,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(site_id, ip)
);
CREATE TABLE IF NOT EXISTS scanner_alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id VARCHAR(10) NOT NULL,
check_name VARCHAR(255) NOT NULL,
check_type VARCHAR(50),
target VARCHAR(255),
status VARCHAR(20) NOT NULL,
latency_ms INTEGER DEFAULT 0,
error_msg TEXT,
subject TEXT,
triggered_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_scanner_assets_site ON scanner_assets(site_id);
CREATE INDEX IF NOT EXISTS idx_scanner_assets_status ON scanner_assets(status);
CREATE INDEX IF NOT EXISTS idx_scanner_alerts_site ON scanner_alerts(site_id, triggered_at DESC);

726
backend/src/db/seed.js Normal file
View File

@@ -0,0 +1,726 @@
const Database = require('better-sqlite3');
const bcrypt = require('bcrypt');
const path = require('path');
const fs = require('fs');
require('dotenv').config();
const DB_PATH = process.env.DATABASE_PATH || './database.sqlite';
const BCRYPT_ROUNDS = parseInt(process.env.BCRYPT_ROUNDS) || 12;
// Default Super Admin credentials
const DEFAULT_SUPERADMIN = {
username: 'superadmin',
email: 'admin@fido-manager.local',
password: 'Admin123!',
first_name: 'Super',
last_name: 'Administrator'
};
async function initializeDatabase() {
try {
console.log('🔧 Initializing database...');
// Create database connection
const db = new Database(DB_PATH);
// Read and execute schema
const schemaPath = path.join(__dirname, 'schema.sql');
const schema = fs.readFileSync(schemaPath, 'utf8');
// Execute schema (split by semicolon and execute each statement)
const statements = schema.split(';').filter(stmt => stmt.trim());
for (const statement of statements) {
if (statement.trim()) {
db.exec(statement);
}
}
console.log('✅ Database schema created successfully');
// Create uploads directories
console.log('📁 Creating uploads directories...');
const uploadsDir = path.join(__dirname, '../../uploads');
const onboardingDir = path.join(uploadsDir, 'onboarding');
const offboardingDir = path.join(uploadsDir, 'offboarding');
const currentYear = new Date().getFullYear().toString();
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
if (!fs.existsSync(onboardingDir)) {
fs.mkdirSync(onboardingDir, { recursive: true });
}
if (!fs.existsSync(offboardingDir)) {
fs.mkdirSync(offboardingDir, { recursive: true });
}
// Create year subdirectories
const onboardingYearDir = path.join(onboardingDir, currentYear);
const offboardingYearDir = path.join(offboardingDir, currentYear);
if (!fs.existsSync(onboardingYearDir)) {
fs.mkdirSync(onboardingYearDir, { recursive: true });
}
if (!fs.existsSync(offboardingYearDir)) {
fs.mkdirSync(offboardingYearDir, { recursive: true });
}
console.log('✅ Uploads directories created successfully');
// Insert roles
console.log('📝 Inserting default roles...');
const insertRole = db.prepare(`
INSERT OR IGNORE INTO roles (name, description)
VALUES (?, ?)
`);
const roles = [
['super_admin', 'Super Administrator - Full system access including user management'],
['admin', 'Administrator - Can manage FIDO keys and view users'],
['bearbeiter', 'Editor - Can create and edit FIDO keys'],
['benutzer', 'User - Can only view FIDO keys'],
['support', 'Support - Kann Tickets sehen und bearbeiten'],
['hr_personal', 'HR / Personal - Sieht HR-Aufgaben im Onboarding'],
['buchhaltung', 'Buchhaltung / Lohn - Sieht Buchhaltungs-Aufgaben im Onboarding'],
['produktion', 'Produktion - Sieht und verwaltet Produktions-Assets'],
];
for (const [name, description] of roles) {
insertRole.run(name, description);
}
console.log('✅ Roles created successfully');
// Run migrations for existing databases
console.log('🔄 Running database migrations...');
const migrations = [
`ALTER TABLE assets ADD COLUMN teamviewer_id TEXT`,
`ALTER TABLE users ADD COLUMN azure_id TEXT`,
`ALTER TABLE users ADD COLUMN email_notifications INTEGER NOT NULL DEFAULT 1`,
`ALTER TABLE assets ADD COLUMN last_maintenance_date DATE`,
`ALTER TABLE assets ADD COLUMN next_maintenance_date DATE`,
`ALTER TABLE assets ADD COLUMN maintenance_interval_months INTEGER`,
`ALTER TABLE assets ADD COLUMN maintenance_notes TEXT`,
`CREATE TABLE IF NOT EXISTS licenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
vendor TEXT,
license_type TEXT NOT NULL DEFAULT 'subscription',
product_key TEXT,
seats INTEGER,
purchase_date DATE,
expiry_date DATE,
cost REAL,
notes TEXT,
created_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL
)`,
`CREATE INDEX IF NOT EXISTS idx_licenses_expiry ON licenses(expiry_date)`,
`CREATE INDEX IF NOT EXISTS idx_licenses_type ON licenses(license_type)`,
`ALTER TABLE licenses ADD COLUMN sku_id TEXT`,
`ALTER TABLE tickets ADD COLUMN snoozed_until DATETIME`,
`CREATE TABLE IF NOT EXISTS ticket_links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
linked_ticket_id INTEGER NOT NULL,
link_type TEXT NOT NULL DEFAULT 'related',
created_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
FOREIGN KEY (linked_ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
UNIQUE(ticket_id, linked_ticket_id)
)`,
`ALTER TABLE tickets ADD COLUMN ai_suggestion TEXT`,
`ALTER TABLE ticket_comments ADD COLUMN is_ai_comment INTEGER DEFAULT 0`,
`ALTER TABLE tickets ADD COLUMN ai_active INTEGER DEFAULT 1`,
`CREATE TABLE IF NOT EXISTS ticket_assignees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
assigned_by_user_id INTEGER,
assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(ticket_id, user_id)
)`,
`CREATE TABLE IF NOT EXISTS knowledge_base (
id INTEGER PRIMARY KEY AUTOINCREMENT,
problem TEXT NOT NULL,
solution TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'Allgemein',
tags TEXT,
created_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL
)`,
`ALTER TABLE tickets ADD COLUMN escalated_at DATETIME`,
`ALTER TABLE tickets ADD COLUMN satisfaction_rating TEXT`,
`CREATE TABLE IF NOT EXISTS ticket_routing (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL UNIQUE,
assigned_to_user_id INTEGER,
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE SET NULL
)`,
`ALTER TABLE onboarding_protocols ADD COLUMN dept_contacts TEXT`,
`ALTER TABLE knowledge_base ADD COLUMN source_ticket_id INTEGER REFERENCES tickets(id) ON DELETE SET NULL`,
`ALTER TABLE knowledge_base ADD COLUMN auto_generated INTEGER DEFAULT 0`,
`ALTER TABLE knowledge_base ADD COLUMN images TEXT DEFAULT '[]'`,
`ALTER TABLE onboarding_protocols ADD COLUMN confirm_token TEXT`,
`ALTER TABLE onboarding_protocols ADD COLUMN employee_confirmed_at DATETIME`,
`ALTER TABLE users ADD COLUMN notif_ticket_created INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE users ADD COLUMN notif_ticket_assigned INTEGER NOT NULL DEFAULT 1`,
`ALTER TABLE users ADD COLUMN notif_new_comment INTEGER NOT NULL DEFAULT 1`,
`ALTER TABLE users ADD COLUMN notif_weekly_report INTEGER NOT NULL DEFAULT 1`,
`ALTER TABLE assets ADD COLUMN department TEXT NOT NULL DEFAULT 'IT'`,
`UPDATE roles SET name = 'produktion', description = 'Produktion - Sieht und verwaltet Produktions-Assets' WHERE name = 'techniker'`,
`ALTER TABLE assets ADD COLUMN inventory_number TEXT`,
`ALTER TABLE assets ADD COLUMN purchase_price REAL`,
`ALTER TABLE assets ADD COLUMN useful_life_years INTEGER`,
`ALTER TABLE assets ADD COLUMN residual_value REAL DEFAULT 0`,
`CREATE TABLE IF NOT EXISTS network_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'host' CHECK(type IN ('host','switch','router','printer','nas','service')),
host TEXT NOT NULL,
check_type TEXT NOT NULL DEFAULT 'icmp' CHECK(check_type IN ('icmp','http','https','tcp')),
port INTEGER,
http_path TEXT DEFAULT '/',
http_keyword TEXT,
interval_sec INTEGER NOT NULL DEFAULT 60,
timeout_sec INTEGER NOT NULL DEFAULT 5,
enabled INTEGER NOT NULL DEFAULT 1,
notify_email TEXT,
location TEXT,
last_status TEXT DEFAULT 'unknown',
last_checked DATETIME,
last_rtt_ms REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_network_devices_enabled ON network_devices(enabled)`,
`CREATE TABLE IF NOT EXISTS device_checks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id INTEGER NOT NULL,
status TEXT NOT NULL CHECK(status IN ('up','down')),
rtt_ms REAL,
error_msg TEXT,
checked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (device_id) REFERENCES network_devices(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS idx_device_checks_time ON device_checks(device_id, checked_at)`,
`CREATE TABLE IF NOT EXISTS teams_channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
team_id TEXT NOT NULL,
team_name TEXT,
channel_id TEXT NOT NULL UNIQUE,
channel_name TEXT,
created_at_teams TEXT,
first_seen_at DATETIME DEFAULT CURRENT_TIMESTAMP,
notified INTEGER NOT NULL DEFAULT 0
)`,
`CREATE INDEX IF NOT EXISTS idx_teams_channels_notified ON teams_channels(notified)`,
`CREATE TABLE IF NOT EXISTS unifi_config (
id INTEGER PRIMARY KEY DEFAULT 1,
controller_url TEXT NOT NULL DEFAULT '',
username TEXT NOT NULL DEFAULT '',
password TEXT NOT NULL DEFAULT '',
site TEXT NOT NULL DEFAULT 'default',
poll_interval_min INTEGER NOT NULL DEFAULT 5,
enabled INTEGER NOT NULL DEFAULT 0
)`,
`INSERT OR IGNORE INTO unifi_config (id) VALUES (1)`,
`CREATE TABLE IF NOT EXISTS unifi_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
unifi_id TEXT UNIQUE,
mac TEXT,
name TEXT,
ip TEXT,
type TEXT,
model TEXT,
version TEXT,
state INTEGER DEFAULT 0,
uptime INTEGER DEFAULT 0,
num_sta INTEGER DEFAULT 0,
cpu_pct REAL,
ram_pct REAL,
ports_json TEXT DEFAULT '[]',
radio_json TEXT DEFAULT '[]',
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_unifi_devices_type ON unifi_devices(type)`,
`CREATE TABLE IF NOT EXISTS external_alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT DEFAULT 'netgo',
device TEXT,
service TEXT,
state_transition TEXT,
severity TEXT,
message TEXT,
customer TEXT,
monitored_by TEXT,
state_time TEXT,
raw_body TEXT,
acknowledged INTEGER DEFAULT 0,
ticket_id INTEGER,
email_message_id TEXT UNIQUE,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_external_alerts_acknowledged ON external_alerts(acknowledged)`,
`ALTER TABLE tickets ADD COLUMN satisfaction_comment TEXT`,
`ALTER TABLE external_alerts ADD COLUMN ai_analysis TEXT`,
// === LAGER-MODUL MIGRATIONS ===
`ALTER TABLE assets ADD COLUMN location_id INTEGER REFERENCES warehouse_locations(id) ON DELETE SET NULL`,
`ALTER TABLE assets ADD COLUMN min_stock INTEGER DEFAULT 0`,
`CREATE TABLE IF NOT EXISTS warehouse_locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`INSERT OR IGNORE INTO warehouse_locations (name, description) VALUES ('Büro', 'Allgemeines Büro')`,
`INSERT OR IGNORE INTO warehouse_locations (name, description) VALUES ('Serverraum', 'Serverraum / Technikraum')`,
`INSERT OR IGNORE INTO warehouse_locations (name, description) VALUES ('Lager', 'Hauptlager')`,
`CREATE TABLE IF NOT EXISTS asset_movements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
asset_id INTEGER NOT NULL,
type TEXT NOT NULL CHECK(type IN ('in','out','transfer','status_change')),
from_location_id INTEGER REFERENCES warehouse_locations(id) ON DELETE SET NULL,
to_location_id INTEGER REFERENCES warehouse_locations(id) ON DELETE SET NULL,
assigned_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
ticket_id INTEGER REFERENCES tickets(id) ON DELETE SET NULL,
reason TEXT,
notes TEXT,
performed_by INTEGER NOT NULL REFERENCES users(id),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE CASCADE
)`,
`CREATE INDEX IF NOT EXISTS idx_asset_movements_asset ON asset_movements(asset_id)`,
`CREATE INDEX IF NOT EXISTS idx_asset_movements_created ON asset_movements(created_at)`,
`CREATE TABLE IF NOT EXISTS stock_thresholds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL UNIQUE,
min_stock INTEGER NOT NULL DEFAULT 1,
notify_email TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS purchase_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
item_name TEXT NOT NULL,
quantity INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'offen' CHECK(status IN ('offen','bestellt','erledigt')),
notes TEXT,
created_by INTEGER NOT NULL REFERENCES users(id),
ordered_at DATETIME,
completed_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE INDEX IF NOT EXISTS idx_purchase_orders_status ON purchase_orders(status)`,
`CREATE TABLE IF NOT EXISTS asset_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
icon TEXT DEFAULT 'devices_other',
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Notebook', 'laptop', 1)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Monitor', 'monitor', 2)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Headset', 'headset', 3)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Drucker', 'print', 4)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Smartphone', 'smartphone', 5)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Tablet', 'tablet', 6)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Server', 'storage', 7)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Switch', 'device_hub', 8)`,
`INSERT OR IGNORE INTO asset_types (name, icon, sort_order) VALUES ('Sonstiges', 'devices_other', 99)`,
`ALTER TABLE monitoring_agents ADD COLUMN agent_version TEXT DEFAULT '1.0.0'`,
`ALTER TABLE monitoring_agents ADD COLUMN tpm_present INTEGER DEFAULT 0`,
`ALTER TABLE monitoring_agents ADD COLUMN tpm_version TEXT`,
`ALTER TABLE monitoring_agents ADD COLUMN tpm_v2 INTEGER DEFAULT 0`,
`ALTER TABLE monitoring_agents ADD COLUMN secure_boot INTEGER DEFAULT 0`,
`ALTER TABLE monitoring_agents ADD COLUMN win11_ready INTEGER DEFAULT 0`,
`DROP TABLE IF EXISTS patch_commands_v2`,
`CREATE TABLE patch_commands_v2 (id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id INTEGER NOT NULL, command TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','sent','done','failed')), triggered_by_user_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, sent_at DATETIME, completed_at DATETIME, result TEXT)`,
`INSERT OR IGNORE INTO patch_commands_v2 SELECT id,agent_id,command,status,triggered_by_user_id,created_at,sent_at,completed_at,result FROM patch_commands`,
`DROP TABLE patch_commands`,
`ALTER TABLE patch_commands_v2 RENAME TO patch_commands`,
`CREATE INDEX IF NOT EXISTS idx_patch_commands_agent ON patch_commands(agent_id, status)`,
`DROP TABLE IF EXISTS patch_commands_v3`,
`CREATE TABLE patch_commands_v3 (id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id INTEGER NOT NULL, command TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','sent','done','failed','running')), triggered_by_user_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, sent_at DATETIME, completed_at DATETIME, result TEXT)`,
`INSERT OR IGNORE INTO patch_commands_v3 SELECT id,agent_id,command,status,triggered_by_user_id,created_at,sent_at,completed_at,result FROM patch_commands`,
`DROP TABLE patch_commands`,
`ALTER TABLE patch_commands_v3 RENAME TO patch_commands`,
`CREATE INDEX IF NOT EXISTS idx_patch_commands_agent_v3 ON patch_commands(agent_id, status)`,
`ALTER TABLE portal_guides ADD COLUMN visible_roles TEXT DEFAULT '[]'`,
`CREATE TABLE IF NOT EXISTS patch_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
description TEXT,
color TEXT DEFAULT '#3B82F6',
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`INSERT OR IGNORE INTO patch_groups (name, description, color, sort_order) VALUES ('Test', 'Testgeräte zuerst patchen', '#8B5CF6', 1)`,
`INSERT OR IGNORE INTO patch_groups (name, description, color, sort_order) VALUES ('Pilot', 'Pilotgruppe frühe Nutzer', '#F59E0B', 2)`,
`INSERT OR IGNORE INTO patch_groups (name, description, color, sort_order) VALUES ('Produktion', 'Produktionsgeräte zuletzt patchen', '#10B981', 3)`,
`CREATE TABLE IF NOT EXISTS patch_policies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER NOT NULL REFERENCES patch_groups(id) ON DELETE CASCADE,
severity TEXT NOT NULL DEFAULT 'critical' CHECK(severity IN ('critical','important','moderate','low','all')),
max_days INTEGER NOT NULL DEFAULT 7,
notify_email TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS patch_agent_groups (
agent_id INTEGER NOT NULL REFERENCES monitoring_agents(id) ON DELETE CASCADE,
group_id INTEGER NOT NULL REFERENCES patch_groups(id) ON DELETE CASCADE,
assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (agent_id, group_id)
)`,
`CREATE TABLE IF NOT EXISTS patch_commands (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL REFERENCES monitoring_agents(id) ON DELETE CASCADE,
command TEXT NOT NULL CHECK(command IN ('install_updates','check_updates','reboot')),
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','sent','done','failed')),
triggered_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
sent_at DATETIME,
completed_at DATETIME,
result TEXT
)`,
`CREATE INDEX IF NOT EXISTS idx_patch_commands_agent ON patch_commands(agent_id, status)`,
`ALTER TABLE patch_groups ADD COLUMN target_agent_version TEXT DEFAULT NULL`,
`CREATE TABLE IF NOT EXISTS ai_knowledge (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'Allgemein',
tags TEXT NOT NULL DEFAULT '[]',
created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
`DROP TABLE IF EXISTS announcement_acks`,
`DROP TABLE IF EXISTS announcements`,
`CREATE TABLE IF NOT EXISTS announcements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
message TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'info' CHECK(type IN ('info','warning','maintenance')),
target_groups TEXT NOT NULL DEFAULT '[]',
created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
expires_at DATETIME DEFAULT NULL,
active INTEGER NOT NULL DEFAULT 1
)`,
`CREATE TABLE IF NOT EXISTS announcement_acks (
announcement_id INTEGER NOT NULL REFERENCES announcements(id) ON DELETE CASCADE,
agent_id INTEGER NOT NULL REFERENCES monitoring_agents(id) ON DELETE CASCADE,
hostname TEXT NOT NULL,
acknowledged_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (announcement_id, agent_id)
)`,
`CREATE TABLE IF NOT EXISTS health_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
checked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
api_status TEXT NOT NULL DEFAULT 'ok',
db_status TEXT NOT NULL DEFAULT 'ok',
response_ms INTEGER
)`,
`CREATE INDEX IF NOT EXISTS idx_health_history_date ON health_history(checked_at)`,
`CREATE TABLE IF NOT EXISTS portal_guides (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'Allgemein',
description TEXT,
html_content TEXT NOT NULL,
icon TEXT DEFAULT '📄',
sort_order INTEGER DEFAULT 0,
is_public INTEGER DEFAULT 1,
created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
// Agent v2.0.0 — neue Felder
`ALTER TABLE monitoring_agents ADD COLUMN bitlocker_status TEXT DEFAULT 'unknown'`,
`ALTER TABLE monitoring_agents ADD COLUMN defender_enabled INTEGER DEFAULT 0`,
`ALTER TABLE monitoring_agents ADD COLUMN defender_signatures_age INTEGER DEFAULT -1`,
`ALTER TABLE monitoring_agents ADD COLUMN hardware_serial TEXT`,
// Sichere Links (Shares)
`CREATE TABLE IF NOT EXISTS shares (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token TEXT UNIQUE NOT NULL,
type TEXT NOT NULL CHECK(type IN ('file','text')),
filename TEXT,
stored_filename TEXT,
text_content TEXT,
password_hash TEXT,
expires_at DATETIME,
max_downloads INTEGER,
download_count INTEGER DEFAULT 0,
created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME
)`,
`CREATE INDEX IF NOT EXISTS idx_shares_token ON shares(token)`,
// Benutzerverwaltung — erweiterte User-Felder
`ALTER TABLE users ADD COLUMN department TEXT`,
`ALTER TABLE users ADD COLUMN position TEXT`,
`ALTER TABLE users ADD COLUMN phone TEXT`,
`ALTER TABLE users ADD COLUMN location TEXT`,
`ALTER TABLE users ADD COLUMN manager_id INTEGER REFERENCES users(id)`,
`ALTER TABLE users ADD COLUMN cost_center TEXT`,
`ALTER TABLE users ADD COLUMN employment_type TEXT`,
`ALTER TABLE users ADD COLUMN work_hours INTEGER`,
`ALTER TABLE users ADD COLUMN joined_date DATE`,
// Benutzerverwaltung — erweiterte FIDO-Key-Felder
`ALTER TABLE fido_keys ADD COLUMN key_type TEXT DEFAULT 'primary'`,
`ALTER TABLE fido_keys ADD COLUMN last_used_at DATETIME`,
`ALTER TABLE fido_keys ADD COLUMN manufacturer TEXT`,
`ALTER TABLE fido_keys ADD COLUMN connection_type TEXT`,
// Asset-Agent-Sync
`ALTER TABLE assets ADD COLUMN os TEXT`,
`ALTER TABLE assets ADD COLUMN ip_address TEXT`,
`ALTER TABLE assets ADD COLUMN manufacturer TEXT`,
`ALTER TABLE assets ADD COLUMN last_agent_sync DATETIME`,
`ALTER TABLE announcements ADD COLUMN target_agent_ids TEXT NOT NULL DEFAULT '[]'`,
`CREATE TABLE IF NOT EXISTS security_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
original_filename TEXT NOT NULL,
report_period TEXT,
risk_level TEXT DEFAULT 'unbekannt',
summary_json TEXT,
created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
];
for (const migration of migrations) {
try {
db.exec(migration);
} catch (e) {
// Column already exists ignore
}
}
console.log('✅ Database migrations completed');
// Special migration: rebuild network_devices to add SNMP + AP support
try {
const ndDef = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='network_devices'").get();
if (ndDef && !ndDef.sql.includes('snmp_community')) {
console.log('🔄 Migrating network_devices (adding SNMP + AP support)...');
db.pragma('foreign_keys = OFF');
db.exec(`CREATE TABLE network_devices_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'host' CHECK(type IN ('host','switch','router','printer','nas','service','ap')),
host TEXT NOT NULL,
check_type TEXT NOT NULL DEFAULT 'icmp' CHECK(check_type IN ('icmp','http','https','tcp','snmp')),
port INTEGER,
http_path TEXT DEFAULT '/',
http_keyword TEXT,
snmp_community TEXT DEFAULT 'public',
snmp_version TEXT DEFAULT '2c',
interval_sec INTEGER NOT NULL DEFAULT 60,
timeout_sec INTEGER NOT NULL DEFAULT 5,
enabled INTEGER NOT NULL DEFAULT 1,
notify_email TEXT,
location TEXT,
last_status TEXT DEFAULT 'unknown',
last_checked DATETIME,
last_rtt_ms REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
db.exec(`INSERT INTO network_devices_new
(id, name, type, host, check_type, port, http_path, http_keyword,
snmp_community, snmp_version, interval_sec, timeout_sec, enabled,
notify_email, location, last_status, last_checked, last_rtt_ms, created_at)
SELECT id, name, type, host, check_type, port, http_path, http_keyword,
'public', '2c', interval_sec, timeout_sec, enabled,
notify_email, location, last_status, last_checked, last_rtt_ms, created_at
FROM network_devices`);
db.exec('DROP TABLE network_devices');
db.exec('ALTER TABLE network_devices_new RENAME TO network_devices');
db.pragma('foreign_keys = ON');
db.exec('CREATE INDEX IF NOT EXISTS idx_network_devices_enabled ON network_devices(enabled)');
console.log('✅ network_devices SNMP migration complete');
}
} catch (e) {
console.error('❌ network_devices SNMP migration failed:', e.message);
}
// Special migration: add personal data columns to onboarding_protocols
try {
const obDef = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='onboarding_protocols'").get();
if (obDef && !obDef.sql.includes('emp_first_name')) {
console.log('🔄 Migrating onboarding_protocols (adding personal data columns)...');
db.pragma('foreign_keys = OFF');
db.exec(`CREATE TABLE onboarding_protocols_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_user_id INTEGER,
emp_first_name TEXT,
emp_last_name TEXT,
emp_private_email TEXT,
emp_phone TEXT,
emp_address TEXT,
department TEXT,
position TEXT,
work_location TEXT,
hours_model TEXT,
vacation_model TEXT,
status VARCHAR(20) NOT NULL CHECK(status IN ('pending', 'in_progress', 'completed')),
start_date DATE NOT NULL,
completion_date DATE,
checklist_data TEXT,
notes TEXT,
pdf_file_path VARCHAR(500),
created_by_user_id INTEGER NOT NULL,
updated_by_user_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (employee_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id),
FOREIGN KEY (updated_by_user_id) REFERENCES users(id)
)`);
db.exec(`INSERT INTO onboarding_protocols_new
(id, employee_user_id, status, start_date, completion_date, checklist_data, notes, pdf_file_path, created_by_user_id, updated_by_user_id, created_at, updated_at)
SELECT id, employee_user_id, status, start_date, completion_date, checklist_data, notes, pdf_file_path, created_by_user_id, updated_by_user_id, created_at, updated_at
FROM onboarding_protocols`);
db.exec('DROP TABLE onboarding_protocols');
db.exec('ALTER TABLE onboarding_protocols_new RENAME TO onboarding_protocols');
db.pragma('foreign_keys = ON');
db.exec('CREATE INDEX IF NOT EXISTS idx_onboarding_employee ON onboarding_protocols(employee_user_id)');
db.exec('CREATE INDEX IF NOT EXISTS idx_onboarding_status ON onboarding_protocols(status)');
console.log('✅ Onboarding personal data migration complete');
}
} catch (e) {
console.error('❌ Onboarding migration failed:', e.message);
}
// Special migration: update ticket status CHECK constraint to add response-based statuses
try {
const ticketDef = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='tickets'").get();
if (ticketDef && !ticketDef.sql.includes('warten_auf_mitarbeiter')) {
console.log('🔄 Migrating ticket status constraint (adding warten_auf_mitarbeiter/support)...');
db.pragma('foreign_keys = OFF');
db.exec(`CREATE TABLE tickets_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_number TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
description TEXT,
status VARCHAR(30) NOT NULL DEFAULT 'offen'
CHECK(status IN ('offen','in_bearbeitung','warten_auf_mitarbeiter','warten_auf_support','geschlossen')),
priority VARCHAR(20) NOT NULL DEFAULT 'mittel'
CHECK(priority IN ('niedrig','mittel','hoch','kritisch')),
category VARCHAR(50) NOT NULL DEFAULT 'Allgemein'
CHECK(category IN ('Software','Allgemein','SelectLine','Hardware')),
source VARCHAR(10) NOT NULL DEFAULT 'web'
CHECK(source IN ('web','email')),
requester_name TEXT,
requester_email TEXT,
assigned_to_user_id INTEGER,
created_by_user_id INTEGER,
asset_id INTEGER,
email_message_id TEXT,
snoozed_until DATETIME,
ai_suggestion TEXT,
ai_active INTEGER DEFAULT 1,
resolved_at DATETIME,
closed_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (assigned_to_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY (asset_id) REFERENCES assets(id) ON DELETE SET NULL
)`);
db.exec(`INSERT INTO tickets_new
SELECT id, ticket_number, title, description,
CASE status WHEN 'geloest' THEN 'in_bearbeitung' ELSE status END,
priority, category, source, requester_name, requester_email,
assigned_to_user_id, created_by_user_id, asset_id, email_message_id,
snoozed_until, ai_suggestion, ai_active, resolved_at, closed_at,
created_at, updated_at
FROM tickets`);
db.exec('DROP TABLE tickets');
db.exec('ALTER TABLE tickets_new RENAME TO tickets');
db.pragma('foreign_keys = ON');
db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_status ON tickets(status)');
db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_priority ON tickets(priority)');
db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_assigned ON tickets(assigned_to_user_id)');
db.exec('CREATE INDEX IF NOT EXISTS idx_tickets_created_by ON tickets(created_by_user_id)');
console.log('✅ Ticket status migration complete');
}
} catch (e) {
console.error('❌ Ticket status migration failed:', e.message);
}
// Check if super admin already exists
const existingAdmin = db.prepare('SELECT id FROM users WHERE username = ?').get(DEFAULT_SUPERADMIN.username);
if (!existingAdmin) {
console.log('👤 Creating default Super Admin user...');
// Hash password
const passwordHash = await bcrypt.hash(DEFAULT_SUPERADMIN.password, BCRYPT_ROUNDS);
// Get super_admin role ID
const superAdminRole = db.prepare('SELECT id FROM roles WHERE name = ?').get('super_admin');
// Insert super admin user
const insertUser = db.prepare(`
INSERT INTO users (
username,
email,
password_hash,
role_id,
first_name,
last_name,
is_active,
must_change_password
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`);
insertUser.run(
DEFAULT_SUPERADMIN.username,
DEFAULT_SUPERADMIN.email,
passwordHash,
superAdminRole.id,
DEFAULT_SUPERADMIN.first_name,
DEFAULT_SUPERADMIN.last_name,
1, // is_active
1 // must_change_password
);
console.log('✅ Super Admin user created successfully');
console.log('');
console.log('═══════════════════════════════════════════');
console.log('🔐 DEFAULT SUPER ADMIN CREDENTIALS');
console.log('═══════════════════════════════════════════');
console.log(`Username: ${DEFAULT_SUPERADMIN.username}`);
console.log(`Password: ${DEFAULT_SUPERADMIN.password}`);
console.log('');
console.log('⚠️ IMPORTANT: Please change this password on first login!');
console.log('═══════════════════════════════════════════');
console.log('');
} else {
console.log(' Super Admin user already exists, skipping creation');
}
db.close();
console.log('✅ Database initialization completed successfully!');
} catch (error) {
console.error('❌ Database initialization failed:', error.message);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
initializeDatabase();
}
module.exports = { initializeDatabase };