Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-28 21:36:45 +00:00
Commit ab1e5be9a9
146 geänderte Dateien mit 65525 neuen und 0 gelöschten Zeilen

238
backend/routes/files.js Normale Datei
Datei anzeigen

@ -0,0 +1,238 @@
/**
* 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;