Files
familienarchiv/frontend/src/routes/admin/users/[id]/+page.server.ts
Marcel 31d3ec8367 refactor(admin/users): migrate update action to createApiClient
Replace fetch('/api/users/${id}', { method: 'PUT', ... }) + inline JSON
error parsing with createApiClient(fetch).PUT('/api/users/{id}', ...) and
the standard result.error cast pattern.

Also fix pre-existing Sentry mock event failures in layout.server.spec.ts
by adding request and url to the test event object.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 13:33:07 +02:00

79 lines
2.6 KiB
TypeScript

import { error, fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { createApiClient } from '$lib/shared/api.server';
import { getErrorMessage } from '$lib/shared/errors';
import type { components } from '$lib/generated/api';
export const load: PageServerLoad = async ({ params, fetch, locals }) => {
const user = locals.user;
const hasAdmin = user?.groups?.some((g: { permissions: string[] }) =>
g.permissions.includes('ADMIN')
);
if (!hasAdmin) throw error(403, getErrorMessage('FORBIDDEN'));
const api = createApiClient(fetch);
const [userResult, groupsResult] = await Promise.all([
api.GET('/api/users/{id}', { params: { path: { id: params.id } } }),
api.GET('/api/groups')
]);
if (!userResult.response.ok) throw error(404, getErrorMessage('USER_NOT_FOUND'));
return {
editUser: userResult.data!,
groups: groupsResult.data ?? []
};
};
export const actions: Actions = {
update: async ({ params, request, fetch }) => {
const data = await request.formData();
const newPassword = data.get('newPassword') as string;
const confirmPassword = data.get('confirmPassword') as string;
if (newPassword && newPassword !== confirmPassword) {
return fail(400, { error: getErrorMessage('PASSWORDS_DO_NOT_MATCH') });
}
const birthDateRaw = data.get('birthDate') as string;
const body = {
firstName: (data.get('firstName') as string) || null,
lastName: (data.get('lastName') as string) || null,
birthDate: birthDateRaw || null,
email: (data.get('email') as string) || null,
contact: (data.get('contact') as string) || null,
newPassword: newPassword || null,
groupIds: data.getAll('groupIds') as string[]
};
const api = createApiClient(fetch);
const result = await api.PUT('/api/users/{id}', {
params: { path: { id: params.id } },
// Body may contain null for fields the user cleared; the backend treats
// null as "clear this field". Cast to satisfy the optional-only spec type.
body: body as components['schemas']['AdminUpdateUserRequest']
});
if (!result.response.ok) {
const code = (result.error as unknown as { code?: string })?.code;
return fail(result.response.status, { error: getErrorMessage(code) });
}
return { success: true };
},
delete: async ({ params, fetch }) => {
const api = createApiClient(fetch);
const result = await api.DELETE('/api/users/{id}', {
params: { path: { id: params.id } }
});
if (!result.response.ok) {
const code = (result.error as unknown as { code?: string })?.code;
return fail(result.response.status, { error: getErrorMessage(code) });
}
throw redirect(303, '/admin/users');
}
};