2026-04-17 17:08:22 +02:00
|
|
|
import type { RequestHandler } from './$types';
|
2026-04-18 22:19:12 +02:00
|
|
|
import { json } from '@sveltejs/kit';
|
2026-04-17 17:08:22 +02:00
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { getDb } from '$lib/server/db';
|
2026-04-18 22:19:12 +02:00
|
|
|
import { validateBody } from '$lib/server/api-helpers';
|
2026-04-17 17:08:22 +02:00
|
|
|
import {
|
|
|
|
|
addToWishlist,
|
|
|
|
|
listWishlist,
|
|
|
|
|
type SortKey
|
|
|
|
|
} from '$lib/server/wishlist/repository';
|
|
|
|
|
|
|
|
|
|
const AddSchema = z.object({
|
|
|
|
|
recipe_id: z.number().int().positive(),
|
feat(wishlist): per-user Wünsche + Header-Badge mit Gesamtzahl
Schema-Änderung (Migration 005):
- Tabelle wishlist umgestellt auf PK (recipe_id, profile_id)
- wishlist_like-Tabelle zusammengelegt — Liken WAR schon "will ich auch",
also werden alle bestehenden Likes Memberships auf der neuen Tabelle.
- Alt-Einträge mit added_by_profile_id werden migriert, anonyme gehen
verloren (war inkonsistent, jetzt erzwingen wir profile_id NOT NULL).
Repository:
- listWishlist aggregiert pro Rezept: wanted_by_count, wanted_by_names
(kommagetrennt), on_my_wishlist für das aktive Profil
- listWishlistProfileIds(recipeId) für den Recipe-Page-Loader
- countWishlistRecipes für das Header-Badge (DISTINCT recipe_id)
- addToWishlist/removeFromWishlist/isOnMyWishlist alle mit profile_id
als Pflicht
API:
- POST /api/wishlist: profile_id jetzt Pflicht (nullable raus)
- DELETE /api/wishlist/[recipe_id]?profile_id=X (nur eigenes Entry)
- /api/wishlist/[recipe_id]/like komplett entfernt (Konzept obsolet)
- Neu: GET /api/wishlist/count → { count: <distinct recipes> }
UI:
- Header-Heart bekommt rotes Badge mit Zahl der Wunschliste-Rezepte.
wishlistStore in $lib/client/wishlist.svelte.ts hält den Count reaktiv;
Refresh auf Mount, nach Add/Remove, beim Öffnen der Wunschliste.
- Recipe-Detail: Loader liefert wishlist_profile_ids; onMyWishlist ist
ein $derived. Toggle fragt aktives Profil (alertAction sonst), mutiert
die lokale Liste + ruft wishlistStore.refresh.
- Wunschliste-Seite: Heart toggelt eigenen Wunsch, Count zeigt Gesamt-
wünsche, kommagetrennte Namen zeigen "wer will". Trash-Button
entfernt — Heart-off reicht jetzt.
Tests (99 → 99, 8 neu geschrieben):
- Per-User-Add/Remove, aggregierte Counts, on_my_wishlist, Cascades bei
Recipe/Profile-Delete, countWishlistRecipes = DISTINCT.
2026-04-17 19:16:19 +02:00
|
|
|
profile_id: z.number().int().positive()
|
2026-04-17 17:08:22 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const VALID_SORTS: readonly SortKey[] = ['popular', 'newest', 'oldest'] as const;
|
|
|
|
|
|
|
|
|
|
function parseSort(raw: string | null): SortKey {
|
|
|
|
|
return VALID_SORTS.includes(raw as SortKey) ? (raw as SortKey) : 'popular';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseProfileId(raw: string | null): number | null {
|
|
|
|
|
if (!raw) return null;
|
|
|
|
|
const n = Number(raw);
|
|
|
|
|
return Number.isInteger(n) && n > 0 ? n : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const GET: RequestHandler = async ({ url }) => {
|
|
|
|
|
const sort = parseSort(url.searchParams.get('sort'));
|
|
|
|
|
const profileId = parseProfileId(url.searchParams.get('profile_id'));
|
|
|
|
|
return json({ sort, entries: listWishlist(getDb(), profileId, sort) });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const POST: RequestHandler = async ({ request }) => {
|
2026-04-18 22:19:12 +02:00
|
|
|
const data = validateBody(await request.json().catch(() => null), AddSchema);
|
|
|
|
|
addToWishlist(getDb(), data.recipe_id, data.profile_id);
|
2026-04-17 17:08:22 +02:00
|
|
|
return json({ ok: true }, { status: 201 });
|
|
|
|
|
};
|