feat(uploads): лимиты загрузки в env, 50 МБ на файл и 200 МБ на запись

Лимиты были захардкожены в четырёх местах фронтенда и в константах multer,
из-за чего расходились с текстами ошибок на сервере.

- UPLOAD_FILE_LIMIT_MB (50) и UPLOAD_TOTAL_LIMIT_MB (200) читаются из env;
  оба multer-конфига (upload, adminUpload) берут fileSize из них, тексты
  ошибок собираются из тех же констант вместо литералов
- UPLOAD_REQUEST_TIMEOUT_MS снимает дефолт Node в 5 минут: считается как
  UPLOAD_TOTAL_LIMIT_MB * 7500, иначе 200 МБ по мобильной сети не успевают
- GET /api/public-settings отдаёт upload_file_limit_mb / upload_total_limit_mb,
  фронтенд читает их вместо собственных констант

Проверено на живом стеке: 20 МБ и 180 МБ суммарно принимаются, 55 МБ и
225 МБ отклоняются с верными сообщениями, скачивание 45 МБ из S3 совпадает
по sha256 с оригиналом, api.smoketest.js — 57 PASS / 0 FAIL.
This commit is contained in:
dev
2026-10-03 10:38:36 +03:00
parent 449b86955e
commit 104bdc4f49
9 changed files with 78 additions and 24 deletions
+12 -1
View File
@@ -3,6 +3,11 @@ let mPage = 1;
const M_PAGE_SIZE = 25;
let mSearchTimer = null;
let modulePhotoDeleteFlag = false;
let modulePhotoLimitMb = 50;
function modulePhotoHint() {
return `jpg, png, gif, webp, avif, heic и др., до ${modulePhotoLimitMb} МБ · или вставьте из буфера (Ctrl+V)`;
}
function mPlural(n, one, few, many) {
n = Math.abs(n) % 100; const n1 = n % 10;
@@ -68,7 +73,7 @@ function resetModulePhoto(m) {
preview.removeAttribute('src');
preview.style.display = 'none';
delBtn.style.display = 'none';
note.textContent = 'jpg, png, gif, webp, avif, heic и др., до 10 МБ · или вставьте из буфера (Ctrl+V)';
note.textContent = modulePhotoHint();
}
}
@@ -313,6 +318,12 @@ document.getElementById('moduleList').addEventListener('click', e => {
(async () => {
if (await requireAdminPage()) {
try {
const res = await fetch(`${API}/api/public-settings`);
const f = await res.json();
const n = parseInt(f.upload_file_limit_mb, 10);
if (Number.isFinite(n) && n >= 1) modulePhotoLimitMb = n;
} catch (e) { /* ignore */ }
buildSidebar(document.body.dataset.page);
loadModules();
}