refactor: migrate all page.server.ts files to typed API client

All server-side fetch calls now go through createApiClient() from
$lib/api.server.ts, which wraps openapi-fetch with the generated OpenAPI
types. This means backend changes are reflected in the frontend after
running npm run generate:api.

- Add stub src/lib/generated/api.ts (replaced by generate:api output)
- Fix GroupController: missing /api prefix and ResponseStatusException
- Root, conversations, persons, documents pages all use typed client
- Error handling uses apiError.code directly (no parseBackendError needed)
- Edit page load uses typed client; PUT action keeps raw fetch (multipart)
- Login keeps raw fetch (explicit Authorization header, not cookie auth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-15 13:39:15 +01:00
parent 5d356cd694
commit d76248cffd
11 changed files with 220 additions and 259 deletions

View File

@@ -1,72 +1,58 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { createApiClient } from '$lib/api.server';
export const load: PageServerLoad = async ({ url, fetch }) => {
// 1. Extract params
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 tags = url.searchParams.getAll('tag');
// 2. Build Search URL
const searchUrl = new URL('http://localhost:8080/api/documents/search');
if (q) searchUrl.searchParams.set('q', q);
if (from) searchUrl.searchParams.set('from', from);
if (to) searchUrl.searchParams.set('to', to);
if (senderId) searchUrl.searchParams.set('senderId', senderId);
if (receiverId) searchUrl.searchParams.set('receiverId', receiverId);
if(tags) tags.forEach(tag => searchUrl.searchParams.append('tag', tag));
// 3. Build Persons URL (to resolve names for the typeahead initial value)
// Ideally, we would have endpoints like /api/persons/{id}, but for now we load the list or search.
// To keep it simple and performant enough for now, we fetch all to find the names.
const personsUrl = 'http://localhost:8080/api/persons';
const api = createApiClient(fetch);
try {
const [docsRes, personsRes] = await Promise.all([
fetch(searchUrl.toString()),
fetch(personsUrl)
const [docsResult, personsResult] = 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
}
}
}),
api.GET('/api/persons')
]);
if (docsRes.status === 401 || personsRes.status === 401) {
if (docsResult.response.status === 401 || personsResult.response.status === 401) {
throw redirect(302, '/login');
}
const documents = await docsRes.json();
const allPersons = await personsRes.json();
const documents = docsResult.data ?? [];
const allPersons: { id: string; firstName: string; lastName: string }[] = personsResult.data ?? [];
// Resolve Names for the Typeahead Inputs
const senderObj = allPersons.find((p: any) => p.id === senderId);
const receiverObj = allPersons.find((p: any) => p.id === receiverId);
const senderName = senderObj ? `${senderObj.firstName} ${senderObj.lastName}` : '';
const receiverName = receiverObj ? `${receiverObj.firstName} ${receiverObj.lastName}` : '';
const senderObj = allPersons.find(p => p.id === senderId);
const receiverObj = allPersons.find(p => p.id === receiverId);
return {
documents,
// We don't need to pass the full persons list to the frontend anymore,
// as the Typeahead fetches it dynamically. We only pass the resolved names.
initialValues: {
senderName,
receiverName
senderName: senderObj ? `${senderObj.firstName} ${senderObj.lastName}` : '',
receiverName: receiverObj ? `${receiverObj.firstName} ${receiverObj.lastName}` : ''
},
filters: { q, from, to, senderId, receiverId, tags }
};
} catch (error) {
console.error("Error loading data:", error);
} catch (e) {
if ((e as { status?: number }).status) throw e;
console.error('Error loading data:', e);
return {
documents: [],
initialValues: { senderName: '', receiverName: '' },
filters: { q, from, to, senderId, receiverId },
error: "Could not load data."
filters: { q, from, to, senderId, receiverId, tags }
};
}
};
}