diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 8a4cf7a..3c19186 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -7,11 +7,21 @@ import { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } from "$env/static/pr import { sequence } from "@sveltejs/kit/hooks" import * as auth from "./auth" import { initializeScheduler } from "./lib/server/scheduler" +import { dbConnect } from "./utils/db" import fs from 'fs' import path from 'path' -// Initialize the recurring payment scheduler -initializeScheduler(); +// Initialize database connection on server startup +console.log('🚀 Server starting - initializing database connection...'); +await dbConnect().then(() => { + console.log('✅ Database connected successfully'); + // Initialize the recurring payment scheduler after DB is ready + initializeScheduler(); + console.log('✅ Recurring payment scheduler initialized'); +}).catch((error) => { + console.error('❌ Failed to connect to database on startup:', error); + // Don't crash the server - API routes will attempt reconnection +}); async function authorization({ event, resolve }) { const session = await event.locals.auth(); diff --git a/src/lib/server/favorites.ts b/src/lib/server/favorites.ts index c251759..8b155d2 100644 --- a/src/lib/server/favorites.ts +++ b/src/lib/server/favorites.ts @@ -24,6 +24,12 @@ export async function getUserFavorites(fetch: any, locals: any): Promise ({ ...recipe, isFavorite: userFavorites.some(favId => favId.toString() === recipe._id.toString()) diff --git a/src/routes/api/cospend/balance/+server.ts b/src/routes/api/cospend/balance/+server.ts index 843ae81..ab2c4b2 100644 --- a/src/routes/api/cospend/balance/+server.ts +++ b/src/routes/api/cospend/balance/+server.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from '@sveltejs/kit'; import { PaymentSplit } from '../../../../models/PaymentSplit'; import { Payment } from '../../../../models/Payment'; // Need to import Payment for populate to work -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import { error, json } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ locals, url }) => { @@ -94,7 +94,5 @@ export const GET: RequestHandler = async ({ locals, url }) => { } catch (e) { console.error('Error calculating balance:', e); throw error(500, 'Failed to calculate balance'); - } finally { - await dbDisconnect(); } }; \ No newline at end of file diff --git a/src/routes/api/cospend/debts/+server.ts b/src/routes/api/cospend/debts/+server.ts index 1741b8f..6fd03e4 100644 --- a/src/routes/api/cospend/debts/+server.ts +++ b/src/routes/api/cospend/debts/+server.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from '@sveltejs/kit'; import { PaymentSplit } from '../../../../models/PaymentSplit'; import { Payment } from '../../../../models/Payment'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import { error, json } from '@sveltejs/kit'; interface DebtSummary { @@ -105,6 +105,6 @@ export const GET: RequestHandler = async ({ locals }) => { console.error('Error calculating debt breakdown:', e); throw error(500, 'Failed to calculate debt breakdown'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/payments/+server.ts b/src/routes/api/cospend/payments/+server.ts index 6c71f5f..bc397e1 100644 --- a/src/routes/api/cospend/payments/+server.ts +++ b/src/routes/api/cospend/payments/+server.ts @@ -1,7 +1,8 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Payment } from '../../../../models/Payment'; import { PaymentSplit } from '../../../../models/PaymentSplit'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; +import { convertToCHF, isValidCurrencyCode } from '../../../../lib/utils/currency'; import { error, json } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ locals, url }) => { @@ -27,7 +28,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { } catch (e) { throw error(500, 'Failed to fetch payments'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -38,7 +39,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { } const data = await request.json(); - const { title, description, amount, paidBy, date, image, category, splitMethod, splits } = data; + const { title, description, amount, currency, paidBy, date, image, category, splitMethod, splits } = data; if (!title || !amount || !paidBy || !splitMethod || !splits) { throw error(400, 'Missing required fields'); @@ -56,6 +57,12 @@ export const POST: RequestHandler = async ({ request, locals }) => { throw error(400, 'Invalid category'); } + // Validate currency if provided + const inputCurrency = currency?.toUpperCase() || 'CHF'; + if (currency && !isValidCurrencyCode(inputCurrency)) { + throw error(400, 'Invalid currency code'); + } + // Validate personal + equal split method if (splitMethod === 'personal_equal' && splits) { const totalPersonal = splits.reduce((sum: number, split: any) => { @@ -67,30 +74,66 @@ export const POST: RequestHandler = async ({ request, locals }) => { } } + const paymentDate = date ? new Date(date) : new Date(); + let finalAmount = amount; + let originalAmount: number | undefined; + let exchangeRate: number | undefined; + + // Convert currency if not CHF + if (inputCurrency !== 'CHF') { + try { + const conversion = await convertToCHF(amount, inputCurrency, paymentDate.toISOString()); + finalAmount = conversion.convertedAmount; + originalAmount = amount; + exchangeRate = conversion.exchangeRate; + } catch (e) { + console.error('Currency conversion error:', e); + throw error(400, `Failed to convert ${inputCurrency} to CHF: ${e.message}`); + } + } + await dbConnect(); try { const payment = await Payment.create({ title, description, - amount, - currency: 'CHF', + amount: finalAmount, + currency: inputCurrency, + originalAmount, + exchangeRate, paidBy, - date: date ? new Date(date) : new Date(), + date: paymentDate, image, category: category || 'groceries', splitMethod, createdBy: auth.user.nickname }); - const splitPromises = splits.map((split: any) => { - return PaymentSplit.create({ + // Convert split amounts to CHF if needed + const convertedSplits = splits.map((split: any) => { + let convertedAmount = split.amount; + let convertedPersonalAmount = split.personalAmount; + + // Convert amounts if we have a foreign currency + if (inputCurrency !== 'CHF' && exchangeRate) { + convertedAmount = split.amount * exchangeRate; + if (split.personalAmount) { + convertedPersonalAmount = split.personalAmount * exchangeRate; + } + } + + return { paymentId: payment._id, username: split.username, - amount: split.amount, + amount: convertedAmount, proportion: split.proportion, - personalAmount: split.personalAmount - }); + personalAmount: convertedPersonalAmount + }; + }); + + const splitPromises = convertedSplits.map((split) => { + return PaymentSplit.create(split); }); await Promise.all(splitPromises); @@ -104,6 +147,6 @@ export const POST: RequestHandler = async ({ request, locals }) => { console.error('Error creating payment:', e); throw error(500, 'Failed to create payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/payments/[id]/+server.ts b/src/routes/api/cospend/payments/[id]/+server.ts index f461286..5296a3b 100644 --- a/src/routes/api/cospend/payments/[id]/+server.ts +++ b/src/routes/api/cospend/payments/[id]/+server.ts @@ -1,7 +1,7 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Payment } from '../../../../../models/Payment'; import { PaymentSplit } from '../../../../../models/PaymentSplit'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { error, json } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ params, locals }) => { @@ -26,7 +26,7 @@ export const GET: RequestHandler = async ({ params, locals }) => { if (e.status === 404) throw e; throw error(500, 'Failed to fetch payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -88,7 +88,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => { if (e.status) throw e; throw error(500, 'Failed to update payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -121,6 +121,6 @@ export const DELETE: RequestHandler = async ({ params, locals }) => { if (e.status) throw e; throw error(500, 'Failed to delete payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/recurring-payments/+server.ts b/src/routes/api/cospend/recurring-payments/+server.ts index 5bb03da..5224cd6 100644 --- a/src/routes/api/cospend/recurring-payments/+server.ts +++ b/src/routes/api/cospend/recurring-payments/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { RecurringPayment } from '../../../../models/RecurringPayment'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import { error, json } from '@sveltejs/kit'; import { calculateNextExecutionDate, validateCronExpression } from '../../../../lib/utils/recurring'; @@ -33,7 +33,7 @@ export const GET: RequestHandler = async ({ locals, url }) => { console.error('Error fetching recurring payments:', e); throw error(500, 'Failed to fetch recurring payments'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -134,6 +134,6 @@ export const POST: RequestHandler = async ({ request, locals }) => { console.error('Error creating recurring payment:', e); throw error(500, 'Failed to create recurring payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/recurring-payments/[id]/+server.ts b/src/routes/api/cospend/recurring-payments/[id]/+server.ts index 141ecff..697467f 100644 --- a/src/routes/api/cospend/recurring-payments/[id]/+server.ts +++ b/src/routes/api/cospend/recurring-payments/[id]/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { RecurringPayment } from '../../../../../models/RecurringPayment'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { error, json } from '@sveltejs/kit'; import { calculateNextExecutionDate, validateCronExpression } from '../../../../../lib/utils/recurring'; import mongoose from 'mongoose'; @@ -30,7 +30,7 @@ export const GET: RequestHandler = async ({ params, locals }) => { console.error('Error fetching recurring payment:', e); throw error(500, 'Failed to fetch recurring payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -149,7 +149,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => { } throw error(500, 'Failed to update recurring payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; @@ -179,6 +179,6 @@ export const DELETE: RequestHandler = async ({ params, locals }) => { console.error('Error deleting recurring payment:', e); throw error(500, 'Failed to delete recurring payment'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/recurring-payments/cron-execute/+server.ts b/src/routes/api/cospend/recurring-payments/cron-execute/+server.ts index e80775e..835b088 100644 --- a/src/routes/api/cospend/recurring-payments/cron-execute/+server.ts +++ b/src/routes/api/cospend/recurring-payments/cron-execute/+server.ts @@ -2,7 +2,7 @@ import type { RequestHandler } from '@sveltejs/kit'; import { RecurringPayment } from '../../../../../models/RecurringPayment'; import { Payment } from '../../../../../models/Payment'; import { PaymentSplit } from '../../../../../models/PaymentSplit'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { error, json } from '@sveltejs/kit'; import { calculateNextExecutionDate } from '../../../../../lib/utils/recurring'; @@ -119,6 +119,6 @@ export const POST: RequestHandler = async ({ request }) => { console.error('[Cron] Error executing recurring payments:', e); throw error(500, 'Failed to execute recurring payments'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/cospend/recurring-payments/execute/+server.ts b/src/routes/api/cospend/recurring-payments/execute/+server.ts index bf6f114..34ef135 100644 --- a/src/routes/api/cospend/recurring-payments/execute/+server.ts +++ b/src/routes/api/cospend/recurring-payments/execute/+server.ts @@ -2,9 +2,10 @@ import type { RequestHandler } from '@sveltejs/kit'; import { RecurringPayment } from '../../../../../models/RecurringPayment'; import { Payment } from '../../../../../models/Payment'; import { PaymentSplit } from '../../../../../models/PaymentSplit'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { error, json } from '@sveltejs/kit'; import { calculateNextExecutionDate } from '../../../../../lib/utils/recurring'; +import { convertToCHF } from '../../../../../lib/utils/currency'; export const POST: RequestHandler = async ({ locals }) => { const auth = await locals.auth(); @@ -32,12 +33,35 @@ export const POST: RequestHandler = async ({ locals }) => { for (const recurringPayment of duePayments) { try { + // Handle currency conversion for execution date + let finalAmount = recurringPayment.amount; + let originalAmount: number | undefined; + let exchangeRate: number | undefined; + + if (recurringPayment.currency !== 'CHF') { + try { + const conversion = await convertToCHF( + recurringPayment.amount, + recurringPayment.currency, + now.toISOString() + ); + finalAmount = conversion.convertedAmount; + originalAmount = recurringPayment.amount; + exchangeRate = conversion.exchangeRate; + } catch (conversionError) { + console.error(`Currency conversion failed for recurring payment ${recurringPayment._id}:`, conversionError); + // Continue with original amount if conversion fails + } + } + // Create the payment const payment = await Payment.create({ title: recurringPayment.title, description: recurringPayment.description, - amount: recurringPayment.amount, + amount: finalAmount, currency: recurringPayment.currency, + originalAmount, + exchangeRate, paidBy: recurringPayment.paidBy, date: now, category: recurringPayment.category, @@ -45,15 +69,31 @@ export const POST: RequestHandler = async ({ locals }) => { createdBy: recurringPayment.createdBy }); - // Create payment splits - const splitPromises = recurringPayment.splits.map((split) => { - return PaymentSplit.create({ + // Convert split amounts to CHF if needed + const convertedSplits = recurringPayment.splits.map((split) => { + let convertedAmount = split.amount || 0; + let convertedPersonalAmount = split.personalAmount; + + // Convert amounts if we have a foreign currency and exchange rate + if (recurringPayment.currency !== 'CHF' && exchangeRate && split.amount) { + convertedAmount = split.amount * exchangeRate; + if (split.personalAmount) { + convertedPersonalAmount = split.personalAmount * exchangeRate; + } + } + + return { paymentId: payment._id, username: split.username, - amount: split.amount, + amount: convertedAmount, proportion: split.proportion, - personalAmount: split.personalAmount - }); + personalAmount: convertedPersonalAmount + }; + }); + + // Create payment splits + const splitPromises = convertedSplits.map((split) => { + return PaymentSplit.create(split); }); await Promise.all(splitPromises); @@ -99,6 +139,6 @@ export const POST: RequestHandler = async ({ locals }) => { console.error('Error executing recurring payments:', e); throw error(500, 'Failed to execute recurring payments'); } finally { - await dbDisconnect(); + // Connection will be reused } }; \ No newline at end of file diff --git a/src/routes/api/rezepte/add/+server.ts b/src/routes/api/rezepte/add/+server.ts index 1324489..e77c138 100644 --- a/src/routes/api/rezepte/add/+server.ts +++ b/src/routes/api/rezepte/add/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import { error } from '@sveltejs/kit'; // header: use for bearer token for now // recipe json in body @@ -22,7 +22,6 @@ export const POST: RequestHandler = async ({request, cookies, locals}) => { } catch(e){ throw error(400, e) } - await dbDisconnect(); return new Response(JSON.stringify({msg: "Added recipe successfully"}),{ status: 200, }); diff --git a/src/routes/api/rezepte/delete/+server.ts b/src/routes/api/rezepte/delete/+server.ts index ecc02a9..758892d 100644 --- a/src/routes/api/rezepte/delete/+server.ts +++ b/src/routes/api/rezepte/delete/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import type {RecipeModelType} from '../../../../types/types'; import { error } from '@sveltejs/kit'; // header: use for bearer token for now @@ -14,7 +14,6 @@ export const POST: RequestHandler = async ({request, locals}) => { const short_name = message.old_short_name await dbConnect(); await Recipe.findOneAndDelete({short_name: short_name}); - await dbDisconnect(); return new Response(JSON.stringify({msg: "Deleted recipe successfully"}),{ status: 200, }); diff --git a/src/routes/api/rezepte/edit/+server.ts b/src/routes/api/rezepte/edit/+server.ts index 330b622..a52162d 100644 --- a/src/routes/api/rezepte/edit/+server.ts +++ b/src/routes/api/rezepte/edit/+server.ts @@ -1,6 +1,6 @@ import type { RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import type {RecipeModelType} from '../../../../types/types'; import { error } from '@sveltejs/kit'; // header: use for bearer token for now @@ -15,7 +15,6 @@ export const POST: RequestHandler = async ({request, locals}) => { else{ await dbConnect(); await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json); - await dbDisconnect(); return new Response(JSON.stringify({msg: "Edited recipe successfully"}),{ status: 200, }); diff --git a/src/routes/api/rezepte/favorites/+server.ts b/src/routes/api/rezepte/favorites/+server.ts index 5d62cd2..63e9cb5 100644 --- a/src/routes/api/rezepte/favorites/+server.ts +++ b/src/routes/api/rezepte/favorites/+server.ts @@ -1,7 +1,7 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { UserFavorites } from '../../../../models/UserFavorites'; import { Recipe } from '../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; import { error } from '@sveltejs/kit'; import mongoose from 'mongoose'; @@ -19,13 +19,11 @@ export const GET: RequestHandler = async ({ locals }) => { username: session.user.nickname }).lean(); - await dbDisconnect(); return json({ favorites: userFavorites?.favorites || [] }); } catch (e) { - await dbDisconnect(); throw error(500, 'Failed to fetch favorites'); } }; @@ -49,8 +47,7 @@ export const POST: RequestHandler = async ({ request, locals }) => { // 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'); + throw error(404, 'Recipe not found'); } await UserFavorites.findOneAndUpdate( @@ -59,11 +56,9 @@ export const POST: RequestHandler = async ({ request, locals }) => { { upsert: true, new: true } ); - await dbDisconnect(); return json({ success: true }); } catch (e) { - await dbDisconnect(); if (e instanceof Error && e.message.includes('404')) { throw e; } @@ -90,8 +85,7 @@ export const DELETE: RequestHandler = async ({ request, locals }) => { // Find the recipe's ObjectId const recipe = await Recipe.findOne({ short_name: recipeId }); if (!recipe) { - await dbDisconnect(); - throw error(404, 'Recipe not found'); + throw error(404, 'Recipe not found'); } await UserFavorites.findOneAndUpdate( @@ -99,11 +93,9 @@ export const DELETE: RequestHandler = async ({ request, locals }) => { { $pull: { favorites: recipe._id } } ); - await dbDisconnect(); return json({ success: true }); } catch (e) { - await dbDisconnect(); if (e instanceof Error && e.message.includes('404')) { throw e; } diff --git a/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts b/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts index 06afc8d..a152dff 100644 --- a/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts +++ b/src/routes/api/rezepte/favorites/check/[shortName]/+server.ts @@ -1,7 +1,7 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { UserFavorites } from '../../../../../../models/UserFavorites'; import { Recipe } from '../../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { dbConnect } from '../../../../../../utils/db'; import { error } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ locals, params }) => { @@ -17,7 +17,6 @@ export const GET: RequestHandler = async ({ locals, params }) => { // 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'); } @@ -27,13 +26,11 @@ export const GET: RequestHandler = async ({ locals, params }) => { favorites: recipe._id }).lean(); - await dbDisconnect(); return json({ isFavorite: !!userFavorites }); } catch (e) { - await dbDisconnect(); if (e instanceof Error && e.message.includes('404')) { throw e; } diff --git a/src/routes/api/rezepte/favorites/recipes/+server.ts b/src/routes/api/rezepte/favorites/recipes/+server.ts index 8e5f0c7..b0c25a5 100644 --- a/src/routes/api/rezepte/favorites/recipes/+server.ts +++ b/src/routes/api/rezepte/favorites/recipes/+server.ts @@ -1,7 +1,7 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { UserFavorites } from '../../../../../models/UserFavorites'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import type { RecipeModelType } from '../../../../../types/types'; import { error } from '@sveltejs/kit'; @@ -20,7 +20,6 @@ export const GET: RequestHandler = async ({ locals }) => { }).lean(); if (!userFavorites?.favorites?.length) { - await dbDisconnect(); return json([]); } @@ -28,13 +27,11 @@ export const GET: RequestHandler = async ({ locals }) => { _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/api/rezepte/items/[name]/+server.ts b/src/routes/api/rezepte/items/[name]/+server.ts index cd3652c..19c81f4 100644 --- a/src/routes/api/rezepte/items/[name]/+server.ts +++ b/src/routes/api/rezepte/items/[name]/+server.ts @@ -1,13 +1,12 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import type {RecipeModelType} from '../../../../../types/types'; import { error } from '@sveltejs/kit'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let recipe = (await Recipe.findOne({ short_name: params.name}).lean()) as RecipeModelType[]; - await dbDisconnect(); recipe = JSON.parse(JSON.stringify(recipe)); if(recipe == null){ diff --git a/src/routes/api/rezepte/items/all_brief/+server.ts b/src/routes/api/rezepte/items/all_brief/+server.ts index 59ab008..12da43a 100644 --- a/src/routes/api/rezepte/items/all_brief/+server.ts +++ b/src/routes/api/rezepte/items/all_brief/+server.ts @@ -1,12 +1,11 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import type { BriefRecipeType } from '../../../../../types/types'; import { Recipe } from '../../../../../models/Recipe' -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { rand_array } from '$lib/js/randomize'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let found_brief = rand_array(await Recipe.find({}, 'name short_name tags category icon description season dateModified').lean()) as BriefRecipeType[]; - await dbDisconnect(); return json(JSON.parse(JSON.stringify(found_brief))); }; diff --git a/src/routes/api/rezepte/items/category/+server.ts b/src/routes/api/rezepte/items/category/+server.ts index 6fe5113..66da78c 100644 --- a/src/routes/api/rezepte/items/category/+server.ts +++ b/src/routes/api/rezepte/items/category/+server.ts @@ -1,12 +1,11 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../types/types'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let categories = (await Recipe.distinct('category').lean()); - await dbDisconnect(); categories= JSON.parse(JSON.stringify(categories)); return json(categories); diff --git a/src/routes/api/rezepte/items/category/[category]/+server.ts b/src/routes/api/rezepte/items/category/[category]/+server.ts index 0b4b384..e47bb5d 100644 --- a/src/routes/api/rezepte/items/category/[category]/+server.ts +++ b/src/routes/api/rezepte/items/category/[category]/+server.ts @@ -1,13 +1,12 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { dbConnect } from '../../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../../types/types'; import { rand_array } from '$lib/js/randomize'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let recipes = rand_array(await Recipe.find({category: params.category}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[]; - await dbDisconnect(); recipes = JSON.parse(JSON.stringify(recipes)); return json(recipes); diff --git a/src/routes/api/rezepte/items/icon/+server.ts b/src/routes/api/rezepte/items/icon/+server.ts index f82ac7f..ffb77df 100644 --- a/src/routes/api/rezepte/items/icon/+server.ts +++ b/src/routes/api/rezepte/items/icon/+server.ts @@ -1,12 +1,11 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../types/types'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let icons = (await Recipe.distinct('icon').lean()); - await dbDisconnect(); icons = JSON.parse(JSON.stringify(icons)); return json(icons); diff --git a/src/routes/api/rezepte/items/icon/[icon]/+server.ts b/src/routes/api/rezepte/items/icon/[icon]/+server.ts index 5cfa256..e03f942 100644 --- a/src/routes/api/rezepte/items/icon/[icon]/+server.ts +++ b/src/routes/api/rezepte/items/icon/[icon]/+server.ts @@ -1,13 +1,12 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { dbConnect } from '../../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../../types/types'; import { rand_array } from '$lib/js/randomize'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let recipes = rand_array(await Recipe.find({icon: params.icon}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[]; - await dbDisconnect(); recipes = JSON.parse(JSON.stringify(recipes)); return json(recipes); diff --git a/src/routes/api/rezepte/items/in_season/[month]/+server.ts b/src/routes/api/rezepte/items/in_season/[month]/+server.ts index 4bb2ea5..1a7a4c0 100644 --- a/src/routes/api/rezepte/items/in_season/[month]/+server.ts +++ b/src/routes/api/rezepte/items/in_season/[month]/+server.ts @@ -1,13 +1,12 @@ import type {rand_array} from '$lib/js/randomize'; import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../../models/Recipe' -import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { dbConnect } from '../../../../../../utils/db'; import { rand_array } from '$lib/js/randomize'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let found_in_season = rand_array(await Recipe.find({season: params.month, icon: {$ne: "🍽️"}}, 'name short_name images tags category icon description season dateModified').lean()); - await dbDisconnect(); found_in_season = JSON.parse(JSON.stringify(found_in_season)); return json(found_in_season); }; diff --git a/src/routes/api/rezepte/items/tag/+server.ts b/src/routes/api/rezepte/items/tag/+server.ts index 258ce6f..061b1f8 100644 --- a/src/routes/api/rezepte/items/tag/+server.ts +++ b/src/routes/api/rezepte/items/tag/+server.ts @@ -1,12 +1,11 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../types/types'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let categories = (await Recipe.distinct('tags').lean()); - await dbDisconnect(); categories= JSON.parse(JSON.stringify(categories)); return json(categories); diff --git a/src/routes/api/rezepte/items/tag/[tag]/+server.ts b/src/routes/api/rezepte/items/tag/[tag]/+server.ts index 7fe9bb1..1a6ccdf 100644 --- a/src/routes/api/rezepte/items/tag/[tag]/+server.ts +++ b/src/routes/api/rezepte/items/tag/[tag]/+server.ts @@ -1,13 +1,12 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../../utils/db'; +import { dbConnect } from '../../../../../../utils/db'; import type {BriefRecipeType} from '../../../../../../types/types'; import { rand_array } from '$lib/js/randomize'; export const GET: RequestHandler = async ({params}) => { await dbConnect(); let recipes = rand_array(await Recipe.find({tags: params.tag}, 'name short_name images tags category icon description season dateModified').lean()) as BriefRecipeType[]; - await dbDisconnect(); recipes = JSON.parse(JSON.stringify(recipes)); return json(recipes); diff --git a/src/routes/api/rezepte/json-ld/[name]/+server.ts b/src/routes/api/rezepte/json-ld/[name]/+server.ts index 03fe60c..f221f3d 100644 --- a/src/routes/api/rezepte/json-ld/[name]/+server.ts +++ b/src/routes/api/rezepte/json-ld/[name]/+server.ts @@ -1,6 +1,6 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import { Recipe } from '../../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../../utils/db'; +import { dbConnect } from '../../../../../utils/db'; import { generateRecipeJsonLd } from '$lib/js/recipeJsonLd'; import type { RecipeModelType } from '../../../../../types/types'; import { error } from '@sveltejs/kit'; @@ -8,7 +8,6 @@ import { error } from '@sveltejs/kit'; export const GET: RequestHandler = async ({ params, setHeaders }) => { await dbConnect(); let recipe = (await Recipe.findOne({ short_name: params.name }).lean()) as RecipeModelType; - await dbDisconnect(); recipe = JSON.parse(JSON.stringify(recipe)); if (recipe == null) { diff --git a/src/routes/api/rezepte/search/+server.ts b/src/routes/api/rezepte/search/+server.ts index 4886a40..f5a1429 100644 --- a/src/routes/api/rezepte/search/+server.ts +++ b/src/routes/api/rezepte/search/+server.ts @@ -1,7 +1,7 @@ import { json, type RequestHandler } from '@sveltejs/kit'; import type { BriefRecipeType } from '../../../../types/types'; import { Recipe } from '../../../../models/Recipe'; -import { dbConnect, dbDisconnect } from '../../../../utils/db'; +import { dbConnect } from '../../../../utils/db'; export const GET: RequestHandler = async ({ url, locals }) => { await dbConnect(); @@ -64,11 +64,9 @@ export const GET: RequestHandler = async ({ url, locals }) => { }); } - 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/utils/db.ts b/src/utils/db.ts index 3eb4f97..783d3ac 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -1,35 +1,54 @@ import mongoose from 'mongoose'; import { MONGO_URL } from '$env/static/private'; -/* - 0 - disconnected - 1 - connected - 2 - connecting - 3 - disconnecting - 4 - uninitialized -*/ -const mongoConnection = { - isConnected: 0, -}; + +let isConnected = false; export const dbConnect = async () => { - if (mongoConnection.isConnected === 1) { - return; + // If already connected, return immediately + if (isConnected && mongoose.connection.readyState === 1) { + return mongoose.connection; } - if (mongoose.connections.length > 0) { - mongoConnection.isConnected = mongoose.connections[0].readyState; - if (mongoConnection.isConnected === 1) { - return; - } + try { + // Configure MongoDB driver options + const options = { + maxPoolSize: 10, // Maintain up to 10 socket connections + serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds + socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity + }; - await mongoose.disconnect(); + const connection = await mongoose.connect(MONGO_URL ?? '', options); + + isConnected = true; + console.log('MongoDB connected with persistent connection'); + + // Handle connection events + mongoose.connection.on('error', (err) => { + console.error('MongoDB connection error:', err); + isConnected = false; + }); + + mongoose.connection.on('disconnected', () => { + console.log('MongoDB disconnected'); + isConnected = false; + }); + + mongoose.connection.on('reconnected', () => { + console.log('MongoDB reconnected'); + isConnected = true; + }); + + return connection; + } catch (error) { + console.error('MongoDB connection failed:', error); + isConnected = false; + throw error; } - await mongoose.connect(MONGO_URL ?? ''); - mongoConnection.isConnected = 1; }; +// No longer disconnect - let the connection pool manage connections export const dbDisconnect = async () => { - // Don't disconnect in production to avoid "Client must be connected" errors - // The connection pool will handle connection cleanup automatically + // Keep connections persistent for performance and to avoid race conditions + // MongoDB driver will handle connection pooling and cleanup automatically return; };