fix(recipe): Favoriten-Markierung persistiert beim Neuladen
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 50s

Bug: Beim Neuanzeigen einer Rezeptseite war der Favoriten-Button immer
grau — isFav wurde als local $state(false) initialisiert und die
checkFavorite()-Funktion war eine Stub-Implementation, die nichts
gemacht hat. State lebte nur innerhalb einer Session.

Fix:
- Neue Server-Funktion listFavoriteProfiles(db, recipeId): number[]
  in $lib/server/recipes/actions.ts
- +page.server.ts lädt favorite_profile_ids mit in die Page-Daten
- +page.svelte macht isFav zum $derived aus favoriteProfileIds +
  aktivem Profil. toggleFavorite mutiert die lokale Liste (Add/Remove
  der aktiven Profil-ID) — beim nächsten Load ist die Server-Liste
  wieder Source of Truth.
- Alte Stub-Funktion checkFavorite() entfernt (inkl. Aufruf in
  onMount).
This commit is contained in:
hsiegeln
2026-04-17 18:43:38 +02:00
parent cf31e79fb0
commit 657d006441
3 changed files with 24 additions and 13 deletions

View File

@@ -73,6 +73,17 @@ export function isFavorite(
); );
} }
export function listFavoriteProfiles(
db: Database.Database,
recipeId: number
): number[] {
return (
db
.prepare('SELECT profile_id FROM favorite WHERE recipe_id = ?')
.all(recipeId) as { profile_id: number }[]
).map((r) => r.profile_id);
}
export function logCooked( export function logCooked(
db: Database.Database, db: Database.Database,
recipeId: number, recipeId: number,

View File

@@ -5,6 +5,7 @@ import { getRecipeById } from '$lib/server/recipes/repository';
import { import {
listComments, listComments,
listCookingLog, listCookingLog,
listFavoriteProfiles,
listRatings listRatings
} from '$lib/server/recipes/actions'; } from '$lib/server/recipes/actions';
@@ -17,7 +18,8 @@ export const load: PageServerLoad = async ({ params }) => {
const ratings = listRatings(db, id); const ratings = listRatings(db, id);
const comments = listComments(db, id); const comments = listComments(db, id);
const cooking_log = listCookingLog(db, id); const cooking_log = listCookingLog(db, id);
const favorite_profile_ids = listFavoriteProfiles(db, id);
const avg_stars = const avg_stars =
ratings.length === 0 ? null : ratings.reduce((s, r) => s + r.stars, 0) / ratings.length; ratings.length === 0 ? null : ratings.reduce((s, r) => s + r.stars, 0) / ratings.length;
return { recipe, ratings, comments, cooking_log, avg_stars }; return { recipe, ratings, comments, cooking_log, favorite_profile_ids, avg_stars };
}; };

View File

@@ -13,7 +13,7 @@
let ratings = $state<typeof data.ratings>([]); let ratings = $state<typeof data.ratings>([]);
let comments = $state<CommentRow[]>([]); let comments = $state<CommentRow[]>([]);
let cookingLog = $state<typeof data.cooking_log>([]); let cookingLog = $state<typeof data.cooking_log>([]);
let isFav = $state(false); let favoriteProfileIds = $state<number[]>([]);
let onWishlist = $state(false); let onWishlist = $state(false);
let newComment = $state(''); let newComment = $state('');
@@ -21,6 +21,7 @@
ratings = [...data.ratings]; ratings = [...data.ratings];
comments = [...data.comments]; comments = [...data.comments];
cookingLog = [...data.cooking_log]; cookingLog = [...data.cooking_log];
favoriteProfileIds = [...data.favorite_profile_ids];
}); });
const myRating = $derived( const myRating = $derived(
@@ -29,14 +30,9 @@
: null : null
); );
async function checkFavorite() { const isFav = $derived(
if (!profileStore.active) { profileStore.active ? favoriteProfileIds.includes(profileStore.active.id) : false
isFav = false; );
return;
}
// Fetch favorite status via list endpoint (quick hack: GET not implemented, infer from no-op)
// Not critical for MVP — we mutate state on toggle.
}
async function setRating(stars: number) { async function setRating(stars: number) {
if (!profileStore.active) { if (!profileStore.active) {
@@ -64,13 +60,16 @@
}); });
return; return;
} }
const profileId = profileStore.active.id;
const method = isFav ? 'DELETE' : 'PUT'; const method = isFav ? 'DELETE' : 'PUT';
await fetch(`/api/recipes/${data.recipe.id}/favorite`, { await fetch(`/api/recipes/${data.recipe.id}/favorite`, {
method, method,
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },
body: JSON.stringify({ profile_id: profileStore.active.id }) body: JSON.stringify({ profile_id: profileId })
}); });
isFav = !isFav; favoriteProfileIds = isFav
? favoriteProfileIds.filter((id) => id !== profileId)
: [...favoriteProfileIds, profileId];
} }
async function logCooked() { async function logCooked() {
@@ -183,7 +182,6 @@
onMount(() => { onMount(() => {
void requestWakeLock(); void requestWakeLock();
void checkFavorite();
void refreshWishlistState(); void refreshWishlistState();
const onVisibility = () => { const onVisibility = () => {
if (document.visibilityState === 'visible' && !wakeLock) void requestWakeLock(); if (document.visibilityState === 'visible' && !wakeLock) void requestWakeLock();