/** * TASKMATE - File Routes * ====================== * Upload, Download, Löschen von Dateien */ const express = require('express'); const router = express.Router(); const path = require('path'); const fs = require('fs'); const { getDb } = require('../database'); const logger = require('../utils/logger'); const { upload, deleteFile, formatFileSize, isImage, getFileIcon, UPLOAD_DIR } = require('../middleware/upload'); const csrfProtection = require('../middleware/csrf'); /** * GET /api/files/:taskId * Alle Dateien einer Aufgabe */ router.get('/:taskId', (req, res) => { try { const db = getDb(); const attachments = db.prepare(` SELECT a.*, u.display_name as uploader_name FROM attachments a LEFT JOIN users u ON a.uploaded_by = u.id WHERE a.task_id = ? ORDER BY a.uploaded_at DESC `).all(req.params.taskId); res.json(attachments.map(a => ({ id: a.id, taskId: a.task_id, filename: a.filename, originalName: a.original_name, mimeType: a.mime_type, sizeBytes: a.size_bytes, sizeFormatted: formatFileSize(a.size_bytes), isImage: isImage(a.mime_type), icon: getFileIcon(a.mime_type), uploadedBy: a.uploaded_by, uploaderName: a.uploader_name, uploadedAt: a.uploaded_at }))); } catch (error) { logger.error('Fehler beim Abrufen der Dateien:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * POST /api/files/:taskId * Datei(en) hochladen */ router.post('/:taskId', csrfProtection, upload.array('files', 10), (req, res) => { try { const taskId = req.params.taskId; const db = getDb(); // Task prüfen const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId); if (!task) { // Hochgeladene Dateien löschen req.files?.forEach(f => fs.unlinkSync(f.path)); return res.status(404).json({ error: 'Aufgabe nicht gefunden' }); } if (!req.files || req.files.length === 0) { return res.status(400).json({ error: 'Keine Dateien hochgeladen' }); } const insertAttachment = db.prepare(` INSERT INTO attachments (task_id, filename, original_name, mime_type, size_bytes, uploaded_by) VALUES (?, ?, ?, ?, ?, ?) `); const attachments = []; req.files.forEach(file => { const result = insertAttachment.run( taskId, `task_${taskId}/${file.filename}`, file.originalname, file.mimetype, file.size, req.user.id ); attachments.push({ id: result.lastInsertRowid, taskId: parseInt(taskId), filename: `task_${taskId}/${file.filename}`, originalName: file.originalname, mimeType: file.mimetype, sizeBytes: file.size, sizeFormatted: formatFileSize(file.size), isImage: isImage(file.mimetype), icon: getFileIcon(file.mimetype), uploadedBy: req.user.id, uploaderName: req.user.displayName, uploadedAt: new Date().toISOString() }); }); // Task updated_at aktualisieren db.prepare('UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(taskId); // Historie db.prepare(` INSERT INTO history (task_id, user_id, action, new_value) VALUES (?, ?, 'attachment_added', ?) `).run(taskId, req.user.id, attachments.map(a => a.originalName).join(', ')); logger.info(`${attachments.length} Datei(en) hochgeladen für Task ${taskId}`); // WebSocket const io = req.app.get('io'); io.to(`project:${task.project_id}`).emit('files:uploaded', { taskId, attachments }); res.status(201).json({ attachments }); } catch (error) { logger.error('Fehler beim Hochladen:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * GET /api/files/download/:id * Datei herunterladen */ router.get('/download/:id', (req, res) => { try { const db = getDb(); const attachment = db.prepare('SELECT * FROM attachments WHERE id = ?').get(req.params.id); if (!attachment) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } const filePath = path.join(UPLOAD_DIR, attachment.filename); if (!fs.existsSync(filePath)) { logger.error(`Datei existiert nicht: ${filePath}`); return res.status(404).json({ error: 'Datei nicht gefunden' }); } res.download(filePath, attachment.original_name); } catch (error) { logger.error('Fehler beim Download:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * GET /api/files/preview/:id * Bild-Vorschau */ router.get('/preview/:id', (req, res) => { try { const db = getDb(); const attachment = db.prepare('SELECT * FROM attachments WHERE id = ?').get(req.params.id); if (!attachment) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } if (!isImage(attachment.mime_type)) { return res.status(400).json({ error: 'Keine Bilddatei' }); } const filePath = path.join(UPLOAD_DIR, attachment.filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } res.setHeader('Content-Type', attachment.mime_type); res.sendFile(filePath); } catch (error) { logger.error('Fehler bei Vorschau:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * DELETE /api/files/:id * Datei löschen */ router.delete('/:id', csrfProtection, (req, res) => { try { const attachmentId = req.params.id; const db = getDb(); const attachment = db.prepare('SELECT * FROM attachments WHERE id = ?').get(attachmentId); if (!attachment) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(attachment.task_id); // 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 attachments WHERE id = ?').run(attachmentId); // Task updated_at aktualisieren db.prepare('UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(attachment.task_id); // Historie db.prepare(` INSERT INTO history (task_id, user_id, action, old_value) VALUES (?, ?, 'attachment_removed', ?) `).run(attachment.task_id, req.user.id, attachment.original_name); logger.info(`Datei gelöscht: ${attachment.original_name}`); // WebSocket const io = req.app.get('io'); io.to(`project:${task.project_id}`).emit('file:deleted', { taskId: attachment.task_id, attachmentId }); res.json({ message: 'Datei gelöscht' }); } catch (error) { logger.error('Fehler beim Löschen der Datei:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); module.exports = router;