Files
TaskMate/backend/routes/links.js
Claude Project Manager ab1e5be9a9 Initial commit
2025-12-28 21:36:45 +00:00

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;