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

946 Zeilen
28 KiB
JavaScript

/**
* TASKMATE - Knowledge Management Routes
* ======================================
* CRUD für Wissensmanagement: Kategorien, Einträge, Anhänge
*/
const express = require('express');
const router = express.Router();
const path = require('path');
const fs = require('fs');
const multer = require('multer');
const { getDb } = require('../database');
const logger = require('../utils/logger');
const { validators, stripHtml } = require('../middleware/validation');
// Upload-Konfiguration für Knowledge-Anhänge
const UPLOAD_DIR = path.join(__dirname, '..', 'uploads', 'knowledge');
// Sicherstellen, dass das Upload-Verzeichnis existiert
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, UPLOAD_DIR);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, `knowledge-${uniqueSuffix}${ext}`);
}
});
const upload = multer({
storage,
limits: {
fileSize: 50 * 1024 * 1024 // 50MB max
}
});
// =====================
// KATEGORIEN
// =====================
/**
* GET /api/knowledge/categories
* Alle Kategorien abrufen
*/
router.get('/categories', (req, res) => {
try {
const db = getDb();
const categories = db.prepare(`
SELECT kc.*,
(SELECT COUNT(*) FROM knowledge_entries WHERE category_id = kc.id) as entry_count,
u.display_name as creator_name
FROM knowledge_categories kc
LEFT JOIN users u ON kc.created_by = u.id
ORDER BY kc.position, kc.created_at
`).all();
res.json(categories.map(c => ({
id: c.id,
name: c.name,
description: c.description,
color: c.color,
icon: c.icon,
position: c.position,
entryCount: c.entry_count,
createdBy: c.created_by,
creatorName: c.creator_name,
createdAt: c.created_at
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Knowledge-Kategorien:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/knowledge/categories
* Neue Kategorie erstellen
*/
router.post('/categories', (req, res) => {
try {
const { name, description, color, icon } = req.body;
// Validierung
const nameError = validators.required(name, 'Name') ||
validators.maxLength(name, 50, 'Name');
if (nameError) {
return res.status(400).json({ error: nameError });
}
if (color) {
const colorError = validators.hexColor(color, 'Farbe');
if (colorError) {
return res.status(400).json({ error: colorError });
}
}
const db = getDb();
// Duplikat-Prüfung
const existing = db.prepare(
'SELECT id FROM knowledge_categories WHERE LOWER(name) = LOWER(?)'
).get(name);
if (existing) {
return res.status(400).json({ error: 'Eine Kategorie mit diesem Namen existiert bereits' });
}
// Alle bestehenden Kategorien um 1 nach unten verschieben
db.prepare(`
UPDATE knowledge_categories
SET position = position + 1
`).run();
// Neue Kategorie an Position 0 (ganz oben) einfügen
const result = db.prepare(`
INSERT INTO knowledge_categories (name, description, color, icon, position, created_by)
VALUES (?, ?, ?, ?, ?, ?)
`).run(
stripHtml(name),
description ? stripHtml(description) : null,
color || '#3B82F6',
icon || null,
0, // Neue Kategorien immer an Position 0 (oben)
req.user.id
);
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?')
.get(result.lastInsertRowid);
logger.info(`Knowledge-Kategorie erstellt: ${name}`);
res.status(201).json({
id: category.id,
name: category.name,
description: category.description,
color: category.color,
icon: category.icon,
position: category.position,
entryCount: 0,
createdBy: category.created_by,
createdAt: category.created_at
});
} catch (error) {
logger.error('Fehler beim Erstellen der Knowledge-Kategorie:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/knowledge/categories/:id
* Kategorie aktualisieren
*/
router.put('/categories/:id', (req, res) => {
try {
const categoryId = req.params.id;
const { name, description, color, icon } = req.body;
const db = getDb();
const existing = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
if (!existing) {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
// Validierung
if (name) {
const nameError = validators.maxLength(name, 50, 'Name');
if (nameError) {
return res.status(400).json({ error: nameError });
}
// Duplikat-Prüfung (ausser eigene)
const duplicate = db.prepare(
'SELECT id FROM knowledge_categories WHERE LOWER(name) = LOWER(?) AND id != ?'
).get(name, categoryId);
if (duplicate) {
return res.status(400).json({ error: 'Eine Kategorie mit diesem Namen existiert bereits' });
}
}
if (color) {
const colorError = validators.hexColor(color, 'Farbe');
if (colorError) {
return res.status(400).json({ error: colorError });
}
}
db.prepare(`
UPDATE knowledge_categories SET
name = COALESCE(?, name),
description = COALESCE(?, description),
color = COALESCE(?, color),
icon = COALESCE(?, icon)
WHERE id = ?
`).run(
name ? stripHtml(name) : null,
description !== undefined ? (description ? stripHtml(description) : '') : null,
color || null,
icon !== undefined ? icon : null,
categoryId
);
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
logger.info(`Knowledge-Kategorie aktualisiert: ${category.name}`);
res.json({
id: category.id,
name: category.name,
description: category.description,
color: category.color,
icon: category.icon,
position: category.position,
createdBy: category.created_by,
createdAt: category.created_at
});
} catch (error) {
logger.error('Fehler beim Aktualisieren der Knowledge-Kategorie:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/knowledge/categories/:id/position
* Kategorie-Position ändern
*/
router.put('/categories/:id/position', (req, res) => {
try {
const categoryId = req.params.id;
const { newPosition } = req.body;
const db = getDb();
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
if (!category) {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
const oldPosition = category.position;
if (newPosition > oldPosition) {
db.prepare(`
UPDATE knowledge_categories SET position = position - 1
WHERE position > ? AND position <= ?
`).run(oldPosition, newPosition);
} else if (newPosition < oldPosition) {
db.prepare(`
UPDATE knowledge_categories SET position = position + 1
WHERE position >= ? AND position < ?
`).run(newPosition, oldPosition);
}
db.prepare('UPDATE knowledge_categories SET position = ? WHERE id = ?').run(newPosition, categoryId);
const categories = db.prepare(
'SELECT * FROM knowledge_categories ORDER BY position'
).all();
res.json({
categories: categories.map(c => ({
id: c.id,
name: c.name,
position: c.position
}))
});
} catch (error) {
logger.error('Fehler beim Verschieben der Knowledge-Kategorie:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/knowledge/categories/:id
* Kategorie löschen (inkl. aller Einträge und Anhänge)
*/
router.delete('/categories/:id', (req, res) => {
try {
const categoryId = req.params.id;
const db = getDb();
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
if (!category) {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
// Anhänge von allen Einträgen dieser Kategorie löschen
const entries = db.prepare('SELECT id FROM knowledge_entries WHERE category_id = ?').all(categoryId);
for (const entry of entries) {
const attachments = db.prepare('SELECT * FROM knowledge_attachments WHERE entry_id = ?').all(entry.id);
for (const attachment of attachments) {
const filePath = path.join(UPLOAD_DIR, attachment.filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
}
// Kategorie löschen (CASCADE löscht Einträge und Anhänge aus DB)
db.prepare('DELETE FROM knowledge_categories WHERE id = ?').run(categoryId);
// Positionen neu nummerieren
const remaining = db.prepare(
'SELECT id FROM knowledge_categories ORDER BY position'
).all();
remaining.forEach((c, idx) => {
db.prepare('UPDATE knowledge_categories SET position = ? WHERE id = ?').run(idx, c.id);
});
logger.info(`Knowledge-Kategorie gelöscht: ${category.name}`);
res.json({ message: 'Kategorie gelöscht' });
} catch (error) {
logger.error('Fehler beim Löschen der Knowledge-Kategorie:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
// =====================
// EINTRÄGE
// =====================
/**
* GET /api/knowledge/entries
* Einträge abrufen (optional gefiltert nach Kategorie)
*/
router.get('/entries', (req, res) => {
try {
const { categoryId } = req.query;
const db = getDb();
let query = `
SELECT ke.*,
kc.name as category_name,
kc.color as category_color,
u.display_name as creator_name,
(SELECT COUNT(*) FROM knowledge_attachments WHERE entry_id = ke.id) as attachment_count
FROM knowledge_entries ke
LEFT JOIN knowledge_categories kc ON ke.category_id = kc.id
LEFT JOIN users u ON ke.created_by = u.id
`;
const params = [];
if (categoryId) {
query += ' WHERE ke.category_id = ?';
params.push(categoryId);
}
query += ' ORDER BY ke.position, ke.created_at DESC';
const entries = db.prepare(query).all(...params);
res.json(entries.map(e => ({
id: e.id,
categoryId: e.category_id,
categoryName: e.category_name,
categoryColor: e.category_color,
title: e.title,
url: e.url,
notes: e.notes,
position: e.position,
attachmentCount: e.attachment_count,
createdBy: e.created_by,
creatorName: e.creator_name,
createdAt: e.created_at,
updatedAt: e.updated_at
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Knowledge-Einträge:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/knowledge/entries/:id
* Einzelnen Eintrag abrufen (mit Anhängen)
*/
router.get('/entries/:id', (req, res) => {
try {
const entryId = req.params.id;
const db = getDb();
const entry = db.prepare(`
SELECT ke.*,
kc.name as category_name,
kc.color as category_color,
u.display_name as creator_name
FROM knowledge_entries ke
LEFT JOIN knowledge_categories kc ON ke.category_id = kc.id
LEFT JOIN users u ON ke.created_by = u.id
WHERE ke.id = ?
`).get(entryId);
if (!entry) {
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
const attachments = db.prepare(`
SELECT ka.*, u.display_name as uploader_name
FROM knowledge_attachments ka
LEFT JOIN users u ON ka.uploaded_by = u.id
WHERE ka.entry_id = ?
ORDER BY ka.uploaded_at DESC
`).all(entryId);
res.json({
id: entry.id,
categoryId: entry.category_id,
categoryName: entry.category_name,
categoryColor: entry.category_color,
title: entry.title,
url: entry.url,
notes: entry.notes,
position: entry.position,
createdBy: entry.created_by,
creatorName: entry.creator_name,
createdAt: entry.created_at,
updatedAt: entry.updated_at,
attachments: attachments.map(a => ({
id: a.id,
filename: a.filename,
originalName: a.original_name,
mimeType: a.mime_type,
sizeBytes: a.size_bytes,
uploadedBy: a.uploaded_by,
uploaderName: a.uploader_name,
uploadedAt: a.uploaded_at
}))
});
} catch (error) {
logger.error('Fehler beim Abrufen des Knowledge-Eintrags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/knowledge/entries
* Neuen Eintrag erstellen
*/
router.post('/entries', (req, res) => {
try {
const { categoryId, title, url, notes } = req.body;
// Validierung
const categoryError = validators.required(categoryId, 'Kategorie');
if (categoryError) {
return res.status(400).json({ error: categoryError });
}
const titleError = validators.required(title, 'Titel') ||
validators.maxLength(title, 200, 'Titel');
if (titleError) {
return res.status(400).json({ error: titleError });
}
if (url) {
const urlError = validators.url(url, 'URL');
if (urlError) {
return res.status(400).json({ error: urlError });
}
}
const db = getDb();
// Kategorie prüfen
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
if (!category) {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
// Alle bestehenden Einträge um 1 nach unten verschieben
db.prepare(`
UPDATE knowledge_entries
SET position = position + 1
WHERE category_id = ?
`).run(categoryId);
// Neuen Eintrag an Position 0 (ganz oben) einfügen
const result = db.prepare(`
INSERT INTO knowledge_entries (category_id, title, url, notes, position, created_by)
VALUES (?, ?, ?, ?, ?, ?)
`).run(
categoryId,
stripHtml(title),
url || null,
notes || null,
0, // Neue Einträge immer an Position 0 (oben)
req.user.id
);
const entry = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?')
.get(result.lastInsertRowid);
logger.info(`Knowledge-Eintrag erstellt: ${title}`);
res.status(201).json({
id: entry.id,
categoryId: entry.category_id,
title: entry.title,
url: entry.url,
notes: entry.notes,
position: entry.position,
attachmentCount: 0,
createdBy: entry.created_by,
createdAt: entry.created_at,
updatedAt: entry.updated_at
});
} catch (error) {
logger.error('Fehler beim Erstellen des Knowledge-Eintrags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/knowledge/entries/:id
* Eintrag aktualisieren
*/
router.put('/entries/:id', (req, res) => {
try {
const entryId = req.params.id;
const { categoryId, title, url, notes } = req.body;
const db = getDb();
const existing = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?').get(entryId);
if (!existing) {
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
// Validierung
if (title) {
const titleError = validators.maxLength(title, 200, 'Titel');
if (titleError) {
return res.status(400).json({ error: titleError });
}
}
if (url) {
const urlError = validators.url(url, 'URL');
if (urlError) {
return res.status(400).json({ error: urlError });
}
}
if (categoryId) {
const category = db.prepare('SELECT * FROM knowledge_categories WHERE id = ?').get(categoryId);
if (!category) {
return res.status(404).json({ error: 'Kategorie nicht gefunden' });
}
}
db.prepare(`
UPDATE knowledge_entries SET
category_id = COALESCE(?, category_id),
title = COALESCE(?, title),
url = ?,
notes = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`).run(
categoryId || null,
title ? stripHtml(title) : null,
url !== undefined ? url : existing.url,
notes !== undefined ? notes : existing.notes,
entryId
);
const entry = db.prepare(`
SELECT ke.*, kc.name as category_name, kc.color as category_color
FROM knowledge_entries ke
LEFT JOIN knowledge_categories kc ON ke.category_id = kc.id
WHERE ke.id = ?
`).get(entryId);
logger.info(`Knowledge-Eintrag aktualisiert: ${entry.title}`);
res.json({
id: entry.id,
categoryId: entry.category_id,
categoryName: entry.category_name,
categoryColor: entry.category_color,
title: entry.title,
url: entry.url,
notes: entry.notes,
position: entry.position,
createdBy: entry.created_by,
createdAt: entry.created_at,
updatedAt: entry.updated_at
});
} catch (error) {
logger.error('Fehler beim Aktualisieren des Knowledge-Eintrags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/knowledge/entries/:id/position
* Eintrag-Position ändern (oder in andere Kategorie verschieben)
*/
router.put('/entries/:id/position', (req, res) => {
try {
const entryId = req.params.id;
const { newPosition, newCategoryId } = req.body;
const db = getDb();
const entry = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?').get(entryId);
if (!entry) {
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
const oldPosition = entry.position;
const oldCategoryId = entry.category_id;
const targetCategoryId = newCategoryId || oldCategoryId;
// Wenn Kategorie wechselt
if (targetCategoryId !== oldCategoryId) {
// Alte Kategorie: Positionen nach dem entfernten Eintrag reduzieren
db.prepare(`
UPDATE knowledge_entries SET position = position - 1
WHERE category_id = ? AND position > ?
`).run(oldCategoryId, oldPosition);
// Neue Kategorie: Platz für neuen Eintrag schaffen
db.prepare(`
UPDATE knowledge_entries SET position = position + 1
WHERE category_id = ? AND position >= ?
`).run(targetCategoryId, newPosition);
// Eintrag verschieben
db.prepare(`
UPDATE knowledge_entries SET category_id = ?, position = ?
WHERE id = ?
`).run(targetCategoryId, newPosition, entryId);
} else {
// Innerhalb der gleichen Kategorie
if (newPosition > oldPosition) {
db.prepare(`
UPDATE knowledge_entries SET position = position - 1
WHERE category_id = ? AND position > ? AND position <= ?
`).run(oldCategoryId, oldPosition, newPosition);
} else if (newPosition < oldPosition) {
db.prepare(`
UPDATE knowledge_entries SET position = position + 1
WHERE category_id = ? AND position >= ? AND position < ?
`).run(oldCategoryId, newPosition, oldPosition);
}
db.prepare('UPDATE knowledge_entries SET position = ? WHERE id = ?').run(newPosition, entryId);
}
res.json({ message: 'Position aktualisiert' });
} catch (error) {
logger.error('Fehler beim Verschieben des Knowledge-Eintrags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/knowledge/entries/:id
* Eintrag löschen
*/
router.delete('/entries/:id', (req, res) => {
try {
const entryId = req.params.id;
const db = getDb();
const entry = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?').get(entryId);
if (!entry) {
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
// Anhänge vom Dateisystem löschen
const attachments = db.prepare('SELECT * FROM knowledge_attachments WHERE entry_id = ?').all(entryId);
for (const attachment of attachments) {
const filePath = path.join(UPLOAD_DIR, attachment.filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
const categoryId = entry.category_id;
// Eintrag löschen
db.prepare('DELETE FROM knowledge_entries WHERE id = ?').run(entryId);
// Positionen neu nummerieren
const remaining = db.prepare(
'SELECT id FROM knowledge_entries WHERE category_id = ? ORDER BY position'
).all(categoryId);
remaining.forEach((e, idx) => {
db.prepare('UPDATE knowledge_entries SET position = ? WHERE id = ?').run(idx, e.id);
});
logger.info(`Knowledge-Eintrag gelöscht: ${entry.title}`);
res.json({ message: 'Eintrag gelöscht' });
} catch (error) {
logger.error('Fehler beim Löschen des Knowledge-Eintrags:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
// =====================
// ANHÄNGE
// =====================
/**
* GET /api/knowledge/attachments/:entryId
* Anhänge eines Eintrags abrufen
*/
router.get('/attachments/:entryId', (req, res) => {
try {
const entryId = req.params.entryId;
const db = getDb();
const entry = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?').get(entryId);
if (!entry) {
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
const attachments = db.prepare(`
SELECT ka.*, u.display_name as uploader_name
FROM knowledge_attachments ka
LEFT JOIN users u ON ka.uploaded_by = u.id
WHERE ka.entry_id = ?
ORDER BY ka.uploaded_at DESC
`).all(entryId);
res.json(attachments.map(a => ({
id: a.id,
entryId: a.entry_id,
filename: a.filename,
originalName: a.original_name,
mimeType: a.mime_type,
sizeBytes: a.size_bytes,
uploadedBy: a.uploaded_by,
uploaderName: a.uploader_name,
uploadedAt: a.uploaded_at
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Knowledge-Anhänge:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/knowledge/attachments/:entryId
* Anhang hochladen
*/
router.post('/attachments/:entryId', upload.single('files'), (req, res) => {
try {
const entryId = req.params.entryId;
const db = getDb();
const entry = db.prepare('SELECT * FROM knowledge_entries WHERE id = ?').get(entryId);
if (!entry) {
// Hochgeladene Datei löschen
if (req.file) {
fs.unlinkSync(req.file.path);
}
return res.status(404).json({ error: 'Eintrag nicht gefunden' });
}
if (!req.file) {
return res.status(400).json({ error: 'Keine Datei hochgeladen' });
}
const result = db.prepare(`
INSERT INTO knowledge_attachments (entry_id, filename, original_name, mime_type, size_bytes, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?)
`).run(
entryId,
req.file.filename,
req.file.originalname,
req.file.mimetype,
req.file.size,
req.user.id
);
// Eintrag updated_at aktualisieren
db.prepare('UPDATE knowledge_entries SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(entryId);
const attachment = db.prepare('SELECT * FROM knowledge_attachments WHERE id = ?')
.get(result.lastInsertRowid);
logger.info(`Knowledge-Anhang hochgeladen: ${req.file.originalname} für Eintrag ${entry.title}`);
res.status(201).json({
id: attachment.id,
entryId: attachment.entry_id,
filename: attachment.filename,
originalName: attachment.original_name,
mimeType: attachment.mime_type,
sizeBytes: attachment.size_bytes,
uploadedBy: attachment.uploaded_by,
uploadedAt: attachment.uploaded_at
});
} catch (error) {
logger.error('Fehler beim Hochladen des Knowledge-Anhangs:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/knowledge/attachments/download/:id
* Anhang herunterladen
*/
router.get('/attachments/download/:id', (req, res) => {
try {
const attachmentId = req.params.id;
const db = getDb();
const attachment = db.prepare('SELECT * FROM knowledge_attachments WHERE id = ?').get(attachmentId);
if (!attachment) {
return res.status(404).json({ error: 'Anhang nicht gefunden' });
}
const filePath = path.join(UPLOAD_DIR, attachment.filename);
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'Datei nicht gefunden' });
}
res.download(filePath, attachment.original_name);
} catch (error) {
logger.error('Fehler beim Herunterladen des Knowledge-Anhangs:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/knowledge/attachments/:id
* Anhang löschen
*/
router.delete('/attachments/:id', (req, res) => {
try {
const attachmentId = req.params.id;
const db = getDb();
const attachment = db.prepare('SELECT * FROM knowledge_attachments WHERE id = ?').get(attachmentId);
if (!attachment) {
return res.status(404).json({ error: 'Anhang nicht gefunden' });
}
// Datei vom Dateisystem löschen
const filePath = path.join(UPLOAD_DIR, attachment.filename);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
// Aus Datenbank löschen
db.prepare('DELETE FROM knowledge_attachments WHERE id = ?').run(attachmentId);
// Eintrag updated_at aktualisieren
db.prepare('UPDATE knowledge_entries SET updated_at = CURRENT_TIMESTAMP WHERE id = ?')
.run(attachment.entry_id);
logger.info(`Knowledge-Anhang gelöscht: ${attachment.original_name}`);
res.json({ message: 'Anhang gelöscht' });
} catch (error) {
logger.error('Fehler beim Löschen des Knowledge-Anhangs:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
// =====================
// SUCHE
// =====================
/**
* GET /api/knowledge/search
* Wissensmanagement durchsuchen
*/
router.get('/search', (req, res) => {
try {
const { q } = req.query;
if (!q || q.trim().length < 2) {
return res.json({ categories: [], entries: [] });
}
const searchTerm = `%${q.toLowerCase()}%`;
const db = getDb();
// Kategorien durchsuchen
const categories = db.prepare(`
SELECT kc.*,
(SELECT COUNT(*) FROM knowledge_entries WHERE category_id = kc.id) as entry_count
FROM knowledge_categories kc
WHERE LOWER(kc.name) LIKE ? OR LOWER(kc.description) LIKE ?
ORDER BY kc.position
`).all(searchTerm, searchTerm);
// Einträge durchsuchen
const entries = db.prepare(`
SELECT ke.*,
kc.name as category_name,
kc.color as category_color,
(SELECT COUNT(*) FROM knowledge_attachments WHERE entry_id = ke.id) as attachment_count
FROM knowledge_entries ke
LEFT JOIN knowledge_categories kc ON ke.category_id = kc.id
WHERE LOWER(ke.title) LIKE ? OR LOWER(ke.notes) LIKE ? OR LOWER(ke.url) LIKE ?
ORDER BY
CASE
WHEN LOWER(ke.title) LIKE ? THEN 1
ELSE 2
END,
ke.created_at DESC
LIMIT 50
`).all(searchTerm, searchTerm, searchTerm, searchTerm);
res.json({
categories: categories.map(c => ({
id: c.id,
name: c.name,
description: c.description,
color: c.color,
icon: c.icon,
entryCount: c.entry_count
})),
entries: entries.map(e => ({
id: e.id,
categoryId: e.category_id,
categoryName: e.category_name,
categoryColor: e.category_color,
title: e.title,
url: e.url,
notes: e.notes,
attachmentCount: e.attachment_count,
createdAt: e.created_at
}))
});
} catch (error) {
logger.error('Fehler bei der Knowledge-Suche:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
module.exports = router;