feat(geschichten): wire the ?documentId list filter the drawer already links to

DocumentMetadataDrawer links to /geschichten?documentId={id}, but the list
loader silently dropped the param — the user got the unfiltered list. The
loader now validates the UUID and forwards it to GET /api/geschichten,
returning it as documentIdFilter in page data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-10 07:36:54 +02:00
parent c4606cef8b
commit 98e3d924e5
2 changed files with 123 additions and 2 deletions

View File

@@ -6,16 +6,21 @@ import type { PageServerLoad } from './$types';
type Person = components['schemas']['Person'];
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
export const load: PageServerLoad = async ({ url, fetch }) => {
const api = createApiClient(fetch);
const personIds = url.searchParams.getAll('personId');
const rawDocumentId = url.searchParams.get('documentId');
const documentId = rawDocumentId && UUID_PATTERN.test(rawDocumentId) ? rawDocumentId : null;
const [listResult, ...personResults] = await Promise.all([
api.GET('/api/geschichten', {
params: {
query: {
status: 'PUBLISHED',
personId: personIds.length ? personIds : undefined
personId: personIds.length ? personIds : undefined,
documentId: documentId ?? undefined
}
}
}),
@@ -32,6 +37,7 @@ export const load: PageServerLoad = async ({ url, fetch }) => {
return {
geschichten: listResult.data ?? [],
personFilters
personFilters,
documentIdFilter: documentId
};
};

View File

@@ -0,0 +1,115 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
vi.mock('$lib/shared/api.server', () => ({
createApiClient: vi.fn(),
extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code
}));
import { load } from './+page.server';
import { createApiClient } from '$lib/shared/api.server';
beforeEach(() => vi.clearAllMocks());
const VALID_UUID = '11111111-2222-3333-4444-555555555555';
function makeUrl(params: Record<string, string | string[]> = {}) {
const url = new URL('http://localhost/geschichten');
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
value.forEach((v) => url.searchParams.append(key, v));
} else {
url.searchParams.set(key, value);
}
}
return url;
}
function mockApi() {
const mockGet = vi.fn().mockResolvedValue({
response: { ok: true, status: 200 },
data: []
});
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
return mockGet;
}
function callLoad(url: URL) {
return load({
url,
request: new Request('http://localhost/geschichten'),
fetch: vi.fn() as unknown as typeof fetch
});
}
// ─── documentId filter forwarding ─────────────────────────────────────────────
describe('geschichten page load — documentId filter', () => {
it('passes a valid documentId to the geschichten API', async () => {
const mockGet = mockApi();
await callLoad(makeUrl({ documentId: VALID_UUID }));
expect(mockGet).toHaveBeenCalledWith(
'/api/geschichten',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({ documentId: VALID_UUID })
})
})
);
});
it('omits documentId from the API call when the value is not a UUID', async () => {
const mockGet = mockApi();
await callLoad(makeUrl({ documentId: 'not-a-uuid' }));
const query = mockGet.mock.calls[0][1].params.query;
expect(query.documentId).toBeUndefined();
});
it('omits documentId from the API call when the param is absent', async () => {
const mockGet = mockApi();
await callLoad(makeUrl());
const query = mockGet.mock.calls[0][1].params.query;
expect(query.documentId).toBeUndefined();
});
it('returns documentIdFilter in page data when a valid documentId is given', async () => {
mockApi();
const result = await callLoad(makeUrl({ documentId: VALID_UUID }));
expect(result.documentIdFilter).toBe(VALID_UUID);
});
it('returns null documentIdFilter when documentId is invalid', async () => {
mockApi();
const result = await callLoad(makeUrl({ documentId: 'not-a-uuid' }));
expect(result.documentIdFilter).toBeNull();
});
it('keeps forwarding personId filters alongside documentId', async () => {
const mockGet = mockApi();
await callLoad(makeUrl({ documentId: VALID_UUID, personId: [VALID_UUID] }));
expect(mockGet).toHaveBeenCalledWith(
'/api/geschichten',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({
documentId: VALID_UUID,
personId: [VALID_UUID]
})
})
})
);
});
});