170 lines
5.5 KiB
JavaScript
170 lines
5.5 KiB
JavaScript
const multer = require('multer');
|
|
const path = require('path');
|
|
const { uploadFile, isEnabled: isMinioEnabled } = require('../utils/minio');
|
|
const { log } = require('./logger');
|
|
const fs = require('fs');
|
|
|
|
// Временное хранилище для файлов
|
|
const tempStorage = multer.memoryStorage();
|
|
|
|
// Конфигурация multer
|
|
const multerConfig = {
|
|
storage: tempStorage,
|
|
limits: {
|
|
fileSize: 10 * 1024 * 1024, // 10MB
|
|
files: 10
|
|
},
|
|
fileFilter: (req, file, cb) => {
|
|
// Запрещенные расширения (исполняемые файлы)
|
|
const forbiddenExts = [
|
|
'.exe', '.bat', '.cmd', '.sh', '.ps1', '.js', '.jar',
|
|
'.app', '.dmg', '.deb', '.rpm', '.msi', '.scr',
|
|
'.vbs', '.com', '.pif', '.cpl'
|
|
];
|
|
const ext = path.extname(file.originalname).toLowerCase();
|
|
|
|
// Проверить на запрещенные расширения
|
|
if (forbiddenExts.includes(ext)) {
|
|
return cb(new Error('Запрещенный тип файла'));
|
|
}
|
|
|
|
// Разрешенные типы изображений и видео
|
|
const allowedMimes = [
|
|
'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp',
|
|
'video/mp4', 'video/quicktime', 'video/x-msvideo'
|
|
];
|
|
|
|
if (!allowedMimes.includes(file.mimetype)) {
|
|
return cb(new Error('Только изображения и видео разрешены'));
|
|
}
|
|
|
|
cb(null, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Middleware для загрузки файлов
|
|
* Автоматически загружает в MinIO если включен, иначе локально
|
|
*/
|
|
function createUploadMiddleware(fieldName, maxCount = 5, folder = 'posts') {
|
|
const upload = multer(multerConfig);
|
|
const multerMiddleware = maxCount === 1
|
|
? upload.single(fieldName)
|
|
: upload.array(fieldName, maxCount);
|
|
|
|
return async (req, res, next) => {
|
|
multerMiddleware(req, res, async (err) => {
|
|
if (err) {
|
|
log('error', 'Ошибка multer', { error: err.message });
|
|
return res.status(400).json({ error: err.message });
|
|
}
|
|
|
|
try {
|
|
// Проверить наличие файлов
|
|
const files = req.files || (req.file ? [req.file] : []);
|
|
|
|
if (!files.length) {
|
|
return next();
|
|
}
|
|
|
|
// Если MinIO включен, загрузить туда
|
|
if (isMinioEnabled()) {
|
|
const uploadedUrls = [];
|
|
|
|
for (const file of files) {
|
|
try {
|
|
const fileUrl = await uploadFile(
|
|
file.buffer,
|
|
file.originalname,
|
|
file.mimetype,
|
|
folder
|
|
);
|
|
uploadedUrls.push(fileUrl);
|
|
} catch (uploadError) {
|
|
log('error', 'Ошибка загрузки в MinIO', {
|
|
error: uploadError.message,
|
|
filename: file.originalname
|
|
});
|
|
throw uploadError;
|
|
}
|
|
}
|
|
|
|
// Сохранить URLs в req для дальнейшей обработки
|
|
req.uploadedFiles = uploadedUrls;
|
|
req.uploadMethod = 'minio';
|
|
|
|
log('info', 'Файлы загружены в MinIO', {
|
|
count: uploadedUrls.length,
|
|
folder
|
|
});
|
|
|
|
} else {
|
|
// Локальное хранилище (fallback)
|
|
const uploadDir = path.join(__dirname, '../uploads', folder);
|
|
|
|
// Создать директорию если не существует
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
|
|
const uploadedPaths = [];
|
|
|
|
for (const file of files) {
|
|
const timestamp = Date.now();
|
|
const random = Math.round(Math.random() * 1E9);
|
|
const ext = path.extname(file.originalname);
|
|
const filename = `${timestamp}-${random}${ext}`;
|
|
const filepath = path.join(uploadDir, filename);
|
|
|
|
// Сохранить файл
|
|
fs.writeFileSync(filepath, file.buffer);
|
|
|
|
// Относительный путь для URL
|
|
const relativePath = `/uploads/${folder}/${filename}`;
|
|
uploadedPaths.push(relativePath);
|
|
}
|
|
|
|
req.uploadedFiles = uploadedPaths;
|
|
req.uploadMethod = 'local';
|
|
|
|
log('info', 'Файлы загружены локально', {
|
|
count: uploadedPaths.length,
|
|
folder
|
|
});
|
|
}
|
|
|
|
next();
|
|
} catch (error) {
|
|
log('error', 'Ошибка обработки загруженных файлов', { error: error.message });
|
|
return res.status(500).json({ error: 'Ошибка загрузки файлов' });
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Middleware для удаления файлов из MinIO при ошибке
|
|
*/
|
|
function cleanupOnError() {
|
|
return (err, req, res, next) => {
|
|
if (req.uploadedFiles && req.uploadMethod === 'minio') {
|
|
const { deleteFiles } = require('../utils/minio');
|
|
deleteFiles(req.uploadedFiles).catch(cleanupErr => {
|
|
log('error', 'Ошибка очистки файлов MinIO', { error: cleanupErr.message });
|
|
});
|
|
}
|
|
next(err);
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
createUploadMiddleware,
|
|
cleanupOnError,
|
|
|
|
// Готовые middleware для разных случаев
|
|
uploadPostImages: createUploadMiddleware('images', 5, 'posts'),
|
|
uploadAvatar: createUploadMiddleware('avatar', 1, 'avatars'),
|
|
uploadChannelMedia: createUploadMiddleware('images', 10, 'channel')
|
|
};
|
|
|