Files
kochwas/src/routes/api/recipes/[id]/+server.ts

128 lines
4.2 KiB
TypeScript
Raw Normal View History

import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';
import { z } from 'zod';
import { getDb } from '$lib/server/db';
import {
deleteRecipe,
getRecipeById,
replaceIngredients,
replaceSteps,
updateRecipeMeta
} from '$lib/server/recipes/repository';
import {
listComments,
listCookingLog,
listRatings,
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
renameRecipe,
setRecipeHiddenFromRecent
} from '$lib/server/recipes/actions';
const IngredientSchema = z.object({
position: z.number().int().nonnegative(),
quantity: z.number().nullable(),
unit: z.string().max(30).nullable(),
name: z.string().min(1).max(200),
note: z.string().max(300).nullable(),
raw_text: z.string().max(500)
});
const StepSchema = z.object({
position: z.number().int().positive(),
text: z.string().min(1).max(4000)
});
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
const PatchSchema = z
.object({
title: z.string().min(1).max(200).optional(),
description: z.string().max(2000).nullable().optional(),
servings_default: z.number().int().positive().nullable().optional(),
servings_unit: z.string().max(30).nullable().optional(),
prep_time_min: z.number().int().nonnegative().nullable().optional(),
cook_time_min: z.number().int().nonnegative().nullable().optional(),
total_time_min: z.number().int().nonnegative().nullable().optional(),
cuisine: z.string().max(60).nullable().optional(),
category: z.string().max(60).nullable().optional(),
ingredients: z.array(IngredientSchema).optional(),
steps: z.array(StepSchema).optional(),
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
hidden_from_recent: z.boolean().optional()
})
.refine((v) => Object.keys(v).length > 0, { message: 'Empty patch' });
function parseId(raw: string): number {
const id = Number(raw);
if (!Number.isInteger(id) || id <= 0) error(400, { message: 'Invalid id' });
return id;
}
export const GET: RequestHandler = async ({ params }) => {
const id = parseId(params.id!);
const db = getDb();
const recipe = getRecipeById(db, id);
if (!recipe) error(404, { message: 'Recipe not found' });
const ratings = listRatings(db, id);
const comments = listComments(db, id);
const cooking_log = listCookingLog(db, id);
const avg_stars =
ratings.length === 0 ? null : ratings.reduce((s, r) => s + r.stars, 0) / ratings.length;
return json({ recipe, ratings, comments, cooking_log, avg_stars });
};
export const PATCH: RequestHandler = async ({ params, request }) => {
const id = parseId(params.id!);
const body = await request.json().catch(() => null);
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
const parsed = PatchSchema.safeParse(body);
if (!parsed.success) error(400, { message: 'Invalid body' });
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
const db = getDb();
const p = parsed.data;
// Spezielle Kurz-Updates (bleiben als Sonderfall, weil sie FTS triggern
// bzw. andere Tabellen mitpflegen).
if (p.title !== undefined && Object.keys(p).length === 1) {
renameRecipe(db, id, p.title);
return json({ ok: true });
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
}
if (p.hidden_from_recent !== undefined && Object.keys(p).length === 1) {
setRecipeHiddenFromRecent(db, id, p.hidden_from_recent);
return json({ ok: true });
feat(ui): Favoriten-Liste, Dismiss-from-Recent, Inline-Rename, Lucide-Icons Homepage: - Neue Sektion "Deine Favoriten" über "Zuletzt hinzugefügt" (alphabetisch sortiert, lädt wenn Profil aktiv ist; versteckt sonst) - Jede Karte in "Zuletzt hinzugefügt" hat jetzt oben-rechts ein X-Icon zum Ausblenden. Das Rezept selbst bleibt in der DB — nur die Anzeige in der Recent-Liste wird per recipe.hidden_from_recent = 1 unterdrückt. Section versteckt sich, wenn die Liste leer wird. DB: - Neue Migration 004_recipe_hidden_from_recent.sql (+Index) - listFavoritesForProfile in search-local.ts (ORDER BY title NOCASE) - setRecipeHiddenFromRecent in actions.ts API: - GET /api/recipes/favorites?profile_id=X - PATCH /api/recipes/[id] akzeptiert jetzt title und/oder hidden_from_recent (Zod-Schema mit refine) Rezept-Detail: - Titel ist jetzt inline editierbar: kleines Stift-Icon rechts neben H1. Click öffnet Input, Enter speichert (PATCH), Escape bricht ab. Kein location.reload() mehr. - RecipeView bekommt neuen Snippet-Prop titleSlot für Title-Override. - Neue Aktionsreihenfolge: Zeile 1: Favorit | Wunschliste | Drucken Zeile 2: Heute gekocht | Löschen (Umbenennen ist jetzt am Titel statt in der Leiste.) Icons (lucide-svelte, neues Dep): - Emoji-Icons durch Lucide-SVGs ersetzt auf Startseite, Header, Rezept-Detail, Wunschliste, Header-Dropdown: 🍽️→Heart/Utensils, ⚙️→Settings, 🥘→CookingPot, 🌐→Globe, ♥/♡→Heart(filled), 🖨→Printer, ✎→Pencil, 🗑→Trash2, ✓→Check, 🍳→ChefHat, X→X - Header-Brand-Badge auf Mobile behält sein 🍳 (ist im ::after-Pseudo, Lucide käme da nicht sauber rein). - SearchLoader-Emojis bleiben — die sind Teil der Animations-Charme. Tests: 99/99 grün (bestehend), Typecheck 0 Fehler.
2026-04-17 18:57:17 +02:00
}
// Voller Edit-Modus-Patch.
const hasMeta =
p.title !== undefined ||
p.description !== undefined ||
p.servings_default !== undefined ||
p.servings_unit !== undefined ||
p.prep_time_min !== undefined ||
p.cook_time_min !== undefined ||
p.total_time_min !== undefined ||
p.cuisine !== undefined ||
p.category !== undefined;
if (hasMeta) {
updateRecipeMeta(db, id, {
title: p.title,
description: p.description,
servings_default: p.servings_default,
servings_unit: p.servings_unit,
prep_time_min: p.prep_time_min,
cook_time_min: p.cook_time_min,
total_time_min: p.total_time_min,
cuisine: p.cuisine,
category: p.category
});
}
if (p.ingredients !== undefined) {
replaceIngredients(db, id, p.ingredients);
}
if (p.steps !== undefined) {
replaceSteps(db, id, p.steps);
}
if (p.hidden_from_recent !== undefined) {
setRecipeHiddenFromRecent(db, id, p.hidden_from_recent);
}
return json({ ok: true, recipe: getRecipeById(db, id) });
};
export const DELETE: RequestHandler = async ({ params }) => {
const id = parseId(params.id!);
deleteRecipe(getDb(), id);
return json({ ok: true });
};