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,159 +1,146 @@
import { error, fail } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
import { parseBackendError, getErrorMessage } from '$lib/errors';
import { createApiClient } from '$lib/api.server';
import { getErrorMessage } from '$lib/errors';
export async function load({ fetch, locals }) {
// 1. Check Permissions (Adapt logic to your user object)
const user = locals.user;
// Assuming user.group.permissions is an array of strings
const hasAdmin = user?.groups.some(g => g.permissions.includes("ADMIN"));
const hasAdmin = user?.groups.some((g: { permissions: string[] }) => g.permissions.includes('ADMIN'));
if (!hasAdmin) throw error(403, getErrorMessage('FORBIDDEN'));
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const api = createApiClient(fetch);
// 2. Load Data
const [usersRes, groupsRes, tagsRes] = await Promise.all([
fetch(baseUrl + '/api/users'),
fetch(baseUrl + '/api/groups'),
fetch(baseUrl + '/api/tags')
const [usersResult, groupsResult, tagsResult] = await Promise.all([
api.GET('/api/users'),
api.GET('/api/groups'),
api.GET('/api/tags')
]);
return {
users: await usersRes.json(),
groups: await groupsRes.json(),
tags: await tagsRes.json()
};
return {
users: usersResult.data ?? [],
groups: groupsResult.data ?? [],
tags: tagsResult.data ?? []
};
}
export const actions = {
createUser: async ({ request, fetch }) => {
const data = await request.formData();
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const api = createApiClient(fetch);
// Extract array of group IDs
// "groupIds" matches the name attribute in the <select>
const groupIds = data.getAll('groupIds');
const payload = {
username: data.get('username'),
initialPassword: data.get('password'),
groupIds: groupIds // Send array to backend
};
console.log("Payload", JSON.stringify(payload))
const res = await fetch(baseUrl + '/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
const { error: apiError, response } = await api.POST('/api/users', {
body: {
username: data.get('username'),
initialPassword: data.get('password'),
groupIds: data.getAll('groupIds')
}
});
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
deleteUser: async ({ request, fetch }) => {
const data = await request.formData();
const id = data.get('id');
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const id = data.get('id') as string;
const api = createApiClient(fetch);
const res = await fetch(baseUrl + `/api/users/${id}`, {
method: 'DELETE'
const { error: apiError, response } = await api.DELETE('/api/users/{id}', {
params: { path: { id } }
});
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
updateTag: async ({ request, fetch }) => {
const data = await request.formData();
const id = data.get('id');
const name = data.get('name');
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const id = data.get('id') as string;
const api = createApiClient(fetch);
await fetch(baseUrl + `/api/tags/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
const { error: apiError, response } = await api.PUT('/api/tags/{id}', {
params: { path: { id } },
body: { name: data.get('name') }
});
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
deleteTag: async ({ request, fetch }) => {
const data = await request.formData();
const id = data.get('id');
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const id = data.get('id') as string;
const api = createApiClient(fetch);
const res = await fetch(baseUrl + `/api/tags/${id}`, { method: 'DELETE' });
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
const { error: apiError, response } = await api.DELETE('/api/tags/{id}', {
params: { path: { id } }
});
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
createGroup: async ({ request, fetch }) => {
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const data = await request.formData();
const payload = {
name: data.get('name'),
permissions: data.getAll('permissions') // Gets all checked checkboxes
};
const api = createApiClient(fetch);
const res = await fetch(baseUrl + '/api/groups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
const { error: apiError, response } = await api.POST('/api/groups', {
body: {
name: data.get('name'),
permissions: data.getAll('permissions')
}
});
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
updateGroup: async ({ request, fetch }) => {
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const data = await request.formData();
const id = data.get('id');
const payload = {
name: data.get('name'),
permissions: data.getAll('permissions')
};
const id = data.get('id') as string;
const api = createApiClient(fetch);
const res = await fetch(baseUrl + `/api/groups/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
const { error: apiError, response } = await api.PATCH('/api/groups/{id}', {
params: { path: { id } },
body: {
name: data.get('name'),
permissions: data.getAll('permissions')
}
});
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
},
deleteGroup: async ({ request, fetch }) => {
const baseUrl = env.API_INTERNAL_URL || 'http://localhost:8080';
const data = await request.formData();
const id = data.get('id');
const res = await fetch(baseUrl + `/api/groups/${id}`, { method: 'DELETE' });
const id = data.get('id') as string;
const api = createApiClient(fetch);
if (!res.ok) {
const backendError = await parseBackendError(res);
return fail(res.status, { success: false, message: getErrorMessage(backendError?.code) });
const { error: apiError, response } = await api.DELETE('/api/groups/{id}', {
params: { path: { id } }
});
if (apiError) {
const code = (apiError as { code?: string })?.code;
return fail(response.status, { success: false, message: getErrorMessage(code) });
}
return { success: true };
}