Files
TaskMate/backend/database.js
hendrik_gebhardt@gmx.de ef153789cc UI-Anpassungen
2026-01-10 10:32:52 +00:00

791 Zeilen
27 KiB
JavaScript

/**
* TASKMATE - Datenbank
* ====================
* SQLite-Datenbank mit better-sqlite3
*/
const Database = require('better-sqlite3');
const path = require('path');
const bcrypt = require('bcryptjs');
const logger = require('./utils/logger');
// Datenbank-Pfad
const DB_PATH = process.env.DB_PATH || path.join(__dirname, 'data', 'taskmate.db');
let db = null;
/**
* Datenbank initialisieren
*/
async function initialize() {
try {
// Datenbank öffnen/erstellen
db = new Database(DB_PATH);
// WAL-Modus für bessere Performance
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// Tabellen erstellen
createTables();
// Standard-Benutzer erstellen (falls nicht vorhanden)
await createDefaultUsers();
logger.info('Datenbank initialisiert');
return db;
} catch (error) {
logger.error('Datenbank-Fehler:', error);
throw error;
}
}
/**
* Tabellen erstellen
*/
function createTables() {
// Benutzer
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
display_name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#00D4FF',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_login DATETIME,
failed_attempts INTEGER DEFAULT 0,
locked_until DATETIME
)
`);
// Login-Audit
db.exec(`
CREATE TABLE IF NOT EXISTS login_audit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
ip_address TEXT,
success INTEGER NOT NULL,
user_agent TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
// Projekte
db.exec(`
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
archived INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Spalten
db.exec(`
CREATE TABLE IF NOT EXISTS columns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
name TEXT NOT NULL,
position INTEGER NOT NULL,
color TEXT,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
)
`);
// Labels
db.exec(`
CREATE TABLE IF NOT EXISTS labels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
name TEXT NOT NULL,
color TEXT NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
)
`);
// Aufgaben
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
column_id INTEGER NOT NULL,
title TEXT NOT NULL,
description TEXT,
priority TEXT DEFAULT 'medium',
start_date DATE,
due_date DATE,
assigned_to INTEGER,
time_estimate_min INTEGER,
depends_on INTEGER,
position INTEGER NOT NULL,
archived INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (column_id) REFERENCES columns(id) ON DELETE CASCADE,
FOREIGN KEY (assigned_to) REFERENCES users(id),
FOREIGN KEY (depends_on) REFERENCES tasks(id) ON DELETE SET NULL,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Migration: Add start_date column if it doesn't exist
const columns = db.prepare("PRAGMA table_info(tasks)").all();
const hasStartDate = columns.some(col => col.name === 'start_date');
if (!hasStartDate) {
db.exec('ALTER TABLE tasks ADD COLUMN start_date DATE');
logger.info('Migration: start_date Spalte zu tasks hinzugefuegt');
}
// Migration: Add role and permissions columns to users
const userColumns = db.prepare("PRAGMA table_info(users)").all();
const hasRole = userColumns.some(col => col.name === 'role');
if (!hasRole) {
db.exec("ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user'");
logger.info('Migration: role Spalte zu users hinzugefuegt');
}
const hasPermissions = userColumns.some(col => col.name === 'permissions');
if (!hasPermissions) {
db.exec("ALTER TABLE users ADD COLUMN permissions TEXT DEFAULT '[]'");
logger.info('Migration: permissions Spalte zu users hinzugefuegt');
}
// Migration: Add email column to users
const hasEmail = userColumns.some(col => col.name === 'email');
if (!hasEmail) {
db.exec("ALTER TABLE users ADD COLUMN email TEXT");
logger.info('Migration: email Spalte zu users hinzugefuegt');
}
// Migration: Add repositories_base_path column to users
const hasRepoBasePath = userColumns.some(col => col.name === 'repositories_base_path');
if (!hasRepoBasePath) {
db.exec("ALTER TABLE users ADD COLUMN repositories_base_path TEXT");
logger.info('Migration: repositories_base_path Spalte zu users hinzugefuegt');
}
// Migration: Add custom_initials column to users
const hasCustomInitials = userColumns.some(col => col.name === 'custom_initials');
if (!hasCustomInitials) {
db.exec("ALTER TABLE users ADD COLUMN custom_initials TEXT");
logger.info('Migration: custom_initials Spalte zu users hinzugefuegt');
}
// Migration: Add initials column and prepare email
const hasInitials = userColumns.some(col => col.name === 'initials');
if (!hasInitials && userColumns.some(col => col.name === 'username')) {
logger.info('Migration: Füge initials Spalte hinzu und bereite E-Mail vor');
// Zuerst Daten vorbereiten
const users = db.prepare('SELECT id, username, email, custom_initials FROM users').all();
for (const user of users) {
// Stelle sicher dass jeder Benutzer eine E-Mail hat
if (!user.email || user.email === '') {
if (user.username === 'admin') {
// Admin bekommt eine spezielle E-Mail
db.prepare('UPDATE users SET email = ? WHERE id = ?').run('admin@taskmate.local', user.id);
} else if (user.username.includes('@')) {
// Username enthält bereits E-Mail (wie bei bestehenden Benutzern)
db.prepare('UPDATE users SET email = ? WHERE id = ?').run(user.username, user.id);
}
}
// Initialen setzen (aus custom_initials oder generieren)
if (!user.custom_initials || user.custom_initials === '') {
let initials = 'XX';
if (user.username === 'admin') {
initials = 'AD';
} else if (user.email || user.username.includes('@')) {
// Generiere Initialen aus E-Mail
const emailPart = (user.email || user.username).split('@')[0];
if (emailPart.includes('_')) {
const parts = emailPart.split('_');
initials = (parts[0][0] + parts[1][0]).toUpperCase();
} else if (emailPart.includes('.')) {
const parts = emailPart.split('.');
initials = (parts[0][0] + parts[1][0]).toUpperCase();
} else {
initials = emailPart.substring(0, 2).toUpperCase();
}
}
db.prepare('UPDATE users SET custom_initials = ? WHERE id = ?').run(initials, user.id);
}
}
// Neue initials Spalte hinzufügen
db.exec("ALTER TABLE users ADD COLUMN initials TEXT");
// Daten von custom_initials nach initials kopieren
db.exec("UPDATE users SET initials = custom_initials");
logger.info('Migration: initials Spalte hinzugefügt und E-Mail-Daten vorbereitet');
}
// Proposals (Vorschlaege)
db.exec(`
CREATE TABLE IF NOT EXISTS proposals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
created_by INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
approved INTEGER DEFAULT 0,
approved_by INTEGER,
approved_at DATETIME,
FOREIGN KEY (created_by) REFERENCES users(id),
FOREIGN KEY (approved_by) REFERENCES users(id)
)
`);
// Proposal Votes
db.exec(`
CREATE TABLE IF NOT EXISTS proposal_votes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
proposal_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
// Unique constraint for proposal votes (one vote per user per proposal)
db.exec(`
CREATE UNIQUE INDEX IF NOT EXISTS idx_proposal_votes_unique
ON proposal_votes(proposal_id, user_id)
`);
// Index for proposal votes
db.exec(`
CREATE INDEX IF NOT EXISTS idx_proposal_votes_proposal
ON proposal_votes(proposal_id)
`);
// Migration: Add archived, task_id, and project_id columns to proposals
const proposalColumns = db.prepare("PRAGMA table_info(proposals)").all();
const hasProposalArchived = proposalColumns.some(col => col.name === 'archived');
if (!hasProposalArchived) {
db.exec('ALTER TABLE proposals ADD COLUMN archived INTEGER DEFAULT 0');
logger.info('Migration: archived Spalte zu proposals hinzugefuegt');
}
const hasProposalTaskId = proposalColumns.some(col => col.name === 'task_id');
if (!hasProposalTaskId) {
db.exec('ALTER TABLE proposals ADD COLUMN task_id INTEGER REFERENCES tasks(id) ON DELETE SET NULL');
logger.info('Migration: task_id Spalte zu proposals hinzugefuegt');
}
const hasProposalProjectId = proposalColumns.some(col => col.name === 'project_id');
if (!hasProposalProjectId) {
db.exec('ALTER TABLE proposals ADD COLUMN project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE');
logger.info('Migration: project_id Spalte zu proposals hinzugefuegt');
}
// Migration: Add filter_category column to columns
const columnColumns = db.prepare("PRAGMA table_info(columns)").all();
const hasFilterCategory = columnColumns.some(col => col.name === 'filter_category');
if (!hasFilterCategory) {
db.exec("ALTER TABLE columns ADD COLUMN filter_category TEXT DEFAULT 'in_progress'");
logger.info('Migration: filter_category Spalte zu columns hinzugefuegt');
// Set default values for existing columns based on position
const projects = db.prepare('SELECT id FROM projects').all();
for (const project of projects) {
const cols = db.prepare('SELECT id, position FROM columns WHERE project_id = ? ORDER BY position').all(project.id);
if (cols.length > 0) {
// First column = open
db.prepare("UPDATE columns SET filter_category = 'open' WHERE id = ?").run(cols[0].id);
// Last column = completed
if (cols.length > 1) {
db.prepare("UPDATE columns SET filter_category = 'completed' WHERE id = ?").run(cols[cols.length - 1].id);
}
}
}
logger.info('Migration: Standard-Filterkategorien fuer bestehende Spalten gesetzt');
}
// Task-Labels (Verknüpfung)
db.exec(`
CREATE TABLE IF NOT EXISTS task_labels (
task_id INTEGER NOT NULL,
label_id INTEGER NOT NULL,
PRIMARY KEY (task_id, label_id),
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE
)
`);
// Task-Assignees (Mehrfachzuweisung von Mitarbeitern)
db.exec(`
CREATE TABLE IF NOT EXISTS task_assignees (
task_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
PRIMARY KEY (task_id, user_id),
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// Unteraufgaben
db.exec(`
CREATE TABLE IF NOT EXISTS subtasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
title TEXT NOT NULL,
completed INTEGER DEFAULT 0,
position INTEGER NOT NULL,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
)
`);
// Kommentare
db.exec(`
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
// Anhänge
db.exec(`
CREATE TABLE IF NOT EXISTS attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
mime_type TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
uploaded_by INTEGER,
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (uploaded_by) REFERENCES users(id)
)
`);
// Links
db.exec(`
CREATE TABLE IF NOT EXISTS links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
title TEXT,
url TEXT NOT NULL,
created_by INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Aufgaben-Vorlagen
db.exec(`
CREATE TABLE IF NOT EXISTS task_templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
name TEXT NOT NULL,
title_template TEXT,
description TEXT,
priority TEXT,
labels TEXT,
subtasks TEXT,
time_estimate_min INTEGER,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
)
`);
// Historie
db.exec(`
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
action TEXT NOT NULL,
field_changed TEXT,
old_value TEXT,
new_value TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
// Einstellungen
db.exec(`
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
)
`);
// Refresh Tokens für sichere Token-Rotation
db.exec(`
CREATE TABLE IF NOT EXISTS refresh_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
token TEXT NOT NULL UNIQUE,
expires_at DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_used DATETIME,
user_agent TEXT,
ip_address TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);
// Index für Token-Lookup
db.exec(`CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token)`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires ON refresh_tokens(expires_at)`);
// Anwendungen (Git-Repositories pro Projekt)
db.exec(`
CREATE TABLE IF NOT EXISTS applications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL UNIQUE,
local_path TEXT NOT NULL,
gitea_repo_url TEXT,
gitea_repo_owner TEXT,
gitea_repo_name TEXT,
default_branch TEXT DEFAULT 'main',
last_sync DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Benachrichtigungen (Inbox)
db.exec(`
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
type TEXT NOT NULL,
title TEXT NOT NULL,
message TEXT,
task_id INTEGER,
project_id INTEGER,
proposal_id INTEGER,
actor_id INTEGER,
is_read INTEGER DEFAULT 0,
is_persistent INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
FOREIGN KEY (actor_id) REFERENCES users(id) ON DELETE SET NULL
)
`);
// Erinnerungen
db.exec(`
CREATE TABLE IF NOT EXISTS reminders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL,
title TEXT NOT NULL,
description TEXT,
reminder_date DATE NOT NULL,
reminder_time TIME DEFAULT '09:00',
color TEXT DEFAULT '#F59E0B',
advance_days TEXT DEFAULT '1',
repeat_type TEXT DEFAULT 'none',
repeat_interval INTEGER DEFAULT 1,
is_active INTEGER DEFAULT 1,
created_by INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Erinnerungs-Benachrichtigungen (für Tracking welche bereits gesendet wurden)
db.exec(`
CREATE TABLE IF NOT EXISTS reminder_notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
reminder_id INTEGER NOT NULL,
notification_date DATE NOT NULL,
sent INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (reminder_id) REFERENCES reminders(id) ON DELETE CASCADE,
UNIQUE(reminder_id, notification_date)
)
`);
// Wissensmanagement - Kategorien
db.exec(`
CREATE TABLE IF NOT EXISTS knowledge_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
color TEXT DEFAULT '#3B82F6',
icon TEXT,
position INTEGER NOT NULL DEFAULT 0,
created_by INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Wissensmanagement - Einträge
db.exec(`
CREATE TABLE IF NOT EXISTS knowledge_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
title TEXT NOT NULL,
url TEXT,
notes TEXT,
position INTEGER NOT NULL DEFAULT 0,
created_by INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES knowledge_categories(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Wissensmanagement - Anhänge
db.exec(`
CREATE TABLE IF NOT EXISTS knowledge_attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
entry_id INTEGER NOT NULL,
filename TEXT NOT NULL,
original_name TEXT NOT NULL,
mime_type TEXT NOT NULL,
size_bytes INTEGER NOT NULL,
uploaded_by INTEGER,
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (entry_id) REFERENCES knowledge_entries(id) ON DELETE CASCADE,
FOREIGN KEY (uploaded_by) REFERENCES users(id)
)
`);
// Coding-Verzeichnisse (projektübergreifend)
db.exec(`
CREATE TABLE IF NOT EXISTS coding_directories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
local_path TEXT NOT NULL UNIQUE,
description TEXT,
color TEXT DEFAULT '#4F46E5',
gitea_repo_url TEXT,
gitea_repo_owner TEXT,
gitea_repo_name TEXT,
default_branch TEXT DEFAULT 'main',
last_sync DATETIME,
position INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Migration: Add claude_instructions column to coding_directories
const codingDirColumns = db.prepare("PRAGMA table_info(coding_directories)").all();
const hasClaudeInstructions = codingDirColumns.some(col => col.name === 'claude_instructions');
if (!hasClaudeInstructions) {
db.exec('ALTER TABLE coding_directories ADD COLUMN claude_instructions TEXT');
logger.info('Migration: claude_instructions Spalte zu coding_directories hinzugefuegt');
}
// Coding Verbrauchsdaten
db.exec(`
CREATE TABLE IF NOT EXISTS coding_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
directory_id INTEGER NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
cpu_percent REAL,
memory_mb REAL,
disk_read_mb REAL,
disk_write_mb REAL,
network_recv_mb REAL,
network_sent_mb REAL,
process_count INTEGER,
FOREIGN KEY (directory_id) REFERENCES coding_directories(id) ON DELETE CASCADE
)
`);
// Kontakte
db.exec(`
CREATE TABLE IF NOT EXISTS contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT,
last_name TEXT,
company TEXT,
position TEXT,
email TEXT,
phone TEXT,
mobile TEXT,
address TEXT,
postal_code TEXT,
city TEXT,
country TEXT,
website TEXT,
notes TEXT,
tags TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
created_by INTEGER,
FOREIGN KEY (created_by) REFERENCES users(id)
)
`);
// Indizes für Performance
db.exec(`
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project_id);
CREATE INDEX IF NOT EXISTS idx_tasks_column ON tasks(column_id);
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to);
CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date);
CREATE INDEX IF NOT EXISTS idx_subtasks_task ON subtasks(task_id);
CREATE INDEX IF NOT EXISTS idx_comments_task ON comments(task_id);
CREATE INDEX IF NOT EXISTS idx_history_task ON history(task_id);
CREATE INDEX IF NOT EXISTS idx_attachments_task ON attachments(task_id);
CREATE INDEX IF NOT EXISTS idx_links_task ON links(task_id);
CREATE INDEX IF NOT EXISTS idx_task_labels_task ON task_labels(task_id);
CREATE INDEX IF NOT EXISTS idx_task_labels_label ON task_labels(label_id);
CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id);
CREATE INDEX IF NOT EXISTS idx_notifications_user_read ON notifications(user_id, is_read);
CREATE INDEX IF NOT EXISTS idx_notifications_created ON notifications(created_at);
CREATE INDEX IF NOT EXISTS idx_applications_project ON applications(project_id);
CREATE INDEX IF NOT EXISTS idx_knowledge_entries_category ON knowledge_entries(category_id);
CREATE INDEX IF NOT EXISTS idx_knowledge_attachments_entry ON knowledge_attachments(entry_id);
CREATE INDEX IF NOT EXISTS idx_coding_directories_position ON coding_directories(position);
CREATE INDEX IF NOT EXISTS idx_coding_usage_directory ON coding_usage(directory_id);
CREATE INDEX IF NOT EXISTS idx_coding_usage_timestamp ON coding_usage(timestamp);
CREATE INDEX IF NOT EXISTS idx_contacts_company ON contacts(company);
CREATE INDEX IF NOT EXISTS idx_contacts_tags ON contacts(tags);
`);
logger.info('Datenbank-Tabellen erstellt');
}
/**
* Standard-Benutzer erstellen und Admin-Passwort korrigieren
*/
async function createDefaultUsers() {
const existingUsers = db.prepare('SELECT COUNT(*) as count FROM users').get();
// Admin-Passwort korrigieren (falls aus .env verschieden)
const adminExists = db.prepare('SELECT id, password_hash FROM users WHERE email = ? AND role = ?').get('admin@taskmate.local', 'admin');
if (adminExists) {
const correctAdminPassword = process.env.ADMIN_PASSWORD || 'admin123';
const bcrypt = require('bcryptjs');
// Prüfen ob das Passwort bereits korrekt ist
const isCorrect = await bcrypt.compare(correctAdminPassword, adminExists.password_hash);
if (!isCorrect) {
logger.info('Admin-Passwort wird aus .env aktualisiert');
const correctHash = await bcrypt.hash(correctAdminPassword, 12);
db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(correctHash, adminExists.id);
logger.info('Admin-Passwort erfolgreich aktualisiert');
} else {
logger.info('Admin-Passwort bereits korrekt');
}
}
if (existingUsers.count === 0) {
// Benutzer aus Umgebungsvariablen
const user1 = {
username: process.env.USER1_USERNAME || 'user1',
password: process.env.USER1_PASSWORD || 'changeme123',
displayName: process.env.USER1_DISPLAYNAME || 'Benutzer 1',
color: process.env.USER1_COLOR || '#00D4FF'
};
const user2 = {
username: process.env.USER2_USERNAME || 'user2',
password: process.env.USER2_PASSWORD || 'changeme456',
displayName: process.env.USER2_DISPLAYNAME || 'Benutzer 2',
color: process.env.USER2_COLOR || '#FF9500'
};
const insertUser = db.prepare(`
INSERT INTO users (username, password_hash, display_name, color, role, permissions)
VALUES (?, ?, ?, ?, ?, ?)
`);
// Admin-Benutzer
const adminUser = {
username: process.env.ADMIN_USERNAME || 'admin',
password: process.env.ADMIN_PASSWORD || 'admin123',
displayName: process.env.ADMIN_DISPLAYNAME || 'Administrator',
color: process.env.ADMIN_COLOR || '#8B5CF6'
};
// Passwoerter hashen und Benutzer erstellen
const hash1 = await bcrypt.hash(user1.password, 12);
const hash2 = await bcrypt.hash(user2.password, 12);
const hashAdmin = await bcrypt.hash(adminUser.password, 12);
insertUser.run(user1.username, hash1, user1.displayName, user1.color, 'user', '[]');
insertUser.run(user2.username, hash2, user2.displayName, user2.color, 'user', '[]');
insertUser.run(adminUser.username, hashAdmin, adminUser.displayName, adminUser.color, 'admin', '[]');
logger.info('Standard-Benutzer und Admin erstellt');
// Standard-Projekt erstellen
const projectResult = db.prepare(`
INSERT INTO projects (name, description, created_by)
VALUES (?, ?, ?)
`).run('Mein erstes Projekt', 'Willkommen bei TaskMate!', 1);
const projectId = projectResult.lastInsertRowid;
// Standard-Spalten erstellen
const insertColumn = db.prepare(`
INSERT INTO columns (project_id, name, position, color)
VALUES (?, ?, ?, ?)
`);
insertColumn.run(projectId, 'Offen', 0, null);
insertColumn.run(projectId, 'In Arbeit', 1, null);
insertColumn.run(projectId, 'Erledigt', 2, null);
// Standard-Labels erstellen
const insertLabel = db.prepare(`
INSERT INTO labels (project_id, name, color)
VALUES (?, ?, ?)
`);
insertLabel.run(projectId, 'Bug', '#DC2626');
insertLabel.run(projectId, 'Feature', '#059669');
insertLabel.run(projectId, 'Dokumentation', '#3182CE');
logger.info('Standard-Projekt mit Spalten und Labels erstellt');
}
}
/**
* Datenbank-Instanz abrufen
*/
function getDb() {
if (!db) {
throw new Error('Datenbank nicht initialisiert');
}
return db;
}
/**
* Datenbank schließen
*/
function close() {
if (db) {
db.close();
logger.info('Datenbank geschlossen');
}
}
module.exports = {
initialize,
getDb,
close
};