feat: support external and step images with structured cooking instructions

This commit is contained in:
Sascha Nesterovic committed 2026-10-24 17:31:47 +02:00
1 parent 361862ad0b
commit 0ec1d45bdb
34 files changed
+898 -92

No files matched your search

+14 -9
View File
@@ -8,6 +8,16 @@ const text = (max: number) =>
.refine((v) => !/<\/?[a-z][^>]*>/i.test(v), 'HTML ist nicht erlaubt.');
const id = z.string().regex(/^[a-zA-Z0-9_-]{1,80}$/);
export const units = ['g', 'kg', 'ml', 'l', 'Stück', 'TL', 'EL', 'Prise', 'Bund', ''] as const;
export const imageSchema = z.union(
[
z.string().regex(/^\/(?:media\/[a-f0-9-]+\.webp|images\/[a-z0-9-]+\.(?:webp|jpg|svg))$/),
z
.url({ protocol: /^https$/ })
.max(2048)
.regex(/^https:\/\/[^@\s/\\]+(?:[/?#][^\s\\]*)?$/i),
],
{ error: 'Bitte einen lokalen Bildpfad oder eine vollständige HTTPS-Bildadresse verwenden.' },
);
export const ingredientSchema = z.object({
id,
name: text(160).min(1, 'Zutat benötigt einen Namen.'),
@@ -22,25 +32,20 @@ export const stepSchema = z.object({
title: text(160).default(''),
text: text(6000).min(1, 'Schritt benötigt eine Anleitung.'),
ingredients: z.array(id).max(100).default([]),
images: z.array(imageSchema).max(10).default([]),
timers: z
.array(z.object({ id, label: text(100).min(1), seconds: z.number().int().min(1).max(172800) }))
.max(10)
.default([]),
});
const image = z
.string()
.regex(
/^\/(?:media\/[a-f0-9-]+\.webp|images\/[a-z0-9-]+\.(?:webp|jpg|svg))$/,
'Nur lokale Bilder sind erlaubt.',
);
export const recipeSchema = z
.object({
id: z.string().uuid().optional(),
title: text(180).min(2, 'Titel: mindestens 2 Zeichen.'),
subtitle: text(200).default(''),
description: text(4000).default(''),
image: image.or(z.literal('')).default(''),
images: z.array(image).max(20).default([]),
image: imageSchema.or(z.literal('')).default(''),
images: z.array(imageSchema).max(20).default([]),
categories: z.array(text(60).min(1)).max(20).default([]),
tags: z.array(text(60).min(1)).max(30).default([]),
cuisine: text(80).default(''),
@@ -128,7 +133,7 @@ export function emptyRecipe(): Recipe {
ingredients: [
{ id: 'i1', name: '', amount: null, unit: 'g', section: '', note: '', optional: false },
],
steps: [{ id: 's1', title: '', text: '', ingredients: [], timers: [] }],
steps: [{ id: 's1', title: '', text: '', ingredients: [], images: [], timers: [] }],
tips: '',
storage: '',
nutrition: null,