diff --git a/src/lib/assets/icons/Heart.svelte b/src/lib/assets/icons/Heart.svelte new file mode 100644 index 0000000..dfd4c1c --- /dev/null +++ b/src/lib/assets/icons/Heart.svelte @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/src/lib/components/FavoriteButton.svelte b/src/lib/components/FavoriteButton.svelte new file mode 100644 index 0000000..9ed997d --- /dev/null +++ b/src/lib/components/FavoriteButton.svelte @@ -0,0 +1,102 @@ + + + + +{#if isLoggedIn} + +{/if} \ No newline at end of file diff --git a/src/models/UserFavorites.ts b/src/models/UserFavorites.ts new file mode 100644 index 0000000..88bd612 --- /dev/null +++ b/src/models/UserFavorites.ts @@ -0,0 +1,11 @@ +import mongoose from 'mongoose'; + +const UserFavoritesSchema = new mongoose.Schema( + { + username: { type: String, required: true, unique: true }, + favorites: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' }] // Recipe MongoDB ObjectIds + }, + { timestamps: true } +); + +export const UserFavorites = mongoose.model("UserFavorites", UserFavoritesSchema); \ No newline at end of file diff --git a/src/routes/api/rezepte/favorites/+server.ts b/src/routes/api/rezepte/favorites/+server.ts new file mode 100644 index 0000000..5d62cd2 --- /dev/null +++ b/src/routes/api/rezepte/favorites/+server.ts @@ -0,0 +1,112 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { UserFavorites } from '../../../../models/UserFavorites'; +import { Recipe } from '../../../../models/Recipe'; +import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { error } from '@sveltejs/kit'; +import mongoose from 'mongoose'; + +export const GET: RequestHandler = async ({ locals }) => { + const session = await locals.auth(); + + if (!session?.user?.nickname) { + throw error(401, 'Authentication required'); + } + + await dbConnect(); + + try { + const userFavorites = await UserFavorites.findOne({ + username: session.user.nickname + }).lean(); + + await dbDisconnect(); + + return json({ + favorites: userFavorites?.favorites || [] + }); + } catch (e) { + await dbDisconnect(); + throw error(500, 'Failed to fetch favorites'); + } +}; + +export const POST: RequestHandler = async ({ request, locals }) => { + const session = await locals.auth(); + + if (!session?.user?.nickname) { + throw error(401, 'Authentication required'); + } + + const { recipeId } = await request.json(); + + if (!recipeId) { + throw error(400, 'Recipe ID required'); + } + + await dbConnect(); + + try { + // Validate that the recipe exists and get its ObjectId + const recipe = await Recipe.findOne({ short_name: recipeId }); + if (!recipe) { + await dbDisconnect(); + throw error(404, 'Recipe not found'); + } + + await UserFavorites.findOneAndUpdate( + { username: session.user.nickname }, + { $addToSet: { favorites: recipe._id } }, + { upsert: true, new: true } + ); + + await dbDisconnect(); + + return json({ success: true }); + } catch (e) { + await dbDisconnect(); + if (e instanceof Error && e.message.includes('404')) { + throw e; + } + throw error(500, 'Failed to add favorite'); + } +}; + +export const DELETE: RequestHandler = async ({ request, locals }) => { + const session = await locals.auth(); + + if (!session?.user?.nickname) { + throw error(401, 'Authentication required'); + } + + const { recipeId } = await request.json(); + + if (!recipeId) { + throw error(400, 'Recipe ID required'); + } + + await dbConnect(); + + try { + // Find the recipe's ObjectId + const recipe = await Recipe.findOne({ short_name: recipeId }); + if (!recipe) { + await dbDisconnect(); + throw error(404, 'Recipe not found'); + } + + await UserFavorites.findOneAndUpdate( + { username: session.user.nickname }, + { $pull: { favorites: recipe._id } } + ); + + await dbDisconnect(); + + return json({ success: true }); + } catch (e) { + await dbDisconnect(); + if (e instanceof Error && e.message.includes('404')) { + throw e; + } + throw error(500, 'Failed to remove favorite'); + } +}; \ No newline at end of file diff --git a/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts b/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts new file mode 100644 index 0000000..06afc8d --- /dev/null +++ b/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts @@ -0,0 +1,42 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { UserFavorites } from '../../../../../../models/UserFavorites'; +import { Recipe } from '../../../../../../models/Recipe'; +import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { error } from '@sveltejs/kit'; + +export const GET: RequestHandler = async ({ locals, params }) => { + const session = await locals.auth(); + + if (!session?.user?.nickname) { + return json({ isFavorite: false }); + } + + await dbConnect(); + + try { + // Find the recipe by short_name to get its ObjectId + const recipe = await Recipe.findOne({ short_name: params.shortName }); + if (!recipe) { + await dbDisconnect(); + throw error(404, 'Recipe not found'); + } + + // Check if this recipe is in the user's favorites + const userFavorites = await UserFavorites.findOne({ + username: session.user.nickname, + favorites: recipe._id + }).lean(); + + await dbDisconnect(); + + return json({ + isFavorite: !!userFavorites + }); + } catch (e) { + await dbDisconnect(); + if (e instanceof Error && e.message.includes('404')) { + throw e; + } + throw error(500, 'Failed to check favorite status'); + } +}; \ No newline at end of file diff --git a/src/routes/api/rezepte/favorites/recipes/+server.ts b/src/routes/api/rezepte/favorites/recipes/+server.ts new file mode 100644 index 0000000..8e5f0c7 --- /dev/null +++ b/src/routes/api/rezepte/favorites/recipes/+server.ts @@ -0,0 +1,40 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { UserFavorites } from '../../../../../models/UserFavorites'; +import { Recipe } from '../../../../../models/Recipe'; +import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import type { RecipeModelType } from '../../../../../types/types'; +import { error } from '@sveltejs/kit'; + +export const GET: RequestHandler = async ({ locals }) => { + const session = await locals.auth(); + + if (!session?.user?.nickname) { + throw error(401, 'Authentication required'); + } + + await dbConnect(); + + try { + const userFavorites = await UserFavorites.findOne({ + username: session.user.nickname + }).lean(); + + if (!userFavorites?.favorites?.length) { + await dbDisconnect(); + return json([]); + } + + let recipes = await Recipe.find({ + _id: { $in: userFavorites.favorites } + }).lean() as RecipeModelType[]; + + await dbDisconnect(); + + recipes = JSON.parse(JSON.stringify(recipes)); + + return json(recipes); + } catch (e) { + await dbDisconnect(); + throw error(500, 'Failed to fetch favorite recipes'); + } +}; \ No newline at end of file diff --git a/src/routes/rezepte/+layout.svelte b/src/routes/rezepte/+layout.svelte index 4c154bc..482c8bf 100644 --- a/src/routes/rezepte/+layout.svelte +++ b/src/routes/rezepte/+layout.svelte @@ -11,6 +11,9 @@ if(data.session){