- Extract isPureTextRelevance() private static method to replace the 7-clause inline boolean in searchDocuments - Guard long→int cast in relevanceSortedPageFromSql to prevent silent overflow at page ≥43M (CWE-190) - resolvePersonName now uses the typed API client (createApiClient) instead of raw fetch, aligning with project conventions - Update DocumentServiceTest stubs to match new FTS path (findFtsPageRaw + findAllById instead of findAllMatchingIdsByFts) - Rewrite page.server.spec.ts person-name tests to mock via path-based API dispatch, matching the new api.GET call site Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
import { redirect } from '@sveltejs/kit';
|
|
import { createApiClient } from '$lib/shared/api.server';
|
|
import { getErrorMessage } from '$lib/shared/errors';
|
|
import type { components } from '$lib/generated/api';
|
|
|
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
|
async function resolvePersonName(
|
|
id: string,
|
|
api: ReturnType<typeof createApiClient>
|
|
): Promise<string> {
|
|
if (!UUID_RE.test(id)) return '';
|
|
try {
|
|
const result = await api.GET('/api/persons/{id}', { params: { path: { id } } });
|
|
if (!result.response.ok) return '';
|
|
return result.data?.displayName ?? '';
|
|
} catch (e) {
|
|
console.error('[resolvePersonName] failed for id', id, e);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
type DocumentSearchItem = components['schemas']['DocumentSearchItem'];
|
|
|
|
const VALID_SORTS = ['DATE', 'TITLE', 'SENDER', 'RECEIVER', 'UPLOAD_DATE', 'RELEVANCE'] as const;
|
|
type ValidSort = (typeof VALID_SORTS)[number];
|
|
const VALID_DIRS = ['asc', 'desc'] as const;
|
|
type ValidDir = (typeof VALID_DIRS)[number];
|
|
|
|
const PAGE_SIZE = 50;
|
|
|
|
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 rawSort = url.searchParams.get('sort') ?? 'DATE';
|
|
const sort: ValidSort = (VALID_SORTS as readonly string[]).includes(rawSort)
|
|
? (rawSort as ValidSort)
|
|
: 'DATE';
|
|
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 tagOp = url.searchParams.get('tagOp') === 'OR' ? 'OR' : 'AND';
|
|
const page = Math.max(0, Number(url.searchParams.get('page') ?? '0') || 0);
|
|
|
|
const api = createApiClient(fetch);
|
|
|
|
let result;
|
|
let initialSenderName = '';
|
|
let initialReceiverName = '';
|
|
try {
|
|
[result, [initialSenderName, initialReceiverName]] = await Promise.all([
|
|
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 && !tags.length ? tagQ : undefined,
|
|
tagOp: tagOp === 'OR' ? 'OR' : undefined,
|
|
sort,
|
|
dir: dir || undefined,
|
|
page,
|
|
size: PAGE_SIZE
|
|
}
|
|
}
|
|
}),
|
|
Promise.all([resolvePersonName(senderId, api), resolvePersonName(receiverId, api)])
|
|
]);
|
|
} catch {
|
|
return {
|
|
items: [] as DocumentSearchItem[],
|
|
totalElements: 0,
|
|
pageNumber: 0,
|
|
pageSize: PAGE_SIZE,
|
|
totalPages: 0,
|
|
q,
|
|
from,
|
|
to,
|
|
senderId,
|
|
receiverId,
|
|
initialSenderName: '',
|
|
initialReceiverName: '',
|
|
tags,
|
|
sort,
|
|
dir,
|
|
tagQ,
|
|
tagOp,
|
|
error: 'Daten konnten nicht geladen werden.' as string | null
|
|
};
|
|
}
|
|
|
|
if (result.response.status === 401) {
|
|
throw redirect(302, '/login');
|
|
}
|
|
|
|
const errorMessage: string | null = !result.response.ok
|
|
? (getErrorMessage((result.error as unknown as { code?: string })?.code) ??
|
|
'Daten konnten nicht geladen werden.')
|
|
: null;
|
|
|
|
return {
|
|
items: (result.data?.items ?? []) as DocumentSearchItem[],
|
|
totalElements: result.data?.totalElements ?? 0,
|
|
pageNumber: result.data?.pageNumber ?? page,
|
|
pageSize: result.data?.pageSize ?? PAGE_SIZE,
|
|
totalPages: result.data?.totalPages ?? 0,
|
|
q,
|
|
from,
|
|
to,
|
|
senderId,
|
|
receiverId,
|
|
initialSenderName,
|
|
initialReceiverName,
|
|
tags,
|
|
sort,
|
|
dir,
|
|
tagQ,
|
|
tagOp,
|
|
error: errorMessage
|
|
};
|
|
}
|