Initial commit
Dieser Commit ist enthalten in:
253
backend/routes/links.js
Normale Datei
253
backend/routes/links.js
Normale Datei
@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 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;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren