Brings the batch-2 security hardening into main (forked from the same base as the UX batch; auto-merged cleanly — verified both sides' edits to social.rs / main.rs coexist): - Cross-event authorization: toggle_like / list_comments / add_comment now event-scope the upload via Upload::find_by_id_and_event (404 on cross-event access); delete_comment uses Comment::soft_delete_in_event. Closes the gap where a guest could like/comment/list across events by upload UUID. - Upload OOM backstop: the /upload route gets DefaultBodyLimit::max(576 MiB) instead of disable(), so a multi-GB body can't be buffered before the handler's per-class size checks run. - upload.rs per-class size-limit refactor, XSS allowlist, deploy hardening (Caddyfile, Dockerfiles, docker-compose, .env.example), and a data-mode doc-comment clarifying the original-media route is capability-(UUID-)gated. The event-scope checks sit before, and batch-3's best-effort count broadcasts after, the like/comment mutations — both preserved. Verified: cargo build clean, svelte-check 0 errors.
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
// Per-device "Datenmodus" — Saver loads compressed previews (default), Original loads
|
|
// the full file via the `/api/v1/upload/{id}/original` endpoint. That route is
|
|
// intentionally unauthenticated (the UUID acts as the capability) so it works from
|
|
// plain `<img src>` / `<video src>` without an Authorization header.
|
|
//
|
|
// Stored per-device in localStorage (not per-user) because data plans are a property
|
|
// of the device the guest is currently holding, not their identity.
|
|
//
|
|
// Used by:
|
|
// - Feed cards (FeedListCard / VirtualFeed grid tiles) to pick which URL to render
|
|
// - Lightbox
|
|
// - Diashow
|
|
// See [docs/FEATURES.md §2.5] for the user-facing model.
|
|
|
|
import { writable } from 'svelte/store';
|
|
import { browser } from '$app/environment';
|
|
|
|
export type DataMode = 'saver' | 'original';
|
|
|
|
const KEY = 'eventsnap_data_mode';
|
|
const DEFAULT: DataMode = 'saver';
|
|
|
|
function readInitial(): DataMode {
|
|
if (!browser) return DEFAULT;
|
|
const raw = localStorage.getItem(KEY);
|
|
return raw === 'original' || raw === 'saver' ? raw : DEFAULT;
|
|
}
|
|
|
|
export const dataMode = writable<DataMode>(readInitial());
|
|
|
|
if (browser) {
|
|
dataMode.subscribe((value) => {
|
|
try {
|
|
localStorage.setItem(KEY, value);
|
|
} catch {
|
|
// localStorage may be unavailable (Safari private mode); ignore.
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build the URL for a feed upload given the current data mode and the URL variants
|
|
* the backend returned. Centralised so every consumer (cards, lightbox, diashow)
|
|
* follows the same fallback rule:
|
|
* Original mode → original API route. Falls back to preview if no upload id is
|
|
* available (defensive — shouldn't happen in practice).
|
|
* Saver mode → preview URL (compressed), falling back to thumbnail and then
|
|
* original.
|
|
*/
|
|
export function pickMediaUrl(
|
|
mode: DataMode,
|
|
upload: { id: string; preview_url: string | null; thumbnail_url: string | null }
|
|
): string {
|
|
if (mode === 'original') {
|
|
return `/api/v1/upload/${upload.id}/original`;
|
|
}
|
|
return upload.preview_url ?? upload.thumbnail_url ?? `/api/v1/upload/${upload.id}/original`;
|
|
}
|