Initial commit
Dieser Commit ist enthalten in:
198
backend/middleware/upload.js
Normale Datei
198
backend/middleware/upload.js
Normale Datei
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* TASKMATE - File Upload
|
||||
* ======================
|
||||
* Multer-Konfiguration für Datei-Uploads
|
||||
*/
|
||||
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Upload-Verzeichnis
|
||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, '..', 'uploads');
|
||||
|
||||
// Verzeichnis erstellen falls nicht vorhanden
|
||||
if (!fs.existsSync(UPLOAD_DIR)) {
|
||||
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Standard-Werte (Fallback)
|
||||
let MAX_FILE_SIZE = (parseInt(process.env.MAX_FILE_SIZE_MB) || 15) * 1024 * 1024;
|
||||
let ALLOWED_MIME_TYPES = [
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
|
||||
'application/pdf',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'text/plain', 'text/csv', 'text/markdown',
|
||||
'application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed',
|
||||
'application/json'
|
||||
];
|
||||
|
||||
/**
|
||||
* Lädt Upload-Einstellungen aus der Datenbank
|
||||
*/
|
||||
function loadUploadSettings() {
|
||||
try {
|
||||
// Lazy-Load um zirkuläre Abhängigkeiten zu vermeiden
|
||||
const { getUploadSettings } = require('../routes/admin');
|
||||
const settings = getUploadSettings();
|
||||
|
||||
if (settings) {
|
||||
MAX_FILE_SIZE = (settings.maxFileSizeMB || 15) * 1024 * 1024;
|
||||
|
||||
// Erlaubte MIME-Types aus den aktiven Kategorien zusammenstellen
|
||||
const types = [];
|
||||
if (settings.allowedTypes) {
|
||||
Object.values(settings.allowedTypes).forEach(category => {
|
||||
if (category.enabled && Array.isArray(category.types)) {
|
||||
types.push(...category.types);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (types.length > 0) {
|
||||
ALLOWED_MIME_TYPES = types;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Bei Fehler Standard-Werte beibehalten
|
||||
logger.warn('Upload-Einstellungen konnten nicht geladen werden, verwende Standards');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktuelle Einstellungen abrufen (für dynamische Prüfung)
|
||||
*/
|
||||
function getCurrentSettings() {
|
||||
loadUploadSettings();
|
||||
return { maxFileSize: MAX_FILE_SIZE, allowedMimeTypes: ALLOWED_MIME_TYPES };
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage-Konfiguration
|
||||
*/
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
// Task-ID aus URL oder Body
|
||||
const taskId = req.params.taskId || req.body.taskId;
|
||||
|
||||
if (taskId) {
|
||||
// Unterordner pro Task
|
||||
const taskDir = path.join(UPLOAD_DIR, `task_${taskId}`);
|
||||
if (!fs.existsSync(taskDir)) {
|
||||
fs.mkdirSync(taskDir, { recursive: true });
|
||||
}
|
||||
cb(null, taskDir);
|
||||
} else {
|
||||
cb(null, UPLOAD_DIR);
|
||||
}
|
||||
},
|
||||
|
||||
filename: (req, file, cb) => {
|
||||
// Eindeutiger Dateiname mit Original-Extension
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
const uniqueName = `${uuidv4()}${ext}`;
|
||||
cb(null, uniqueName);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Datei-Filter
|
||||
*/
|
||||
const fileFilter = (req, file, cb) => {
|
||||
// Aktuelle Einstellungen laden
|
||||
const settings = getCurrentSettings();
|
||||
|
||||
// MIME-Type prüfen
|
||||
if (settings.allowedMimeTypes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
logger.warn(`Abgelehnter Upload: ${file.originalname} (${file.mimetype})`);
|
||||
cb(new Error(`Dateityp nicht erlaubt: ${file.mimetype}`), false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamische Multer-Instanz erstellen
|
||||
*/
|
||||
function createUpload() {
|
||||
const settings = getCurrentSettings();
|
||||
return multer({
|
||||
storage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: settings.maxFileSize,
|
||||
files: 10 // Maximal 10 Dateien pro Request
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Standard-Instanz für Rückwärtskompatibilität
|
||||
const upload = createUpload();
|
||||
|
||||
/**
|
||||
* Datei löschen
|
||||
*/
|
||||
function deleteFile(filePath) {
|
||||
try {
|
||||
const fullPath = path.join(UPLOAD_DIR, filePath);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
fs.unlinkSync(fullPath);
|
||||
logger.info(`Datei gelöscht: ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
logger.error(`Fehler beim Löschen: ${filePath}`, { error: error.message });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dateigröße formatieren
|
||||
*/
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist Bild?
|
||||
*/
|
||||
function isImage(mimeType) {
|
||||
return mimeType && mimeType.startsWith('image/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Datei-Icon basierend auf MIME-Type
|
||||
*/
|
||||
function getFileIcon(mimeType) {
|
||||
if (mimeType.startsWith('image/')) return 'image';
|
||||
if (mimeType === 'application/pdf') return 'pdf';
|
||||
if (mimeType.includes('word')) return 'word';
|
||||
if (mimeType.includes('excel') || mimeType.includes('spreadsheet')) return 'excel';
|
||||
if (mimeType.includes('powerpoint') || mimeType.includes('presentation')) return 'powerpoint';
|
||||
if (mimeType.includes('zip') || mimeType.includes('rar') || mimeType.includes('7z')) return 'archive';
|
||||
if (mimeType.startsWith('text/')) return 'text';
|
||||
return 'file';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
upload,
|
||||
createUpload,
|
||||
deleteFile,
|
||||
formatFileSize,
|
||||
isImage,
|
||||
getFileIcon,
|
||||
getCurrentSettings,
|
||||
UPLOAD_DIR,
|
||||
MAX_FILE_SIZE,
|
||||
ALLOWED_MIME_TYPES
|
||||
};
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren