Implement secure client-side favorites loading to fix nginx 502 issues
All checks were successful
CI / update (push) Successful in 16s
All checks were successful
CI / update (push) Successful in 16s
- Create client-side favorites store with secure authentication - Remove server-side favorites fetching that caused nginx routing issues - Update FavoriteButton to properly handle short_name/ObjectId relationship - Use existing /api/rezepte/favorites/check endpoint for status checking - Maintain security by requiring authentication for all favorites operations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,30 +1,28 @@
|
||||
<script lang="ts">
|
||||
export let recipeId: string;
|
||||
export let isFavorite: boolean = false;
|
||||
import { favorites } from '$lib/stores/favorites';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let recipeId: string; // This will be short_name from the component usage
|
||||
export let isFavorite: boolean = false; // Initial state from server
|
||||
export let isLoggedIn: boolean = false;
|
||||
|
||||
let currentIsFavorite = isFavorite;
|
||||
let isLoading = false;
|
||||
|
||||
// Load current favorite status when component mounts
|
||||
onMount(async () => {
|
||||
if (isLoggedIn) {
|
||||
currentIsFavorite = await favorites.isFavoriteByShortName(recipeId);
|
||||
}
|
||||
});
|
||||
|
||||
async function toggleFavorite() {
|
||||
if (!isLoggedIn || isLoading) return;
|
||||
|
||||
isLoading = true;
|
||||
|
||||
try {
|
||||
const method = isFavorite ? 'DELETE' : 'POST';
|
||||
const response = await fetch('/api/rezepte/favorites', {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ recipeId }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
isFavorite = !isFavorite;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle favorite:', error);
|
||||
await favorites.toggle(recipeId);
|
||||
currentIsFavorite = !currentIsFavorite;
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
@@ -59,8 +57,8 @@
|
||||
class="favorite-button"
|
||||
disabled={isLoading}
|
||||
on:click={toggleFavorite}
|
||||
title={isFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||
title={currentIsFavorite ? 'Favorit entfernen' : 'Als Favorit speichern'}
|
||||
>
|
||||
{isFavorite ? '❤️' : '🖤'}
|
||||
{currentIsFavorite ? '❤️' : '🖤'}
|
||||
</button>
|
||||
{/if}
|
156
src/lib/stores/favorites.ts
Normal file
156
src/lib/stores/favorites.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export interface FavoritesState {
|
||||
favorites: string[]; // Array of ObjectIds
|
||||
shortNameToObjectId: Map<string, string>; // Mapping from short_name to ObjectId
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Create the favorites store
|
||||
function createFavoritesStore() {
|
||||
const { subscribe, set, update } = writable<FavoritesState>({
|
||||
favorites: [],
|
||||
shortNameToObjectId: new Map(),
|
||||
loading: false,
|
||||
error: null
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
// Load user's favorites from API
|
||||
async load() {
|
||||
update(state => ({ ...state, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/rezepte/favorites', {
|
||||
credentials: 'include' // Ensure cookies are sent for authentication
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
update(state => ({
|
||||
...state,
|
||||
favorites: data.favorites || [],
|
||||
loading: false
|
||||
}));
|
||||
} else if (response.status === 401) {
|
||||
// User not authenticated, clear favorites
|
||||
update(state => ({
|
||||
...state,
|
||||
favorites: [],
|
||||
loading: false
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Failed to load favorites: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading favorites:', error);
|
||||
update(state => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to load favorites'
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// Add a recipe to favorites
|
||||
async add(recipeShortName: string) {
|
||||
try {
|
||||
const response = await fetch('/api/rezepte/favorites', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ recipeId: recipeShortName }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload favorites to get the updated list with ObjectIds
|
||||
await this.load();
|
||||
} else {
|
||||
throw new Error(`Failed to add favorite: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding favorite:', error);
|
||||
update(state => ({
|
||||
...state,
|
||||
error: error instanceof Error ? error.message : 'Failed to add favorite'
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// Remove a recipe from favorites
|
||||
async remove(recipeShortName: string) {
|
||||
try {
|
||||
const response = await fetch('/api/rezepte/favorites', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ recipeId: recipeShortName }),
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Reload favorites to get the updated list
|
||||
await this.load();
|
||||
} else {
|
||||
throw new Error(`Failed to remove favorite: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error removing favorite:', error);
|
||||
update(state => ({
|
||||
...state,
|
||||
error: error instanceof Error ? error.message : 'Failed to remove favorite'
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// Toggle favorite status by short_name
|
||||
async toggle(recipeShortName: string) {
|
||||
// Check if favorited by checking the current recipe list for this short_name
|
||||
const response = await fetch(`/api/rezepte/favorites/check/${recipeShortName}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const { isFavorite } = await response.json();
|
||||
if (isFavorite) {
|
||||
await this.remove(recipeShortName);
|
||||
} else {
|
||||
await this.add(recipeShortName);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Check if a recipe is favorited by short_name
|
||||
async isFavoriteByShortName(shortName: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`/api/rezepte/favorites/check/${shortName}`, {
|
||||
credentials: 'include'
|
||||
});
|
||||
if (response.ok) {
|
||||
const { isFavorite } = await response.json();
|
||||
return isFavorite;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking favorite status:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const favorites = createFavoritesStore();
|
||||
|
||||
// Helper function to add favorite status to recipes on client-side
|
||||
export function addFavoriteStatusToRecipes(recipes: any[], userFavorites: string[]): any[] {
|
||||
return recipes.map(recipe => ({
|
||||
...recipe,
|
||||
isFavorite: userFavorites.includes(recipe._id?.toString() || recipe.short_name)
|
||||
}));
|
||||
}
|
Reference in New Issue
Block a user