From cc4503ad28e6b56f8593edf3b54cefe8c4939fc1 Mon Sep 17 00:00:00 2001 From: Paul Nothaft <53005142+the-luap@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:16:28 +0100 Subject: [PATCH] Revert "feat: configurable upload batch size for reverse proxy compatibility" --- .../core/072_add_max_upload_batch_size.js | 20 ---- frontend/src/components/admin/PhotoUpload.tsx | 3 +- .../settings/hooks/useSettingsState.ts | 3 - .../src/features/settings/tabs/GeneralTab.tsx | 22 ---- frontend/src/i18n/locales/de.json | 2 - frontend/src/i18n/locales/en.json | 2 - tests/e2e/upload-batch-size-setting.spec.ts | 104 ------------------ 7 files changed, 1 insertion(+), 155 deletions(-) delete mode 100644 backend/migrations/core/072_add_max_upload_batch_size.js delete mode 100644 tests/e2e/upload-batch-size-setting.spec.ts diff --git a/backend/migrations/core/072_add_max_upload_batch_size.js b/backend/migrations/core/072_add_max_upload_batch_size.js deleted file mode 100644 index 4f1cb45c..00000000 --- a/backend/migrations/core/072_add_max_upload_batch_size.js +++ /dev/null @@ -1,20 +0,0 @@ -exports.up = async function(knex) { - const exists = await knex('app_settings') - .where({ setting_key: 'general_max_upload_batch_size_mb' }) - .first(); - - if (!exists) { - await knex('app_settings').insert({ - setting_key: 'general_max_upload_batch_size_mb', - setting_value: JSON.stringify(95), - setting_type: 'general', - updated_at: new Date() - }); - } -}; - -exports.down = async function(knex) { - await knex('app_settings') - .where({ setting_key: 'general_max_upload_batch_size_mb' }) - .del(); -}; diff --git a/frontend/src/components/admin/PhotoUpload.tsx b/frontend/src/components/admin/PhotoUpload.tsx index ae53557d..a0dd5d61 100644 --- a/frontend/src/components/admin/PhotoUpload.tsx +++ b/frontend/src/components/admin/PhotoUpload.tsx @@ -98,8 +98,7 @@ export const PhotoUpload: React.FC = ({ eventId, onUploadCompl // For large uploads, chunk the files by both count AND size to prevent memory/network issues const MAX_FILES_PER_CHUNK = Math.max(1, Math.min(50, maxFilesPerUpload)); // Max 50 files per chunk - const maxBatchSizeMb = Number(settings?.general_max_upload_batch_size_mb) || 95; - const MAX_BYTES_PER_CHUNK = maxBatchSizeMb * 1024 * 1024; + const MAX_BYTES_PER_CHUNK = 500 * 1024 * 1024; // Max 500MB per chunk (nginx limit is 1GB) const chunks: File[][] = []; let currentChunk: File[] = []; diff --git a/frontend/src/features/settings/hooks/useSettingsState.ts b/frontend/src/features/settings/hooks/useSettingsState.ts index 1ad7c671..fabdf384 100644 --- a/frontend/src/features/settings/hooks/useSettingsState.ts +++ b/frontend/src/features/settings/hooks/useSettingsState.ts @@ -16,7 +16,6 @@ export interface GeneralSettings { max_file_size_mb: number; max_files_per_upload: number; allowed_file_types: string; - max_upload_batch_size_mb: number; enable_analytics: boolean; enable_registration: boolean; maintenance_mode: boolean; @@ -88,7 +87,6 @@ export function useSettingsState() { max_file_size_mb: 50, max_files_per_upload: 500, allowed_file_types: 'jpg,jpeg,png,gif,webp', - max_upload_batch_size_mb: 95, enable_analytics: true, enable_registration: false, maintenance_mode: false, @@ -171,7 +169,6 @@ export function useSettingsState() { Math.max(1, toNumber(settings.general_max_files_per_upload, 500)) ), allowed_file_types: settings.general_allowed_file_types || 'jpg,jpeg,png,gif,webp', - max_upload_batch_size_mb: toNumber(settings.general_max_upload_batch_size_mb, 95), enable_analytics: toBoolean(settings.general_enable_analytics, true), enable_registration: toBoolean(settings.general_enable_registration, false), maintenance_mode: toBoolean(settings.general_maintenance_mode, false), diff --git a/frontend/src/features/settings/tabs/GeneralTab.tsx b/frontend/src/features/settings/tabs/GeneralTab.tsx index 6c4ae1fd..964bbdbe 100644 --- a/frontend/src/features/settings/tabs/GeneralTab.tsx +++ b/frontend/src/features/settings/tabs/GeneralTab.tsx @@ -161,28 +161,6 @@ export const GeneralTab: React.FC = ({ {t('settings.general.maxFilesPerUploadHelp', { max: MAX_FILES_PER_UPLOAD_LIMIT })}

-
- - { - const parsed = parseInt(e.target.value, 10); - setGeneralSettings(prev => ({ - ...prev, - max_upload_batch_size_mb: Number.isFinite(parsed) - ? Math.max(1, parsed) - : prev.max_upload_batch_size_mb - })); - }} - min="1" - /> -

- {t('settings.general.maxUploadBatchSizeHelp')} -

-
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 30066b41..33f8ac02 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1012,8 +1012,6 @@ "maxFileSizeHelp": "Maximale Größe pro hochgeladenem Foto", "maxFilesPerUpload": "Max. Dateien pro Upload", "maxFilesPerUploadHelp": "Maximale Anzahl an Fotos pro Upload-Vorgang (1-{{max}}).", - "maxUploadBatchSize": "Max. Upload-Paketgröße (MB)", - "maxUploadBatchSizeHelp": "Maximale Größe pro Upload-Anfrage. Reduzieren Sie diesen Wert bei Nutzung eines Reverse-Proxys mit Größenbeschränkung (z.B. Cloudflare: 100MB).", "allowedFileTypes": "Erlaubte Dateitypen", "allowedFileTypesHelp": "Kommagetrennte Liste von Dateierweiterungen", "featureToggles": "Funktionsschalter", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index eb1b9819..89e0022f 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -637,8 +637,6 @@ "maxFileSizeHelp": "Maximum size per uploaded photo", "maxFilesPerUpload": "Max Files per Upload", "maxFilesPerUploadHelp": "Maximum number of photos allowed in a single upload batch (1-{{max}}).", - "maxUploadBatchSize": "Max Upload Batch Size (MB)", - "maxUploadBatchSizeHelp": "Maximum size per upload request. Lower this if behind a reverse proxy with request size limits (e.g. Cloudflare: 100MB).", "allowedFileTypes": "Allowed File Types", "allowedFileTypesHelp": "Comma-separated list of file extensions", "featureToggles": "Feature Toggles", diff --git a/tests/e2e/upload-batch-size-setting.spec.ts b/tests/e2e/upload-batch-size-setting.spec.ts deleted file mode 100644 index a06bc8f4..00000000 --- a/tests/e2e/upload-batch-size-setting.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { test, expect } from '@playwright/test'; - -const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@example.com'; -const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'Admin!234'; - -async function getAdminToken(page: import('@playwright/test').Page): Promise { - const loginRes = await page.request.post('/api/auth/admin/login', { - data: { username: ADMIN_EMAIL, password: ADMIN_PASSWORD }, - }); - const body = await loginRes.json(); - return body.token; -} - -async function adminLogin(page: import('@playwright/test').Page) { - await page.goto('/admin/login'); - await page.getByLabel(/Email|E-Mail/i).fill(ADMIN_EMAIL); - await page.getByLabel(/Password|Passwort/i).fill(ADMIN_PASSWORD); - await page.getByRole('button', { name: /Sign In|Log in|Anmelden/i }).click(); - await expect(page.getByRole('heading', { name: /Dashboard|Übersicht/i })).toBeVisible({ timeout: 20000 }); -} - -test.describe('Upload batch size setting', () => { - test('setting exists in DB via API with default value 95', async ({ page }) => { - const token = await getAdminToken(page); - - const res = await page.request.get('/api/admin/settings', { - headers: { Authorization: `Bearer ${token}` }, - }); - expect(res.ok()).toBeTruthy(); - const settings = await res.json(); - expect(settings.general_max_upload_batch_size_mb).toBe(95); - }); - - test('setting appears in General settings UI and can be changed', async ({ page }, testInfo) => { - if (testInfo.project.name === 'mobile-chrome') { - test.skip('Settings UI validated on desktop viewport'); - } - - await adminLogin(page); - await page.goto('/admin/settings'); - - // Find the batch size input by its nearby label text - const batchSizeLabel = page.locator('label', { hasText: /Max Upload Batch Size|Max\. Upload-Paketgröße/i }); - await expect(batchSizeLabel).toBeVisible({ timeout: 10000 }); - - // The input is a sibling within the same container - const batchSizeInput = batchSizeLabel.locator('..').locator('input[type="number"]'); - await expect(batchSizeInput).toBeVisible(); - await expect(batchSizeInput).toHaveValue('95'); - - // Change value to 50 - await batchSizeInput.fill('50'); - - // Save general settings - const saveButton = page.getByRole('button', { name: /Save General Settings|Allgemeine Einstellungen speichern/i }); - await saveButton.click(); - - // Wait for success toast - await expect(page.locator('.Toastify__toast').filter({ hasText: /(Settings saved|Einstellungen gespeichert)/i })).toBeVisible({ timeout: 10000 }); - - // Reload and verify persisted - await page.reload(); - const batchSizeLabelAfter = page.locator('label', { hasText: /Max Upload Batch Size|Max\. Upload-Paketgröße/i }); - await expect(batchSizeLabelAfter).toBeVisible({ timeout: 10000 }); - const batchSizeInputAfter = batchSizeLabelAfter.locator('..').locator('input[type="number"]'); - await expect(batchSizeInputAfter).toHaveValue('50'); - - // Revert to default - await batchSizeInputAfter.fill('95'); - await page.getByRole('button', { name: /Save General Settings|Allgemeine Einstellungen speichern/i }).click(); - await expect(page.locator('.Toastify__toast').filter({ hasText: /(Settings saved|Einstellungen gespeichert)/i })).toBeVisible({ timeout: 10000 }); - }); - - test('setting is used for upload chunking via API', async ({ page }) => { - const token = await getAdminToken(page); - - // Set batch size to a small value - const updateRes = await page.request.put('/api/admin/settings/general', { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - data: { general_max_upload_batch_size_mb: 10 }, - }); - expect(updateRes.ok()).toBeTruthy(); - - // Verify the setting was saved - const getRes = await page.request.get('/api/admin/settings', { - headers: { Authorization: `Bearer ${token}` }, - }); - expect(getRes.ok()).toBeTruthy(); - const settings = await getRes.json(); - expect(settings.general_max_upload_batch_size_mb).toBe(10); - - // Revert to default - await page.request.put('/api/admin/settings/general', { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - data: { general_max_upload_batch_size_mb: 95 }, - }); - }); -});