Files
homepage/src/routes/api/recipes/search/+server.ts
T
Alexander 9e7ab0b16f refactor: reorganize components into domain subfolders and replace relative imports
Move components from flat src/lib/components/ into recipes/, faith/, and
cospend/ subdirectories. Replace ~144 relative imports across API routes
and lib files with $models, $utils, $types, and $lib aliases. Add $types
alias to svelte.config.js. Remove unused EditRecipe.svelte.
2026-02-11 09:49:11 +01:00

102 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { json, type RequestHandler } from '@sveltejs/kit';
import type { BriefRecipeType } from '$types/types';
import { Recipe } from '$models/Recipe';
import { dbConnect } 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');
// Support both single tag (backwards compat) and multiple tags
const singleTag = url.searchParams.get('tag');
const multipleTags = url.searchParams.get('tags');
const tags = multipleTags
? multipleTags.split(',').map(t => t.trim()).filter(Boolean)
: (singleTag ? [singleTag] : []);
const icon = url.searchParams.get('icon');
// Support both single season (backwards compat) and multiple seasons
const singleSeason = url.searchParams.get('season');
const multipleSeasons = url.searchParams.get('seasons');
const seasons = multipleSeasons
? multipleSeasons.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n))
: (singleSeason ? [parseInt(singleSeason)].filter(n => !isNaN(n)) : []);
const favoritesOnly = url.searchParams.get('favorites') === 'true';
try {
// Build base query - only recipes with approved English translations
let dbQuery: any = {
'translations.en.translationStatus': 'approved'
};
// Apply filters based on context
if (category) {
dbQuery['translations.en.category'] = category;
}
// Multi-tag AND logic: recipe must have ALL selected tags
if (tags.length > 0) {
dbQuery['translations.en.tags'] = { $all: tags };
}
if (icon) {
dbQuery.icon = icon; // Icon is the same for both languages
}
// Multi-season OR logic: recipe in any selected season
if (seasons.length > 0) {
dbQuery.season = { $in: seasons }; // Season is the same for both languages
}
// Get all recipes matching base filters
let recipes = await Recipe.find(dbQuery).lean();
// Handle favorites filter
if (favoritesOnly && locals.session?.user) {
const { UserFavorites } = await import('../../../../models/UserFavorites');
const userFavorites = await UserFavorites.findOne({ username: locals.session.user.username });
if (userFavorites && userFavorites.favorites) {
const favoriteIds = userFavorites.favorites;
recipes = recipes.filter(recipe => favoriteIds.some(id => id.toString() === recipe._id?.toString()));
} else {
recipes = [];
}
}
// Transform to English brief format
let briefRecipes: BriefRecipeType[] = recipes.map(recipe => ({
_id: recipe._id,
name: recipe.translations.en.name,
short_name: recipe.translations.en.short_name,
tags: recipe.translations.en.tags || [],
category: recipe.translations.en.category,
icon: recipe.icon,
description: recipe.translations.en.description,
season: recipe.season,
dateModified: recipe.dateModified,
germanShortName: recipe.short_name
}));
// Apply text search if query provided
if (query) {
const searchTerms = query.normalize('NFD').replace(/\p{Diacritic}/gu, "").split(" ");
briefRecipes = briefRecipes.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));
});
}
return json(JSON.parse(JSON.stringify(briefRecipes)));
} catch (error) {
return json({ error: 'Search failed' }, { status: 500 });
}
};