Files
mealprep/frontend/src/routes/(app)/recipes/[id]/edit/+page.server.ts
Marcel Raddatz ebaf42d83d feat(recipes): return fail(422) when all ingredients filter to empty
Prevents a silent 400 from the backend when the user submits a form
where every ingredient row has quantity <= 0 or blank name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 09:36:41 +02:00

104 lines
3.4 KiB
TypeScript

import { error, redirect, fail } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { apiClient } from '$lib/server/api';
const VALID_EFFORTS = ['easy', 'medium', 'hard'];
export const load: PageServerLoad = async ({ fetch, params }) => {
const api = apiClient(fetch);
const [recipeResult, tagsResult] = await Promise.all([
api.GET('/v1/recipes/{id}', { params: { path: { id: params.id } } }),
api.GET('/v1/tags', {})
]);
if (recipeResult.error || !recipeResult.data) {
error(404, 'Recipe not found');
}
const recipe = recipeResult.data;
const allTags = tagsResult.data ?? [];
const categories = allTags.map((t) => ({ id: t.id!, name: t.name!, tagType: t.tagType }));
return {
recipe: {
id: recipe.id!,
name: recipe.name!,
serves: recipe.serves,
cookTimeMin: recipe.cookTimeMin,
effort: recipe.effort,
heroImageUrl: recipe.heroImageUrl,
ingredients: (recipe.ingredients ?? []).map((ing) => ({
name: ing.name ?? '',
quantity: ing.quantity ?? 0,
unit: ing.unit ?? ''
})),
steps: (recipe.steps ?? [])
.sort((a, b) => (a.stepNumber ?? 0) - (b.stepNumber ?? 0))
.map((s) => ({ instruction: s.instruction ?? '' })),
tagIds: (recipe.tags ?? []).map((t) => t.id!)
},
categories
};
};
export const actions: Actions = {
update: async ({ request, fetch, params }) => {
const formData = await request.formData();
const name = formData.get('name') as string;
const serves = formData.get('serves');
const cookTimeMin = formData.get('cookTimeMin');
const effort = formData.get('effort') as string;
const heroImageUrl = (formData.get('heroImageUrl') as string) || null;
const ingredientsJson = formData.get('ingredientsJson') as string;
const stepsJson = formData.get('stepsJson') as string;
const tagIds = formData.getAll('tagIds') as string[];
if (!name?.trim()) return fail(422, { error: 'Name ist erforderlich' });
if (!effort || !VALID_EFFORTS.includes(effort))
return fail(422, { error: 'Ungültiger Schwierigkeitsgrad' });
if (!tagIds.length) return fail(422, { error: 'Mindestens eine Kategorie ist erforderlich' });
let parsedIngredients: unknown[];
let parsedSteps: unknown[];
try {
parsedIngredients = JSON.parse(ingredientsJson || '[]');
parsedSteps = JSON.parse(stepsJson || '[]');
} catch {
return fail(400, { error: 'Ungültige Formulardaten' });
}
const filteredIngredients = (
parsedIngredients as { name: string; quantity: string; unit: string }[]
).filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0);
if (!filteredIngredients.length)
return fail(422, { error: 'Mindestens eine gültige Zutat ist erforderlich' });
const api = apiClient(fetch);
const { error: apiError } = await api.PUT('/v1/recipes/{id}', {
params: { path: { id: params.id } },
body: {
name: name.trim(),
serves: serves ? Number(serves) || null : null,
cookTimeMin: cookTimeMin ? Number(cookTimeMin) || null : null,
effort,
heroImageUrl,
ingredients: filteredIngredients.map((ing, i) => ({
newIngredientName: ing.name.trim(),
quantity: Number(ing.quantity),
unit: ing.unit || '',
sortOrder: i
})),
steps: (parsedSteps as string[])
.filter((s) => s?.trim())
.map((s, i) => ({ stepNumber: i + 1, instruction: s.trim() })),
tagIds
}
});
if (apiError) return fail(500, { error: 'Fehler beim Speichern' });
redirect(303, '/recipes');
}
};