653 Zeilen
22 KiB
JavaScript
653 Zeilen
22 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');
|
|
}
|
|
|
|
// 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
|
|
)
|
|
`);
|
|
|
|
// 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');
|
|
}
|
|
|
|
// 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);
|
|
`);
|
|
|
|
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 username = ? AND role = ?').get('admin', '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
|
|
};
|