Initial commit
Dieser Commit ist enthalten in:
444
backend/src/config/database.ts
Normale Datei
444
backend/src/config/database.ts
Normale Datei
@ -0,0 +1,444 @@
|
||||
import Database from 'better-sqlite3'
|
||||
import path from 'path'
|
||||
import { Employee, User, SkillDefinition } from '@skillmate/shared'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { db as secureDb, encryptedDb, initializeSecureDatabase } from './secureDatabase'
|
||||
export { initializeSecureDatabase } from './secureDatabase'
|
||||
|
||||
// Export the secure database instance
|
||||
export const db = secureDb
|
||||
|
||||
export function initializeDatabase() {
|
||||
// Users table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('admin', 'superuser', 'user')),
|
||||
employee_id TEXT,
|
||||
last_login TEXT,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
|
||||
// Profiles table (erweitert für Yellow Pages)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
department TEXT,
|
||||
location TEXT,
|
||||
role TEXT,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
teams_link TEXT,
|
||||
job_category TEXT CHECK(job_category IN ('Technik', 'IT & Digitalisierung', 'Verwaltung', 'F&E', 'Kommunikation & HR', 'Produktion', 'Sonstiges')),
|
||||
job_title TEXT,
|
||||
job_desc TEXT,
|
||||
consent_public_profile INTEGER DEFAULT 0,
|
||||
consent_searchable INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
updated_by TEXT NOT NULL,
|
||||
review_due_at TEXT,
|
||||
search_vector TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
// Volltext-Index für Suche
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_search
|
||||
ON profiles(search_vector);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_department
|
||||
ON profiles(department);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_location
|
||||
ON profiles(location);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_job_category
|
||||
ON profiles(job_category);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_review_due
|
||||
ON profiles(review_due_at);
|
||||
`)
|
||||
|
||||
// Employees table (für Kompatibilität beibehalten)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS employees (
|
||||
id TEXT PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
employee_number TEXT UNIQUE NOT NULL,
|
||||
photo TEXT,
|
||||
position TEXT NOT NULL,
|
||||
department TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
phone TEXT NOT NULL,
|
||||
mobile TEXT,
|
||||
office TEXT,
|
||||
availability TEXT NOT NULL,
|
||||
clearance_level TEXT,
|
||||
clearance_valid_until TEXT,
|
||||
clearance_issued_date TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL,
|
||||
updated_by TEXT
|
||||
)
|
||||
`)
|
||||
|
||||
// Skills table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
description TEXT,
|
||||
requires_certification INTEGER DEFAULT 0,
|
||||
expires_after INTEGER
|
||||
)
|
||||
`)
|
||||
|
||||
// Employee skills junction table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS employee_skills (
|
||||
employee_id TEXT NOT NULL,
|
||||
skill_id TEXT NOT NULL,
|
||||
level TEXT,
|
||||
verified INTEGER DEFAULT 0,
|
||||
verified_by TEXT,
|
||||
verified_date TEXT,
|
||||
PRIMARY KEY (employee_id, skill_id),
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (skill_id) REFERENCES skills(id) ON DELETE CASCADE
|
||||
)
|
||||
`)
|
||||
|
||||
// Profile Kompetenzen (Arrays als separate Tabellen)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS profile_domains (
|
||||
profile_id TEXT NOT NULL,
|
||||
domain TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, domain),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_tools (
|
||||
profile_id TEXT NOT NULL,
|
||||
tool TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, tool),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_methods (
|
||||
profile_id TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, method),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_industry_knowledge (
|
||||
profile_id TEXT NOT NULL,
|
||||
knowledge TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, knowledge),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_regulatory (
|
||||
profile_id TEXT NOT NULL,
|
||||
regulation TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, regulation),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_networks (
|
||||
profile_id TEXT NOT NULL,
|
||||
network TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, network),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_digital_skills (
|
||||
profile_id TEXT NOT NULL,
|
||||
skill TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, skill),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profile_social_skills (
|
||||
profile_id TEXT NOT NULL,
|
||||
skill TEXT NOT NULL,
|
||||
PRIMARY KEY (profile_id, skill),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
// Profile Sprachen
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS profile_languages (
|
||||
profile_id TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
level TEXT NOT NULL CHECK(level IN ('basic', 'fluent', 'native', 'business')),
|
||||
PRIMARY KEY (profile_id, code),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
// Profile Projekte
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS profile_projects (
|
||||
id TEXT PRIMARY KEY,
|
||||
profile_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
role TEXT,
|
||||
summary TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_links (
|
||||
project_id TEXT NOT NULL,
|
||||
link TEXT NOT NULL,
|
||||
PRIMARY KEY (project_id, link),
|
||||
FOREIGN KEY (project_id) REFERENCES profile_projects(id) ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
// Language skills table (für Kompatibilität)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS language_skills (
|
||||
id TEXT PRIMARY KEY,
|
||||
employee_id TEXT NOT NULL,
|
||||
language TEXT NOT NULL,
|
||||
proficiency TEXT NOT NULL,
|
||||
certified INTEGER DEFAULT 0,
|
||||
certificate_type TEXT,
|
||||
is_native INTEGER DEFAULT 0,
|
||||
can_interpret INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
||||
)
|
||||
`)
|
||||
|
||||
// Specializations table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS specializations (
|
||||
id TEXT PRIMARY KEY,
|
||||
employee_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE
|
||||
)
|
||||
`)
|
||||
|
||||
// Sync log table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sync_log (
|
||||
id TEXT PRIMARY KEY,
|
||||
sync_time TEXT NOT NULL,
|
||||
success INTEGER NOT NULL,
|
||||
items_synced INTEGER,
|
||||
error_message TEXT,
|
||||
duration INTEGER
|
||||
)
|
||||
`)
|
||||
|
||||
// Create default admin user if not exists
|
||||
const adminExists = db.prepare('SELECT id FROM users WHERE username = ?').get('admin')
|
||||
if (!adminExists) {
|
||||
const hashedPassword = bcrypt.hashSync('admin123', 10)
|
||||
const now = new Date().toISOString()
|
||||
db.prepare(`
|
||||
INSERT INTO users (id, username, email, password, role, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
uuidv4(),
|
||||
'admin',
|
||||
'admin@skillmate.local',
|
||||
hashedPassword,
|
||||
'admin',
|
||||
1,
|
||||
now,
|
||||
now
|
||||
)
|
||||
}
|
||||
|
||||
// Workspace Management Tables
|
||||
|
||||
// Workspaces (Desks, Meeting Rooms, etc.)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS workspaces (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('desk', 'meeting_room', 'phone_booth', 'parking', 'locker')),
|
||||
floor TEXT NOT NULL,
|
||||
building TEXT,
|
||||
capacity INTEGER DEFAULT 1,
|
||||
equipment TEXT, -- JSON array of equipment
|
||||
position_x INTEGER, -- For floor plan visualization
|
||||
position_y INTEGER,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
|
||||
// Bookings table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS bookings (
|
||||
id TEXT PRIMARY KEY,
|
||||
workspace_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
employee_id TEXT NOT NULL,
|
||||
start_time TEXT NOT NULL,
|
||||
end_time TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK(status IN ('confirmed', 'cancelled', 'completed', 'no_show')),
|
||||
check_in_time TEXT,
|
||||
check_out_time TEXT,
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (workspace_id) REFERENCES workspaces(id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id)
|
||||
)
|
||||
`)
|
||||
|
||||
// Recurring bookings
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS recurring_bookings (
|
||||
id TEXT PRIMARY KEY,
|
||||
workspace_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
employee_id TEXT NOT NULL,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT NOT NULL,
|
||||
time_start TEXT NOT NULL,
|
||||
time_end TEXT NOT NULL,
|
||||
days_of_week TEXT NOT NULL, -- JSON array of days [1,2,3,4,5] for Mon-Fri
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (workspace_id) REFERENCES workspaces(id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (employee_id) REFERENCES employees(id)
|
||||
)
|
||||
`)
|
||||
|
||||
// Booking rules and restrictions
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS booking_rules (
|
||||
id TEXT PRIMARY KEY,
|
||||
workspace_type TEXT,
|
||||
max_duration_hours INTEGER,
|
||||
max_advance_days INTEGER,
|
||||
min_advance_hours INTEGER,
|
||||
max_bookings_per_user_per_day INTEGER,
|
||||
max_bookings_per_user_per_week INTEGER,
|
||||
auto_release_minutes INTEGER, -- Auto-release if not checked in
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
|
||||
// Analytics data
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS workspace_analytics (
|
||||
id TEXT PRIMARY KEY,
|
||||
workspace_id TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
total_bookings INTEGER DEFAULT 0,
|
||||
total_hours_booked REAL DEFAULT 0,
|
||||
utilization_rate REAL DEFAULT 0,
|
||||
no_show_count INTEGER DEFAULT 0,
|
||||
unique_users INTEGER DEFAULT 0,
|
||||
peak_hour INTEGER,
|
||||
FOREIGN KEY (workspace_id) REFERENCES workspaces(id),
|
||||
UNIQUE(workspace_id, date)
|
||||
)
|
||||
`)
|
||||
|
||||
// Floor plans
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS floor_plans (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
floor TEXT NOT NULL,
|
||||
building TEXT,
|
||||
image_url TEXT,
|
||||
svg_data TEXT, -- SVG data for interactive floor plan
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
)
|
||||
`)
|
||||
|
||||
// Audit Log für Änderungsverfolgung
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id TEXT PRIMARY KEY,
|
||||
entity_type TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL CHECK(action IN ('create', 'update', 'delete')),
|
||||
user_id TEXT NOT NULL,
|
||||
changes TEXT, -- JSON mit Änderungen
|
||||
timestamp TEXT NOT NULL,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_user ON audit_log(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
|
||||
`)
|
||||
|
||||
// Reminder-System
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS reminders (
|
||||
id TEXT PRIMARY KEY,
|
||||
profile_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('annual_update', 'overdue', 'custom')),
|
||||
message TEXT,
|
||||
sent_at TEXT,
|
||||
acknowledged_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reminders_profile ON reminders(profile_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_reminders_sent ON reminders(sent_at);
|
||||
`)
|
||||
|
||||
// Kontrollierte Vokabulare/Tags
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS controlled_vocabulary (
|
||||
id TEXT PRIMARY KEY,
|
||||
category TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE(category, value)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_vocab_category ON controlled_vocabulary(category);
|
||||
`)
|
||||
|
||||
// Create indexes for better performance
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_employees_availability ON employees(availability);
|
||||
CREATE INDEX IF NOT EXISTS idx_employees_department ON employees(department);
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_skills_employee ON employee_skills(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_employee_skills_skill ON employee_skills(skill_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_language_skills_employee ON language_skills(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_specializations_employee ON specializations(employee_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bookings_workspace ON bookings(workspace_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bookings_user ON bookings(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bookings_start_time ON bookings(start_time);
|
||||
CREATE INDEX IF NOT EXISTS idx_bookings_status ON bookings(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_workspace_analytics_date ON workspace_analytics(date);
|
||||
`)
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren