/** * TASKMATE - Link Routes * ====================== * CRUD für Links/URLs */ const express = require('express'); const router = express.Router(); const { getDb } = require('../database'); const logger = require('../utils/logger'); const { validators, stripHtml } = require('../middleware/validation'); /** * Hilfsfunktion: Link-Icon basierend auf URL */ function getLinkIcon(url) { try { const hostname = new URL(url).hostname.toLowerCase(); if (hostname.includes('youtube') || hostname.includes('youtu.be')) return 'youtube'; if (hostname.includes('github')) return 'github'; if (hostname.includes('gitlab')) return 'gitlab'; if (hostname.includes('figma')) return 'figma'; if (hostname.includes('drive.google')) return 'google-drive'; if (hostname.includes('docs.google')) return 'google-docs'; if (hostname.includes('notion')) return 'notion'; if (hostname.includes('trello')) return 'trello'; if (hostname.includes('slack')) return 'slack'; if (hostname.includes('jira') || hostname.includes('atlassian')) return 'jira'; return 'link'; } catch { return 'link'; } } /** * GET /api/links/:taskId * Alle Links einer Aufgabe */ router.get('/:taskId', (req, res) => { try { const db = getDb(); const links = db.prepare(` SELECT l.*, u.display_name as creator_name FROM links l LEFT JOIN users u ON l.created_by = u.id WHERE l.task_id = ? ORDER BY l.created_at DESC `).all(req.params.taskId); res.json(links.map(l => ({ id: l.id, taskId: l.task_id, title: l.title, url: l.url, icon: getLinkIcon(l.url), createdBy: l.created_by, creatorName: l.creator_name, createdAt: l.created_at }))); } catch (error) { logger.error('Fehler beim Abrufen der Links:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * POST /api/links * Neuen Link erstellen */ router.post('/', (req, res) => { try { const { taskId, title, url } = req.body; // Validierung const urlError = validators.required(url, 'URL') || validators.url(url, 'URL'); if (urlError) { return res.status(400).json({ error: urlError }); } if (title) { const titleError = validators.maxLength(title, 100, 'Titel'); if (titleError) { return res.status(400).json({ error: titleError }); } } const db = getDb(); // Task prüfen const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId); if (!task) { return res.status(404).json({ error: 'Aufgabe nicht gefunden' }); } const sanitizedTitle = title ? stripHtml(title) : null; const result = db.prepare(` INSERT INTO links (task_id, title, url, created_by) VALUES (?, ?, ?, ?) `).run(taskId, sanitizedTitle, url, req.user.id); // Task updated_at aktualisieren db.prepare('UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(taskId); const link = db.prepare(` SELECT l.*, u.display_name as creator_name FROM links l LEFT JOIN users u ON l.created_by = u.id WHERE l.id = ? `).get(result.lastInsertRowid); logger.info(`Link erstellt: ${url} für Task ${taskId}`); // WebSocket const io = req.app.get('io'); io.to(`project:${task.project_id}`).emit('link:created', { taskId, link: { id: link.id, taskId: link.task_id, title: link.title, url: link.url, icon: getLinkIcon(link.url), createdBy: link.created_by, creatorName: link.creator_name, createdAt: link.created_at } }); res.status(201).json({ id: link.id, taskId: link.task_id, title: link.title, url: link.url, icon: getLinkIcon(link.url), createdBy: link.created_by, creatorName: link.creator_name, createdAt: link.created_at }); } catch (error) { logger.error('Fehler beim Erstellen des Links:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * PUT /api/links/:id * Link aktualisieren */ router.put('/:id', (req, res) => { try { const linkId = req.params.id; const { title, url } = req.body; const db = getDb(); const existing = db.prepare('SELECT * FROM links WHERE id = ?').get(linkId); if (!existing) { return res.status(404).json({ error: 'Link nicht gefunden' }); } // Validierung if (url) { const urlError = validators.url(url, 'URL'); if (urlError) return res.status(400).json({ error: urlError }); } if (title) { const titleError = validators.maxLength(title, 100, 'Titel'); if (titleError) return res.status(400).json({ error: titleError }); } db.prepare(` UPDATE links SET title = ?, url = COALESCE(?, url) WHERE id = ? `).run(title !== undefined ? stripHtml(title) : existing.title, url || null, linkId); const link = db.prepare(` SELECT l.*, u.display_name as creator_name FROM links l LEFT JOIN users u ON l.created_by = u.id WHERE l.id = ? `).get(linkId); const task = db.prepare('SELECT project_id FROM tasks WHERE id = ?').get(link.task_id); // WebSocket const io = req.app.get('io'); io.to(`project:${task.project_id}`).emit('link:updated', { taskId: link.task_id, link: { id: link.id, title: link.title, url: link.url, icon: getLinkIcon(link.url) } }); res.json({ id: link.id, taskId: link.task_id, title: link.title, url: link.url, icon: getLinkIcon(link.url), createdBy: link.created_by, creatorName: link.creator_name, createdAt: link.created_at }); } catch (error) { logger.error('Fehler beim Aktualisieren des Links:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * DELETE /api/links/:id * Link löschen */ router.delete('/:id', (req, res) => { try { const linkId = req.params.id; const db = getDb(); const link = db.prepare('SELECT * FROM links WHERE id = ?').get(linkId); if (!link) { return res.status(404).json({ error: 'Link nicht gefunden' }); } const task = db.prepare('SELECT project_id FROM tasks WHERE id = ?').get(link.task_id); db.prepare('DELETE FROM links WHERE id = ?').run(linkId); // Task updated_at aktualisieren db.prepare('UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(link.task_id); logger.info(`Link gelöscht: ${link.url}`); // WebSocket const io = req.app.get('io'); io.to(`project:${task.project_id}`).emit('link:deleted', { taskId: link.task_id, linkId }); res.json({ message: 'Link gelöscht' }); } catch (error) { logger.error('Fehler beim Löschen des Links:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); module.exports = router;