Backend: rate limiter gains check_with_retry() returning seconds until
the next slot opens. Upload 429 responses include retry_after_secs in
JSON and a Retry-After header.
Frontend: upload queue catches 429 as RateLimitError, resets affected
item to pending, schedules processQueue() for the server-reported delay,
and shows a live countdown banner in the queue UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Disable Axum's 2 MB default body limit on the upload route so large
photos/videos are accepted without HTTP 400
- Serialize UserRole as lowercase in JWT so the frontend role checks
('guest'/'host'/'admin') match correctly
- Drain multipart body before returning early upload errors (rate-limit,
ban, event-lock) to keep the HTTP keep-alive connection clean and
prevent cascading Netzwerkfehler / empty-500 responses
- Add TraceLayer for request logging and Vite dev proxy config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 4-step dismissible onboarding overlay shown on first feed visit
(welcome, upload, hashtags, PIN importance). Dismissed state persisted
in localStorage under eventsnap_guide_seen. Step indicator dots and
skip/continue buttons included.
Update HTML export guide modal to persist the eventsnap_html_guide_seen
flag: first download shows the instructions modal; subsequent clicks go
straight to download without interruption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add /account route showing display name (from localStorage), role badge,
session expiry decoded from JWT, and recovery PIN display with copy button.
Join and recover flows now persist display_name to localStorage via setAuth().
Feed header logout button replaced with person-icon link to /account;
logout is available from the account page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add async ZIP and HTML offline viewer export workers, download endpoints,
and a guest-facing /export page.
Backend — export workers (tokio::spawn, run after gallery release):
- ZIP worker: streams all non-deleted originals into Gallery.zip via
async_zip (Stored compression), organised into Photos/ and Videos/
with {date}_{uploader}_{id}.{ext} filenames; updates progress_pct in DB
- HTML worker: renders Memories.html via minijinja template (self-contained:
inlined CSS + JS, relative media paths); packs it with README.txt and
all media into Memories.zip (Deflate for text, Stored for media)
- Both workers mark export_job status (running → done/failed), update
export_zip_ready / export_html_ready on the event, and broadcast SSE
export-progress + export-available when both complete
Backend — new endpoints (AuthUser):
- GET /export/zip → streams Gallery.zip if export_zip_ready
- GET /export/html → streams Memories.zip if export_html_ready
- GET /export/status → released flag + per-type status/progress (moved from admin)
Memories.html features: warm keepsake aesthetic, responsive grid, fullscreen
lightbox with captions/comments/likes, client-side hashtag filter chips,
XSS-safe JS, fully offline (no external deps)
Frontend — /export page:
- Locked state: padlock illustration + message
- Released state: ZIP and HTML cards with progress bars (SSE-driven),
download buttons enabled only when ready
- HTML guide modal (unzip instructions + Wi-Fi tip) before download begins
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Admin Dashboard at /admin for server configuration, disk usage
monitoring, and export job status, plus a public export/status endpoint.
Backend — new /api/v1/admin/* endpoints (RequireAdmin auth):
- GET /admin/stats → user/upload/comment counts + disk usage
- GET /admin/config → all config key/value pairs
- PATCH /admin/config → update any subset of config keys; validates
key whitelist and numeric values
- GET /admin/export/jobs → export_job rows for the event
Backend — public (AuthUser) endpoint:
- GET /export/status → released flag + zip/html job status/progress
Frontend — /admin page:
- Stats grid: guest count, upload count, comment count
- Disk usage bar with GB/MB formatting; red ≥ 90%, amber ≥ 75%
- Config form: labelled numeric inputs for all eight config keys,
sends only changed values on save
- Export jobs list: type label, status badge, progress bar for running jobs,
error message if failed; manual refresh button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Host Dashboard for event and guest management, accessible at /host.
Backend — new /api/v1/host/* endpoints (RequireHost auth):
- GET /host/event → event name + lock/release state
- POST /host/event/close|open → lock or unlock uploads; SSE broadcast
- POST /host/gallery/release → set release timestamp, enqueue export jobs
- GET /host/users → all guests with upload count & bytes
- POST /host/users/{id}/ban → ban with optional upload-hide choice
- POST /host/users/{id}/unban → lift ban
- PATCH /host/users/{id}/role → promote guest→host or demote host→guest
- DELETE /host/upload/{id} → host-level soft-delete + SSE
- DELETE /host/comment/{id} → host-level soft-delete
Frontend — /host page:
- Event controls: lock/unlock toggle and release-gallery button with status badges
- Guest table: display name, role badge, upload count, storage used
- Ban flow: modal asking whether to keep or hide the user's uploads
- Promote/demote buttons respecting caller role (host can promote guests; admin can demote hosts)
- auth.ts: getRole() decodes JWT payload client-side to gate the route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add in-app camera capture to the upload flow. Guests can now take photos
and record videos directly via getUserMedia without leaving the app.
The captured media is immediately queued through the existing IndexedDB
upload pipeline alongside library-picked files.
- CameraCapture.svelte: fullscreen overlay with live preview, photo
capture (JPEG via canvas), video recording (WebM/MP4 via MediaRecorder),
front/back camera toggle, recording timer, and permission-denied error state
- Upload page: side-by-side "Gallery" and "Camera" pickers; shared
caption/hashtags fields apply to both sources; Blob→File conversion
with timestamped filename before enqueue
- .env.test: reference environment config for local testing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cursor-based feed endpoint using v_feed view with hashtag filtering
- Like toggle (INSERT ON CONFLICT), comments CRUD
- Feed delta endpoint for SSE-driven incremental updates
- SSE client with Page Visibility API (pause/reconnect)
- Responsive photo/video grid with infinite scroll
- Hashtag filter chips, lightbox modal with comments
- Media file serving via tower-http ServeDir
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- AppConfig, AppError, AppState modules for shared infrastructure
- JWT creation/verification with HS256 (jsonwebtoken crate)
- Session management: SHA-256 token hashing, DB-backed sessions
- Auth middleware: AuthUser, RequireHost, RequireAdmin extractors
- POST /api/v1/join: name-only registration, 4-digit PIN + bcrypt hash
- POST /api/v1/recover: PIN-based recovery with 3-attempt lockout (15 min)
- POST /api/v1/admin/login: bcrypt password verification
- DELETE /api/v1/session: logout (session invalidation)
- Migration 006: user PIN lockout columns (failed_pin_attempts, pin_locked_until)
- Models: Event, User (with role enum), Session with all CRUD methods
Frontend:
- api.ts: typed fetch wrapper with automatic Bearer token injection
- auth.ts: JWT/PIN localStorage management with Svelte store
- /join: name entry form with PIN display modal and copy button
- /recover: name + PIN recovery form with saved PIN pre-fill
- /feed: placeholder gallery page with logout
- Root layout: auth initialization on mount
- Root page: redirect to /join or /feed based on auth state
All responses use German language strings as specified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>