compress: снять лимиты (размер/кол-во/rate-limit) для админа

This commit is contained in:
treamz 2026-07-24 17:02:43 +03:00
parent 87f8c55da0
commit 4259c3daf7
2 changed files with 38 additions and 14 deletions

View File

@ -120,7 +120,7 @@
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight dark:text-white text-gray-900">
Конвертер <span class="text-accent">изображений</span>
</h1>
<p class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">Сжатие, ресайз и конвертация · до 50 файлов · до 20 МБ</p>
<p id="limitsHint" class="description mt-2 text-sm dark:text-gray-500 text-gray-400 font-mono">Сжатие, ресайз и конвертация · до 50 файлов · до 20 МБ</p>
</div>
</div>
@ -297,7 +297,17 @@ document.getElementById('resize').addEventListener('change', function() {
});
const VALID_TYPES = ['image/jpeg','image/png','image/webp','image/avif','image/tiff','image/gif','image/bmp','image/svg+xml'];
const MAX_SIZE = 20 * 1024 * 1024;
let MAX_SIZE = 20 * 1024 * 1024;
let isAdmin = false;
// Админу — без лимитов по размеру/количеству
(function(){ fetch('/auth/me').then(function(r){return r.ok?r.json():null}).then(function(u){
if (u && u.role === 'admin') {
isAdmin = true;
MAX_SIZE = Infinity;
var d = document.getElementById('limitsHint');
if (d) d.textContent = 'Сжатие, ресайз и конвертация · без лимитов';
}
}).catch(function(){}); })();
function handleFiles(files) {
const arr = Array.from(files);

View File

@ -35,21 +35,35 @@ setInterval(() => cleanupDir(RESULTS_DIR, 30 * 60 * 1000), 5 * 60 * 1000);
const maxFileSize = (parseInt(process.env.MAX_FILE_SIZE_MB) || 20) * 1024 * 1024;
const maxFiles = parseInt(process.env.MAX_FILES) || 50;
const upload = multer({
dest: UPLOADS_DIR,
limits: { fileSize: maxFileSize },
fileFilter: (req, file, cb) => {
const imageFileFilter = (req, file, cb) => {
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/tiff', 'image/gif', 'image/bmp', 'image/svg+xml'];
if (validTypes.includes(file.mimetype) || file.originalname.match(/\.(jpe?g|png|webp|avif|tiff?|gif|bmp|svg|heic|heif)$/i)) cb(null, true);
else cb(new Error('Неподдерживаемый формат изображения'));
},
});
};
// Rate limiter
// Админ — без лимитов (размер, кол-во файлов, rate-limit)
function isAdmin(req) {
return !!(req.session && req.session.user && req.session.user.role === 'admin');
}
// User — лимит размера из .env; Admin — без лимита размера
const uploadUser = multer({ dest: UPLOADS_DIR, limits: { fileSize: maxFileSize }, fileFilter: imageFileFilter });
const uploadAdmin = multer({ dest: UPLOADS_DIR, fileFilter: imageFileFilter });
// Динамический выбор multer: админу — без лимита количества и размера
function uploadArray(req, res, next) {
return (isAdmin(req) ? uploadAdmin.array('images') : uploadUser.array('images', maxFiles))(req, res, next);
}
function uploadSingle(req, res, next) {
return (isAdmin(req) ? uploadAdmin : uploadUser).single('image')(req, res, next);
}
// Rate limiter (админ пропускается — безлимитная загрузка по одному файлу на запрос)
const limiter = rateLimit({
keyGenerator: (req) => (req.session && req.session.user && req.session.user.id) ? "user_" + req.session.user.id : req.ip,
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 60000,
max: parseInt(process.env.RATE_LIMIT_MAX) || 30,
skip: isAdmin,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Слишком много запросов. Попробуйте через минуту.' },
@ -104,7 +118,7 @@ router.get('/', (req, res) => {
});
// Compress endpoint
router.post('/', limiter, upload.array('images', maxFiles), async (req, res) => {
router.post('/', limiter, uploadArray, async (req, res) => {
const resize = parseInt(req.body.resize || '0', 10);
const format = (req.body.format || 'original').toLowerCase();
const quality = Math.min(100, Math.max(1, parseInt(req.body.quality || process.env.COMPRESS_QUALITY || '80', 10)));
@ -196,7 +210,7 @@ router.post('/', limiter, upload.array('images', maxFiles), async (req, res) =>
// Single file compress endpoint
router.post('/single', limiter, upload.single('image'), async (req, res) => {
router.post('/single', limiter, uploadSingle, async (req, res) => {
const resize = parseInt(req.body.resize || '0', 10);
const format = (req.body.format || 'original').toLowerCase();
const quality = Math.min(100, Math.max(1, parseInt(req.body.quality || process.env.COMPRESS_QUALITY || '80', 10)));