Files
familienarchiv/frontend/src/routes/+page.server.ts
Marcel 25aa05411f fix(server): allowlist dir param in page.server.ts
Mirrors the existing sort allowlist pattern. Any value other than 'asc' or
'desc' silently falls back to 'desc', preventing arbitrary strings from
reaching the search API.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 09:39:24 +02:00

128 lines
4.2 KiB
TypeScript

import { redirect } from '@sveltejs/kit';
import { createApiClient } from '$lib/api.server';
import type { components } from '$lib/generated/api';
type IncompleteDocumentDTO = components['schemas']['IncompleteDocumentDTO'];
type StatsDTO = components['schemas']['StatsDTO'];
type Document = components['schemas']['Document'];
export async function load({ url, fetch }) {
const q = url.searchParams.get('q') || '';
const from = url.searchParams.get('from') || '';
const to = url.searchParams.get('to') || '';
const senderId = url.searchParams.get('senderId') || '';
const receiverId = url.searchParams.get('receiverId') || '';
const tags = url.searchParams.getAll('tag');
const VALID_SORTS = ['DATE', 'TITLE', 'SENDER', 'RECEIVER', 'UPLOAD_DATE'] as const;
type ValidSort = (typeof VALID_SORTS)[number];
const rawSort = url.searchParams.get('sort') ?? 'DATE';
const sort: ValidSort = (VALID_SORTS as readonly string[]).includes(rawSort)
? (rawSort as ValidSort)
: 'DATE';
const VALID_DIRS = ['asc', 'desc'] as const;
type ValidDir = (typeof VALID_DIRS)[number];
const rawDir = url.searchParams.get('dir') ?? 'desc';
const dir: ValidDir = (VALID_DIRS as readonly string[]).includes(rawDir)
? (rawDir as ValidDir)
: 'desc';
const tagQ = url.searchParams.get('tagQ') || '';
const isDashboard = !q && !from && !to && !senderId && !receiverId && !tags.length && !tagQ;
const api = createApiClient(fetch);
try {
const [docsResult, personsResult] = await Promise.all([
isDashboard
? Promise.resolve(null)
: api.GET('/api/documents/search', {
params: {
query: {
q: q || undefined,
from: from || undefined,
to: to || undefined,
senderId: senderId || undefined,
receiverId: receiverId || undefined,
tag: tags.length ? tags : undefined,
tagQ: tagQ || undefined,
sort,
dir: dir || undefined
}
}
}),
api.GET('/api/persons')
]);
if (personsResult.response.status === 401) {
throw redirect(302, '/login');
}
if (docsResult && docsResult.response.status === 401) {
throw redirect(302, '/login');
}
const searchResult = docsResult?.data as { documents?: Document[]; total?: number } | null;
const documents: Document[] = searchResult?.documents ?? [];
const total: number = searchResult?.total ?? 0;
const allPersons = (personsResult.data ?? []) as {
id: string;
firstName: string;
lastName: string;
}[];
const senderObj = allPersons.find((p) => p.id === senderId);
const receiverObj = allPersons.find((p) => p.id === receiverId);
// Dashboard widgets — failures are isolated and don't crash the page
let stats: StatsDTO | null = null;
let incompleteDocs: IncompleteDocumentDTO[] = [];
let recentDocs: Document[] = [];
if (isDashboard) {
const [statsResult, incompleteResult, recentResult] = await Promise.allSettled([
api.GET('/api/stats'),
api.GET('/api/documents/incomplete', { params: { query: { size: 3 } } }),
api.GET('/api/documents/recent-activity', { params: { query: { size: 5 } } })
]);
if (statsResult.status === 'fulfilled' && statsResult.value.response.ok) {
stats = statsResult.value.data ?? null;
}
if (incompleteResult.status === 'fulfilled' && incompleteResult.value.response.ok) {
incompleteDocs = incompleteResult.value.data ?? [];
}
if (recentResult.status === 'fulfilled' && recentResult.value.response.ok) {
recentDocs = recentResult.value.data ?? [];
}
}
return {
isDashboard,
documents,
total,
stats,
incompleteDocs,
recentDocs,
initialValues: {
senderName: senderObj?.displayName ?? '',
receiverName: receiverObj?.displayName ?? ''
},
filters: { q, from, to, senderId, receiverId, tags, sort, dir, tagQ },
error: null as string | null
};
} catch (e) {
if ((e as { status?: number }).status) throw e;
console.error('Error loading data:', e);
return {
isDashboard,
documents: [],
total: 0,
stats: null,
incompleteDocs: [],
recentDocs: [],
initialValues: { senderName: '', receiverName: '' },
filters: { q, from, to, senderId, receiverId, tags, sort, dir, tagQ },
error: 'Daten konnten nicht geladen werden.' as string | null
};
}
}