From 2dc871c50fa50ec7685d6e72519138aafee93619 Mon Sep 17 00:00:00 2001
From: Alexander Bocken
Date: Thu, 4 Sep 2025 17:53:59 +0200
Subject: [PATCH] Implement progressive enhancement for universal search with
context-aware filtering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add comprehensive search solution that works across all recipe pages with proper fallbacks. Features include universal API endpoint, context-aware filtering (category/tag/icon/season/favorites), and progressive enhancement with form submission fallback for no-JS users.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude
---
src/lib/components/IconLayout.svelte | 2 +-
src/lib/components/Search.svelte | 223 +++++++++++-------
src/lib/components/SeasonLayout.svelte | 2 +-
src/routes/api/rezepte/search/+server.ts | 74 ++++++
.../rezepte/category/[category]/+page.svelte | 2 +-
src/routes/rezepte/favorites/+page.svelte | 2 +-
src/routes/rezepte/search/+page.server.ts | 50 ++++
src/routes/rezepte/search/+page.svelte | 75 ++++++
src/routes/rezepte/tag/[tag]/+page.svelte | 2 +-
9 files changed, 348 insertions(+), 84 deletions(-)
create mode 100644 src/routes/api/rezepte/search/+server.ts
create mode 100644 src/routes/rezepte/search/+page.server.ts
create mode 100644 src/routes/rezepte/search/+page.svelte
diff --git a/src/lib/components/IconLayout.svelte b/src/lib/components/IconLayout.svelte
index bdadb69..48d1755 100644
--- a/src/lib/components/IconLayout.svelte
+++ b/src/lib/components/IconLayout.svelte
@@ -72,7 +72,7 @@
{/each}
diff --git a/src/lib/components/Search.svelte b/src/lib/components/Search.svelte
index abc11ec..02424a0 100644
--- a/src/lib/components/Search.svelte
+++ b/src/lib/components/Search.svelte
@@ -1,81 +1,128 @@
-
-
-
- Sucheintrag löschen
-
+
diff --git a/src/lib/components/SeasonLayout.svelte b/src/lib/components/SeasonLayout.svelte
index fdb88b9..cb8b278 100644
--- a/src/lib/components/SeasonLayout.svelte
+++ b/src/lib/components/SeasonLayout.svelte
@@ -41,7 +41,7 @@ a.month:hover,
{/each}
diff --git a/src/routes/api/rezepte/search/+server.ts b/src/routes/api/rezepte/search/+server.ts
new file mode 100644
index 0000000..e2a1391
--- /dev/null
+++ b/src/routes/api/rezepte/search/+server.ts
@@ -0,0 +1,74 @@
+import { json, type RequestHandler } from '@sveltejs/kit';
+import type { BriefRecipeType } from '../../../../types/types';
+import { Recipe } from '../../../../models/Recipe';
+import { dbConnect, dbDisconnect } from '../../../../utils/db';
+
+export const GET: RequestHandler = async ({ url, locals }) => {
+ await dbConnect();
+
+ const query = url.searchParams.get('q')?.toLowerCase().trim() || '';
+ const category = url.searchParams.get('category');
+ const tag = url.searchParams.get('tag');
+ const icon = url.searchParams.get('icon');
+ const season = url.searchParams.get('season');
+ const favoritesOnly = url.searchParams.get('favorites') === 'true';
+
+ try {
+ // Build base query
+ let dbQuery: any = {};
+
+ // Apply filters based on context
+ if (category) {
+ dbQuery.category = category;
+ }
+
+ if (tag) {
+ dbQuery.tags = { $in: [tag] };
+ }
+
+ if (icon) {
+ dbQuery.icon = icon;
+ }
+
+ if (season) {
+ const seasonNum = parseInt(season);
+ if (!isNaN(seasonNum)) {
+ dbQuery.season = { $in: [seasonNum] };
+ }
+ }
+
+ // Get all recipes matching base filters
+ let recipes = await Recipe.find(dbQuery, 'name short_name tags category icon description season dateModified').lean() as BriefRecipeType[];
+
+ // Handle favorites filter
+ if (favoritesOnly && locals.session?.user) {
+ const User = (await import('../../../../models/User')).User;
+ const user = await User.findById(locals.session.user.id);
+ if (user && user.favoriteRecipes) {
+ const favoriteShortNames = user.favoriteRecipes;
+ recipes = recipes.filter(recipe => favoriteShortNames.includes(recipe.short_name));
+ } else {
+ recipes = [];
+ }
+ }
+
+ // Apply text search if query provided
+ if (query) {
+ const searchTerms = query.normalize('NFD').replace(/\p{Diacritic}/gu, "").split(" ");
+
+ recipes = recipes.filter(recipe => {
+ const searchString = `${recipe.name} ${recipe.description || ''} ${recipe.tags?.join(' ') || ''}`.toLowerCase()
+ .normalize('NFD').replace(/\p{Diacritic}/gu, "").replace(/|Â/g, '');
+
+ return searchTerms.every(term => searchString.includes(term));
+ });
+ }
+
+ await dbDisconnect();
+ return json(JSON.parse(JSON.stringify(recipes)));
+
+ } catch (error) {
+ await dbDisconnect();
+ return json({ error: 'Search failed' }, { status: 500 });
+ }
+};
\ No newline at end of file
diff --git a/src/routes/rezepte/category/[category]/+page.svelte b/src/routes/rezepte/category/[category]/+page.svelte
index 53dcb98..d982098 100644
--- a/src/routes/rezepte/category/[category]/+page.svelte
+++ b/src/routes/rezepte/category/[category]/+page.svelte
@@ -14,7 +14,7 @@
}
Rezepte in Kategorie {data.category} :
-
+
{#each rand_array(data.recipes) as recipe}
diff --git a/src/routes/rezepte/favorites/+page.svelte b/src/routes/rezepte/favorites/+page.svelte
index 7873fbf..8906894 100644
--- a/src/routes/rezepte/favorites/+page.svelte
+++ b/src/routes/rezepte/favorites/+page.svelte
@@ -40,7 +40,7 @@ h1{
{/if}
-
+
{#if data.error}
Fehler beim Laden der Favoriten: {data.error}
diff --git a/src/routes/rezepte/search/+page.server.ts b/src/routes/rezepte/search/+page.server.ts
new file mode 100644
index 0000000..a50a2a5
--- /dev/null
+++ b/src/routes/rezepte/search/+page.server.ts
@@ -0,0 +1,50 @@
+import type { PageServerLoad } from './$types';
+
+export const load: PageServerLoad = async ({ url, fetch }) => {
+ const query = url.searchParams.get('q') || '';
+ const category = url.searchParams.get('category');
+ const tag = url.searchParams.get('tag');
+ const icon = url.searchParams.get('icon');
+ const season = url.searchParams.get('season');
+ const favoritesOnly = url.searchParams.get('favorites') === 'true';
+
+ // Build API URL with filters
+ const apiUrl = new URL('/api/rezepte/search', url.origin);
+ if (query) apiUrl.searchParams.set('q', query);
+ if (category) apiUrl.searchParams.set('category', category);
+ if (tag) apiUrl.searchParams.set('tag', tag);
+ if (icon) apiUrl.searchParams.set('icon', icon);
+ if (season) apiUrl.searchParams.set('season', season);
+ if (favoritesOnly) apiUrl.searchParams.set('favorites', 'true');
+
+ try {
+ const response = await fetch(apiUrl.toString());
+ const results = await response.json();
+
+ return {
+ query,
+ results: response.ok ? results : [],
+ error: response.ok ? null : results.error || 'Search failed',
+ filters: {
+ category,
+ tag,
+ icon,
+ season,
+ favoritesOnly
+ }
+ };
+ } catch (error) {
+ return {
+ query,
+ results: [],
+ error: 'Search failed',
+ filters: {
+ category,
+ tag,
+ icon,
+ season,
+ favoritesOnly
+ }
+ };
+ }
+};
\ No newline at end of file
diff --git a/src/routes/rezepte/search/+page.svelte b/src/routes/rezepte/search/+page.svelte
new file mode 100644
index 0000000..7e6bbab
--- /dev/null
+++ b/src/routes/rezepte/search/+page.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+
+ Suchergebnisse{data.query ? ` für "${data.query}"` : ''} - Bocken Rezepte
+
+
+
+Suchergebnisse
+
+{#if data.filters.category || data.filters.tag || data.filters.icon || data.filters.season || data.filters.favoritesOnly}
+
+ Gefiltert nach:
+ {#if data.filters.category}Kategorie "{data.filters.category}"{/if}
+ {#if data.filters.tag}Stichwort "{data.filters.tag}"{/if}
+ {#if data.filters.icon}Icon "{data.filters.icon}"{/if}
+ {#if data.filters.season}Saison "{data.filters.season}"{/if}
+ {#if data.filters.favoritesOnly}Nur Favoriten{/if}
+
+{/if}
+
+
+
+{#if data.error}
+
+
Fehler bei der Suche: {data.error}
+
+{:else if data.query}
+
+
{data.results.length} Ergebnisse für "{data.query}"
+
+{/if}
+
+{#if data.results.length > 0}
+
+ {#each data.results as recipe}
+
+ {/each}
+
+{:else if data.query && !data.error}
+
+
Keine Rezepte gefunden.
+
Versuche es mit anderen Suchbegriffen.
+
+{/if}
\ No newline at end of file
diff --git a/src/routes/rezepte/tag/[tag]/+page.svelte b/src/routes/rezepte/tag/[tag]/+page.svelte
index 6993f55..cc35626 100644
--- a/src/routes/rezepte/tag/[tag]/+page.svelte
+++ b/src/routes/rezepte/tag/[tag]/+page.svelte
@@ -14,7 +14,7 @@
}
Rezepte mit Stichwort {data.tag} :
-
+
{#each rand_array(data.recipes) as recipe}