254 Zeilen
6.9 KiB
JavaScript
254 Zeilen
6.9 KiB
JavaScript
/**
|
|
* 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;
|