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