From 3c3a7d0082e7caca660638f9477b6a6972143666 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sat, 18 Jul 2026 17:29:08 +0200 Subject: [PATCH] =?UTF-8?q?fix(diashow):=20open=20SSE=20on=20mount;=20rais?= =?UTF-8?q?e=20upload=20rate=2010=E2=86=92100/hour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diashow only subscribed to SSE events but never called connectSse(), so a kiosk/projector opening /diashow directly (not via /feed) never opened the EventSource — the showcase display got a one-time /feed snapshot and no live updates, showing "Noch keine Beiträge" forever when turned on before any photos. Open the stream in onMount (idempotent) and close it in onDestroy, mirroring the feed page. Raise the default upload_rate_per_hour from 10 to 100 (migration 015, scoped to installs still on the old default so admin overrides are preserved). Guests routinely upload bursts of 10-20 photos; the old default throttled the first burst. Also update the code fallback and the test-mode reseed. Both verified end-to-end against the docker test stack. Co-Authored-By: Claude Opus 4.8 --- PROJECT.md | 2 +- backend/migrations/015_raise_upload_rate.down.sql | 3 +++ backend/migrations/015_raise_upload_rate.up.sql | 10 ++++++++++ backend/src/handlers/test_admin.rs | 4 ++-- backend/src/handlers/upload.rs | 2 +- frontend/src/routes/diashow/+page.svelte | 10 +++++++++- 6 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 backend/migrations/015_raise_upload_rate.down.sql create mode 100644 backend/migrations/015_raise_upload_rate.up.sql diff --git a/PROJECT.md b/PROJECT.md index 1e009a8..7095f83 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -709,7 +709,7 @@ CREATE TABLE config ( INSERT INTO config (key, value) VALUES ('max_image_size_mb', '20'), ('max_video_size_mb', '500'), - ('upload_rate_per_hour', '10'), + ('upload_rate_per_hour', '100'), -- raised from 10 in migration 015 (guests upload bursts of 10-20) ('feed_rate_per_min', '60'), ('export_rate_per_day', '3'), ('quota_tolerance', '0.75'), diff --git a/backend/migrations/015_raise_upload_rate.down.sql b/backend/migrations/015_raise_upload_rate.down.sql new file mode 100644 index 0000000..17946c8 --- /dev/null +++ b/backend/migrations/015_raise_upload_rate.down.sql @@ -0,0 +1,3 @@ +-- Revert the default upload rate to 10/hour for installs still on the raised +-- default (preserves any explicit admin override at another value). +UPDATE config SET value = '10' WHERE key = 'upload_rate_per_hour' AND value = '100'; diff --git a/backend/migrations/015_raise_upload_rate.up.sql b/backend/migrations/015_raise_upload_rate.up.sql new file mode 100644 index 0000000..677f524 --- /dev/null +++ b/backend/migrations/015_raise_upload_rate.up.sql @@ -0,0 +1,10 @@ +-- Raise the default per-guest upload rate from 10/hour to 100/hour. +-- +-- Rationale: guests routinely upload a burst of 10-20 photos at once (phone +-- multi-select). At the old default of 10/hour a real guest's first burst was +-- throttled — surfaced by the 2026-07-18 load test. 100/hour comfortably covers +-- several bursts across an event while still bounding abuse. +-- +-- Only bump installs still on the old default; an admin who deliberately set a +-- different value keeps it (migration 005 seeded 10; this UPDATE is scoped to '10'). +UPDATE config SET value = '100' WHERE key = 'upload_rate_per_hour' AND value = '10'; diff --git a/backend/src/handlers/test_admin.rs b/backend/src/handlers/test_admin.rs index 917ccbf..7e44c7c 100644 --- a/backend/src/handlers/test_admin.rs +++ b/backend/src/handlers/test_admin.rs @@ -40,13 +40,13 @@ pub async fn truncate_all( .execute(&state.pool) .await?; - // Reseed config — mirrors migrations 005 and 009. Kept in sync by hand + // Reseed config — mirrors migrations 005, 009 and 015. Kept in sync by hand // because pulling SQL out of the migration files at runtime is fragile. sqlx::query( r#"INSERT INTO config (key, value) VALUES ('max_image_size_mb', '20'), ('max_video_size_mb', '500'), - ('upload_rate_per_hour', '10'), + ('upload_rate_per_hour', '100'), ('feed_rate_per_min', '60'), ('export_rate_per_day', '3'), ('quota_tolerance', '0.75'), diff --git a/backend/src/handlers/upload.rs b/backend/src/handlers/upload.rs index aabf8a6..0e84f6a 100644 --- a/backend/src/handlers/upload.rs +++ b/backend/src/handlers/upload.rs @@ -48,7 +48,7 @@ pub async fn upload( let upload_rate_on = config::get_bool(&state.config_cache, "upload_rate_enabled", true).await; if rate_limits_on && upload_rate_on { let upload_rate = - config::get_i64(&state.config_cache, "upload_rate_per_hour", 10).await as usize; + config::get_i64(&state.config_cache, "upload_rate_per_hour", 100).await as usize; if let Err(retry_after_secs) = state.rate_limiter.check_with_retry( format!("upload:{}", auth.user_id), upload_rate, diff --git a/frontend/src/routes/diashow/+page.svelte b/frontend/src/routes/diashow/+page.svelte index 1419d1f..1ed41ff 100644 --- a/frontend/src/routes/diashow/+page.svelte +++ b/frontend/src/routes/diashow/+page.svelte @@ -5,7 +5,7 @@ import { getToken } from '$lib/auth'; import { showBottomNav } from '$lib/ui-store'; import { dataMode, pickMediaUrl } from '$lib/data-mode-store'; - import { onSseEvent } from '$lib/sse'; + import { connectSse, disconnectSse, onSseEvent } from '$lib/sse'; import { SlideQueue } from '$lib/diashow/queue'; import { transitions, findTransition } from '$lib/diashow/transitions'; import { acquireWakeLock, releaseWakeLock } from '$lib/diashow/wakelock'; @@ -233,6 +233,13 @@ unsubs.push(onSseEvent('upload-deleted', handleUploadDeleted)); unsubs.push(onSseEvent('user-hidden', handleUserHidden)); unsubs.push(onSseEvent('feed-delta', handleFeedDelta)); + // Open the stream ourselves — a kiosk/projector loads /diashow directly (never + // via /feed), so we can't rely on another page having opened the singleton + // EventSource. Without this the show only ever gets its one-time /feed snapshot + // and never receives live `upload-processed` events. connectSse() is idempotent + // (no-ops if the feed page already opened it). Subscriptions are registered above + // first so no early event is missed. + connectSse(); void loadInitial(); }); @@ -243,6 +250,7 @@ if (processedDebounce) clearTimeout(processedDebounce); void releaseWakeLock(); for (const unsub of unsubs) unsub(); + disconnectSse(); });