feat: major dependency upgrades, remove Redis, fix mongoose 9 types

Dependencies upgraded:
- svelte 5.38→5.55, @sveltejs/kit 2.37→2.56, adapter-node 5.3→5.5
- mongoose 8→9, sharp 0.33→0.34, typescript 5→6
- lucide-svelte → @lucide/svelte 1.7 (Svelte 5 native package)
- vite 7→8 with rolldown (build time 33s→14s)
- Removed terser (esbuild/oxc default minifier is 20-100x faster)

Infrastructure:
- Removed Redis/ioredis cache layer — MongoDB handles caching natively
- Deleted src/lib/server/cache.ts and all cache.get/set/invalidate usage
- Removed redis-cli from deploy workflow, Redis env vars from .env.example

Mongoose 9 migration:
- Replaced deprecated `new: true` with `returnDocument: 'after'` (16 files)
- Fixed strict query filter types for ObjectId/paymentId fields
- Fixed season param type (string→number) in recipe API
- Removed unused @ts-expect-error in WorkoutSession model
This commit is contained in:
2026-04-06 12:20:59 +02:00
parent 6f53fe3b7b
commit 1fa2e350d7
68 changed files with 981 additions and 1743 deletions
-4
View File
@@ -1,10 +1,6 @@
# Database Configuration
MONGO_URL="mongodb://user:password@host:port/database?authSource=admin"
# Redis Cache Configuration (optional - falls back to direct DB queries if unavailable)
REDIS_HOST="localhost" # Redis server hostname
REDIS_PORT="6379" # Redis server port
# Authentication Secrets (runtime only - not embedded in build)
AUTHENTIK_ID="your-authentik-client-id"
AUTHENTIK_SECRET="your-authentik-client-secret"
-1
View File
@@ -33,7 +33,6 @@ jobs:
git reset --hard origin/master
pnpm install --frozen-lockfile
pnpm run build
redis-cli KEYS 'recipes:*' | xargs -r redis-cli DEL
sudo systemctl stop homepage.service
mkdir -p dist
rm -rf dist/*
+18 -20
View File
@@ -25,43 +25,41 @@
"packageManager": "pnpm@9.0.0",
"devDependencies": {
"@playwright/test": "1.56.1",
"@sveltejs/adapter-auto": "^6.1.0",
"@sveltejs/kit": "^2.37.0",
"@sveltejs/vite-plugin-svelte": "^6.1.3",
"@sveltejs/adapter-auto": "^7.0.1",
"@sveltejs/kit": "^2.56.1",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tauri-apps/cli": "^2.10.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.2.9",
"@testing-library/svelte": "^5.3.1",
"@types/leaflet": "^1.9.21",
"@types/node": "^22.12.0",
"@types/node-cron": "^3.0.11",
"@vitest/ui": "^4.0.10",
"@vitest/ui": "^4.1.2",
"jsdom": "^27.2.0",
"svelte": "^5.38.6",
"svelte-check": "^4.0.0",
"terser": "^5.46.0",
"tslib": "^2.6.0",
"typescript": "^5.1.6",
"vite": "^7.1.3",
"vite-node": "^5.3.0",
"vitest": "^4.0.10"
"svelte": "^5.55.1",
"svelte-check": "^4.4.6",
"tslib": "^2.8.1",
"typescript": "^6.0.2",
"vite": "^8.0.4",
"vite-node": "^6.0.0",
"vitest": "^4.1.2"
},
"dependencies": {
"@auth/sveltekit": "^1.11.1",
"@huggingface/transformers": "^4.0.0",
"@huggingface/transformers": "^4.0.1",
"@lucide/svelte": "^1.7.0",
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
"@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/adapter-node": "^5.5.4",
"@tauri-apps/plugin-geolocation": "^2.3.2",
"barcode-detector": "^3.1.2",
"chart.js": "^4.5.0",
"chart.js": "^4.5.1",
"chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^4.1.0",
"file-type": "^19.0.0",
"ioredis": "^5.9.0",
"leaflet": "^1.9.4",
"lucide-svelte": "^0.575.0",
"mongoose": "^8.0.0",
"mongoose": "^9.4.1",
"node-cron": "^4.2.1",
"sharp": "^0.33.0"
"sharp": "^0.34.5"
},
"pnpm": {
"onlyBuiltDependencies": [
+871 -1123
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -144,7 +144,7 @@ dependencies = [
[[package]]
name = "bocken"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"serde",
"serde_json",
+1 -1
View File
@@ -1,6 +1,6 @@
<script>
import { themeStore } from '$lib/stores/theme.svelte';
import { Sun, Moon, SunMoon } from 'lucide-svelte';
import { Sun, Moon, SunMoon } from '@lucide/svelte';
</script>
<style>
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import { X } from 'lucide-svelte';
import { X } from '@lucide/svelte';
import { getToasts } from '$lib/js/toast.svelte';
const toasts = getToasts();
@@ -2,7 +2,7 @@
import { browser } from '$app/environment';
import { getAngelusStreak, getCurrentTimeSlot, type TimeSlot } from '$lib/stores/angelusStreak.svelte';
import StreakAura from '$lib/components/faith/StreakAura.svelte';
import { Coffee, Sun, Moon } from 'lucide-svelte';
import { Coffee, Sun, Moon } from '@lucide/svelte';
import { tick, onMount } from 'svelte';
let burst = $state(false);
@@ -1,6 +1,6 @@
<script>
import { getFilterOptions, searchExercises, translateTerm } from '$lib/data/exercises';
import { Search, X } from 'lucide-svelte';
import { Search, X } from '@lucide/svelte';
import { page } from '$app/stores';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
+1 -1
View File
@@ -1,7 +1,7 @@
<script>
import { page } from '$app/stores';
import { browser } from '$app/environment';
import { Heart, ExternalLink, ScanBarcode, X } from 'lucide-svelte';
import { Heart, ExternalLink, ScanBarcode, X } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
/**
@@ -1,7 +1,7 @@
<script>
import { page } from '$app/stores';
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
import { Clock, Weight, Trophy, Route, Gauge, Flame } from 'lucide-svelte';
import { Clock, Weight, Trophy, Route, Gauge, Flame } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
+1 -1
View File
@@ -1,5 +1,5 @@
<script>
import { Check, X } from 'lucide-svelte';
import { Check, X } from '@lucide/svelte';
import { METRIC_LABELS } from '$lib/data/exercises';
import RestTimer from './RestTimer.svelte';
import { page } from '$app/stores';
@@ -1,5 +1,5 @@
<script>
import { Cloud, CloudOff, RefreshCw, AlertTriangle } from 'lucide-svelte';
import { Cloud, CloudOff, RefreshCw, AlertTriangle } from '@lucide/svelte';
/** @type {{ status: string }} */
let { status } = $props();
@@ -1,6 +1,6 @@
<script>
import { getExerciseById } from '$lib/data/exercises';
import { EllipsisVertical, MapPin } from 'lucide-svelte';
import { EllipsisVertical, MapPin } from '@lucide/svelte';
import { page } from '$app/stores';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
+1 -1
View File
@@ -1,6 +1,6 @@
<script>
import { goto } from '$app/navigation';
import { Play, Pause } from 'lucide-svelte';
import { Play, Pause } from '@lucide/svelte';
import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte';
import { page } from '$app/stores';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
@@ -1,5 +1,5 @@
<script>
import { UtensilsCrossed, X } from 'lucide-svelte';
import { UtensilsCrossed, X } from '@lucide/svelte';
import { toast } from '$lib/js/toast.svelte';
let {
@@ -1,5 +1,5 @@
<script>
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
import { ChevronLeft, ChevronRight } from '@lucide/svelte';
import { getStickerById } from '$lib/utils/stickers';
import {
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
+1 -1
View File
@@ -1,6 +1,6 @@
<script>
import { X, Sparkles, Wind, Bath, UtensilsCrossed, CookingPot, WashingMachine,
Flower2, Droplets, Leaf, ShoppingCart, Trash2, Shirt, Brush } from 'lucide-svelte';
Flower2, Droplets, Leaf, ShoppingCart, Trash2, Shirt, Brush } from '@lucide/svelte';
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
import Toggle from '$lib/components/Toggle.svelte';
-342
View File
@@ -1,342 +0,0 @@
import Redis from 'ioredis';
// Key prefix for namespace isolation
const KEY_PREFIX = 'homepage:';
// Redis client configuration
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
// Reconnection strategy: exponential backoff with max 2 seconds
retryStrategy: (times) => Math.min(times * 50, 2000),
// Lazy connect to avoid blocking startup
lazyConnect: true,
// Connection timeout
connectTimeout: 10000,
// Enable offline queue to buffer commands during reconnection
enableOfflineQueue: true,
});
// Track connection status
let isConnected = false;
let isConnecting = false;
// Graceful connection with error handling
async function ensureConnection(): Promise<boolean> {
if (isConnected) {
return true;
}
if (isConnecting) {
// Wait for ongoing connection attempt
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (isConnected || !isConnecting) {
clearInterval(checkInterval);
resolve(isConnected);
}
}, 100);
});
}
isConnecting = true;
try {
await redis.connect();
isConnected = true;
console.log('[Redis] Connected successfully');
return true;
} catch (err) {
console.error('[Redis] Connection failed:', err);
isConnected = false;
return false;
} finally {
isConnecting = false;
}
}
// Handle connection events
redis.on('connect', () => {
isConnected = true;
console.log('[Redis] Connected');
});
redis.on('ready', () => {
isConnected = true;
console.log('[Redis] Ready');
});
redis.on('error', (err) => {
console.error('[Redis] Error:', err);
});
redis.on('close', () => {
isConnected = false;
console.log('[Redis] Connection closed');
});
redis.on('reconnecting', () => {
console.log('[Redis] Reconnecting...');
});
// Helper function to add prefix to keys
function prefixKey(key: string): string {
return `${KEY_PREFIX}${key}`;
}
// Helper function to add prefix to multiple keys
function prefixKeys(keys: string[]): string[] {
return keys.map(prefixKey);
}
/**
* Cache wrapper with automatic key prefixing and error handling
*/
export const cache = {
/**
* Get a value from cache
*/
async get(key: string): Promise<string | null> {
if (!(await ensureConnection())) {
return null;
}
try {
return await redis.get(prefixKey(key));
} catch (err) {
console.error(`[Redis] GET error for key "${key}":`, err);
return null;
}
},
/**
* Set a value in cache with optional TTL (in seconds)
*/
async set(key: string, value: string, ttl?: number): Promise<boolean> {
if (!(await ensureConnection())) {
return false;
}
try {
if (ttl) {
await redis.setex(prefixKey(key), ttl, value);
} else {
await redis.set(prefixKey(key), value);
}
return true;
} catch (err) {
console.error(`[Redis] SET error for key "${key}":`, err);
return false;
}
},
/**
* Delete one or more keys from cache
*/
async del(...keys: string[]): Promise<number> {
if (!(await ensureConnection())) {
return 0;
}
try {
const prefixedKeys = prefixKeys(keys);
return await redis.del(...prefixedKeys);
} catch (err) {
console.error(`[Redis] DEL error for keys "${keys.join(', ')}":`, err);
return 0;
}
},
/**
* Delete all keys matching a pattern (uses SCAN for safety)
* Pattern should NOT include the prefix (it will be added automatically)
*/
async delPattern(pattern: string): Promise<number> {
if (!(await ensureConnection())) {
return 0;
}
try {
const prefixedPattern = prefixKey(pattern);
const keys: string[] = [];
let cursor = '0';
// Use SCAN to safely iterate through keys
do {
const [nextCursor, matchedKeys] = await redis.scan(
cursor,
'MATCH',
prefixedPattern,
'COUNT',
100
);
cursor = nextCursor;
keys.push(...matchedKeys);
} while (cursor !== '0');
if (keys.length > 0) {
return await redis.del(...keys);
}
return 0;
} catch (err) {
console.error(`[Redis] DEL PATTERN error for pattern "${pattern}":`, err);
return 0;
}
},
/**
* Redis Set operations for managing sets (e.g., user favorites)
*/
sets: {
/**
* Add members to a set
*/
async add(key: string, ...members: string[]): Promise<number> {
if (!(await ensureConnection())) {
return 0;
}
try {
return await redis.sadd(prefixKey(key), ...members);
} catch (err) {
console.error(`[Redis] SADD error for key "${key}":`, err);
return 0;
}
},
/**
* Remove members from a set
*/
async remove(key: string, ...members: string[]): Promise<number> {
if (!(await ensureConnection())) {
return 0;
}
try {
return await redis.srem(prefixKey(key), ...members);
} catch (err) {
console.error(`[Redis] SREM error for key "${key}":`, err);
return 0;
}
},
/**
* Get all members of a set
*/
async members(key: string): Promise<string[]> {
if (!(await ensureConnection())) {
return [];
}
try {
return await redis.smembers(prefixKey(key));
} catch (err) {
console.error(`[Redis] SMEMBERS error for key "${key}":`, err);
return [];
}
},
/**
* Check if a member exists in a set
*/
async isMember(key: string, member: string): Promise<boolean> {
if (!(await ensureConnection())) {
return false;
}
try {
const result = await redis.sismember(prefixKey(key), member);
return result === 1;
} catch (err) {
console.error(`[Redis] SISMEMBER error for key "${key}":`, err);
return false;
}
},
},
/**
* Get cache statistics
*/
async getStats(): Promise<{ hits: number; misses: number; hitRate: string } | null> {
if (!(await ensureConnection())) {
return null;
}
try {
const info = await redis.info('stats');
const hitsMatch = info.match(/keyspace_hits:(\d+)/);
const missesMatch = info.match(/keyspace_misses:(\d+)/);
const hits = hitsMatch ? parseInt(hitsMatch[1]) : 0;
const misses = missesMatch ? parseInt(missesMatch[1]) : 0;
const total = hits + misses;
const hitRate = total > 0 ? ((hits / total) * 100).toFixed(2) : '0.00';
return { hits, misses, hitRate: `${hitRate}%` };
} catch (err) {
console.error('[Redis] Error getting stats:', err);
return null;
}
},
};
// Graceful shutdown
process.on('SIGTERM', () => {
redis.quit();
});
process.on('SIGINT', () => {
redis.quit();
});
/**
* Helper function to invalidate all recipe caches
* Call this after recipe create/update/delete operations
*/
export async function invalidateRecipeCaches(): Promise<void> {
try {
// Clear all recipe-related caches for both languages in parallel
await Promise.all([
cache.delPattern('recipes:rezepte:*'),
cache.delPattern('recipes:recipes:*'),
]);
} catch (err) {
console.error('[Cache] Error invalidating recipe caches:', err);
}
}
/**
* Helper function to invalidate cospend caches for specific users and/or payments
* Call this after payment create/update/delete operations
* @param usernames - Array of usernames whose caches should be invalidated
* @param paymentId - Optional payment ID to invalidate specific payment cache
*/
export async function invalidateCospendCaches(usernames: string[], paymentId?: string): Promise<void> {
try {
const invalidations: Promise<unknown>[] = [];
// Invalidate balance and debts caches for all affected users
for (const username of usernames) {
invalidations.push(
cache.del(`cospend:balance:${username}`),
cache.del(`cospend:debts:${username}`)
);
}
// Invalidate global balance cache
invalidations.push(cache.del('cospend:balance:all'));
// Invalidate payment list caches (all pagination variants)
invalidations.push(cache.delPattern('cospend:payments:list:*'));
// If specific payment ID provided, invalidate its cache
if (paymentId) {
invalidations.push(cache.del(`cospend:payment:${paymentId}`));
}
await Promise.all(invalidations);
} catch (err) {
console.error('[Cache] Error invalidating cospend caches:', err);
}
}
export default cache;
+1 -1
View File
@@ -100,7 +100,7 @@ class RecurringPaymentScheduler {
amount: split.amount,
proportion: split.proportion,
personalAmount: split.personalAmount
});
} as any);
});
await Promise.all(splitPromises);
-1
View File
@@ -246,5 +246,4 @@ const WorkoutSessionSchema = new mongoose.Schema(
WorkoutSessionSchema.index({ createdBy: 1, startTime: -1 });
WorkoutSessionSchema.index({ templateId: 1 });
// @ts-expect-error Mongoose model() produces a union type too complex for TS
export const WorkoutSession: mongoose.Model<IWorkoutSession> = mongoose.models.WorkoutSession || mongoose.model("WorkoutSession", WorkoutSessionSchema);
@@ -1,5 +1,5 @@
<script>
import { ArrowDown, ArrowLeft } from 'lucide-svelte';
import { ArrowDown, ArrowLeft } from '@lucide/svelte';
import { page } from '$app/stores';
let expanded = $state(null);
const isGerman = $derived($page.url.pathname.startsWith('/glaube'));
@@ -45,7 +45,7 @@ onNavigate((navigation) => {
import UserHeader from '$lib/components/UserHeader.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import OfflineSyncButton from '$lib/components/OfflineSyncButton.svelte';
import { BookOpen, Heart, Leaf, LayoutGrid, Palette, Tag } from 'lucide-svelte';
import { BookOpen, Heart, Leaf, LayoutGrid, Palette, Tag } from '@lucide/svelte';
let { data, children } = $props();
let user = $derived(data.session?.user);
@@ -2,7 +2,6 @@ import { redirect, fail } from "@sveltejs/kit";
import type { Actions, PageServerLoad } from './$types';
import { Recipe } from '$models/Recipe';
import { dbConnect } from '$utils/db';
import { invalidateRecipeCaches } from '$lib/server/cache';
import { IMAGE_DIR } from '$env/static/private';
import { processAndSaveRecipeImage } from '$utils/imageProcessing';
import {
@@ -110,9 +109,6 @@ export const actions = {
try {
await Recipe.create(recipe_json);
// Invalidate recipe caches after successful creation
await invalidateRecipeCaches();
// Redirect to the new recipe page
throw redirect(303, `/${params.recipeLang}/${recipeData.short_name}`);
} catch (dbError: unknown) {
@@ -3,7 +3,6 @@ import { redirect, fail } from "@sveltejs/kit";
import { Recipe } from '$models/Recipe';
import { NutritionOverwrite } from '$models/NutritionOverwrite';
import { dbConnect } from '$utils/db';
import { invalidateRecipeCaches } from '$lib/server/cache';
import { invalidateOverwriteCache } from '$lib/server/nutritionMatcher';
import { IMAGE_DIR } from '$env/static/private';
import { rename, access, unlink } from 'fs/promises';
@@ -200,7 +199,7 @@ export const actions = {
const result = await Recipe.findOneAndUpdate(
{ short_name: originalShortName },
recipe_json,
{ new: true }
{ returnDocument: 'after' }
);
if (!result) {
@@ -249,9 +248,6 @@ export const actions = {
}
}
// Invalidate recipe caches after successful update
await invalidateRecipeCaches();
// Redirect to the updated recipe page (might have new short_name)
throw redirect(303, `/${params.recipeLang}/${recipeData.short_name}`);
} catch (dbError: unknown) {
@@ -2,7 +2,6 @@ import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '$models/Recipe';
import { dbConnect } from '$utils/db';
import { error } from '@sveltejs/kit';
import { invalidateRecipeCaches } from '$lib/server/cache';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, cookies, locals}) => {
@@ -20,8 +19,6 @@ export const POST: RequestHandler = async ({request, cookies, locals}) => {
await dbConnect();
try{
await Recipe.create(recipe_json);
// Invalidate recipe caches after successful creation
await invalidateRecipeCaches();
} catch(e){
throw error(400, e instanceof Error ? e.message : String(e))
}
@@ -4,7 +4,6 @@ import { UserFavorites } from '$models/UserFavorites';
import { dbConnect } from '$utils/db';
import type {RecipeModelType} from '$types/types';
import { error } from '@sveltejs/kit';
import { invalidateRecipeCaches } from '$lib/server/cache';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request, locals}) => {
@@ -70,9 +69,6 @@ export const POST: RequestHandler = async ({request, locals}) => {
// Delete the recipe
await Recipe.findOneAndDelete({short_name: short_name});
// Invalidate recipe caches after successful deletion
await invalidateRecipeCaches();
return new Response(JSON.stringify({msg: "Deleted recipe successfully"}),{
status: 200,
});
@@ -6,7 +6,6 @@ import { error } from '@sveltejs/kit';
import { rename } from 'fs/promises';
import { join } from 'path';
import { existsSync } from 'fs';
import { invalidateRecipeCaches } from '$lib/server/cache';
// header: use for bearer token for now
// recipe json in body
@@ -48,9 +47,6 @@ export const POST: RequestHandler = async ({request, locals}) => {
await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json);
// Invalidate recipe caches after successful update
await invalidateRecipeCaches();
return new Response(JSON.stringify({msg: "Edited recipe successfully"}),{
status: 200,
});
@@ -53,7 +53,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
await UserFavorites.findOneAndUpdate(
{ username: session.user.nickname },
{ $addToSet: { favorites: recipe._id } },
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
);
@@ -28,7 +28,7 @@ export const GET: RequestHandler = async ({ params, locals }) => {
const en = isEnglish(params.recipeLang!);
let recipes = await Recipe.find({
_id: { $in: userFavorites.favorites },
_id: { $in: userFavorites.favorites } as any,
...approvalFilter
}).lean() as unknown as RecipeModelType[];
@@ -1,26 +1,15 @@
import { json, type RequestHandler } from '@sveltejs/kit';
import type { BriefRecipeType } from '$types/types';
import { Recipe } from '$models/Recipe';
import { dbConnect } from '$utils/db';
import { rand_array } from '$lib/js/randomize';
import cache from '$lib/server/cache';
import { briefQueryConfig, toBrief } from '$lib/server/recipeHelpers';
export const GET: RequestHandler = async ({ params }) => {
const { approvalFilter, projection } = briefQueryConfig(params.recipeLang!);
const cacheKey = `recipes:${params.recipeLang}:all_brief`;
let recipes: BriefRecipeType[] | null = null;
const cached = await cache.get(cacheKey);
await dbConnect();
const dbRecipes = await Recipe.find(approvalFilter, projection).lean();
const recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
if (cached) {
recipes = JSON.parse(cached);
} else {
await dbConnect();
const dbRecipes = await Recipe.find(approvalFilter, projection).lean();
recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
await cache.set(cacheKey, JSON.stringify(recipes), 3600);
}
return json(JSON.parse(JSON.stringify(rand_array(recipes!))));
return json(JSON.parse(JSON.stringify(rand_array(recipes))));
};
@@ -2,27 +2,17 @@ import { json, type RequestHandler } from '@sveltejs/kit';
import { Recipe } from '$models/Recipe';
import { dbConnect } from '$utils/db';
import { rand_array } from '$lib/js/randomize';
import cache from '$lib/server/cache';
import { briefQueryConfig, toBrief } from '$lib/server/recipeHelpers';
export const GET: RequestHandler = async ({ params }) => {
const { approvalFilter, projection } = briefQueryConfig(params.recipeLang!);
const cacheKey = `recipes:${params.recipeLang}:in_season:${params.month}`;
let recipes = null;
const cached = await cache.get(cacheKey);
if (cached) {
recipes = JSON.parse(cached);
} else {
await dbConnect();
const dbRecipes = await Recipe.find(
{ season: params.month, icon: { $ne: "🍽️" }, ...approvalFilter },
projection
).lean();
recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
await cache.set(cacheKey, JSON.stringify(recipes), 3600);
}
await dbConnect();
const dbRecipes = await Recipe.find(
{ season: parseInt(params.month!, 10), icon: { $ne: "🍽️" }, ...approvalFilter },
projection
).lean();
const recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
return json(JSON.parse(JSON.stringify(rand_array(recipes))));
};
@@ -2,27 +2,17 @@ import { json, type RequestHandler } from '@sveltejs/kit';
import { Recipe } from '$models/Recipe';
import { dbConnect } from '$utils/db';
import { rand_array } from '$lib/js/randomize';
import cache from '$lib/server/cache';
import { briefQueryConfig, toBrief } from '$lib/server/recipeHelpers';
export const GET: RequestHandler = async ({ params }) => {
const { approvalFilter, prefix, projection } = briefQueryConfig(params.recipeLang!);
const cacheKey = `recipes:${params.recipeLang}:tag:${params.tag}`;
let recipes = null;
const cached = await cache.get(cacheKey);
if (cached) {
recipes = JSON.parse(cached);
} else {
await dbConnect();
const dbRecipes = await Recipe.find(
{ [`${prefix}tags`]: params.tag, ...approvalFilter },
projection
).lean();
recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
await cache.set(cacheKey, JSON.stringify(recipes), 3600);
}
await dbConnect();
const dbRecipes = await Recipe.find(
{ [`${prefix}tags`]: params.tag, ...approvalFilter },
projection
).lean();
const recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
return json(JSON.parse(JSON.stringify(rand_array(recipes))));
};
@@ -81,7 +81,7 @@ export const PATCH: RequestHandler = async ({ request, locals }) => {
links: links.filter((l: any) => l.url?.trim()),
notes: notes?.trim() || ''
},
{ new: true }
{ returnDocument: 'after' }
).lean();
if (!item) {
+1 -24
View File
@@ -3,7 +3,6 @@ import { PaymentSplit } from '$models/PaymentSplit';
import { Payment } from '$models/Payment'; // Need to import Payment for populate to work
import { dbConnect } from '$utils/db';
import { error, json } from '@sveltejs/kit';
import cache from '$lib/server/cache';
export const GET: RequestHandler = async ({ locals, url }) => {
const auth = await locals.auth();
@@ -18,14 +17,6 @@ export const GET: RequestHandler = async ({ locals, url }) => {
try {
if (includeAll) {
// Try cache first for all balances
const cacheKey = 'cospend:balance:all';
const cached = await cache.get(cacheKey);
if (cached) {
return json(JSON.parse(cached));
}
const allSplits = await PaymentSplit.aggregate([
{
$group: {
@@ -58,20 +49,9 @@ export const GET: RequestHandler = async ({ locals, url }) => {
allBalances: allSplits
};
// Cache for 30 minutes
await cache.set(cacheKey, JSON.stringify(result), 1800);
return json(result);
} else {
// Try cache first for individual user balance
const cacheKey = `cospend:balance:${username}`;
const cached = await cache.get(cacheKey);
if (cached) {
return json(JSON.parse(cached));
}
const userSplits = await PaymentSplit.find({ username }).lean();
// Calculate net balance: negative = you are owed money, positive = you owe money
@@ -112,9 +92,6 @@ export const GET: RequestHandler = async ({ locals, url }) => {
recentSplits
};
// Cache for 30 minutes
await cache.set(cacheKey, JSON.stringify(result), 1800);
return json(result);
}
@@ -122,4 +99,4 @@ export const GET: RequestHandler = async ({ locals, url }) => {
console.error('Error calculating balance:', e);
throw error(500, 'Failed to calculate balance');
}
};
};
+2 -16
View File
@@ -3,7 +3,6 @@ import { PaymentSplit } from '$models/PaymentSplit';
import type { IPayment } from '$models/Payment';
import { dbConnect } from '$utils/db';
import { error, json } from '@sveltejs/kit';
import cache from '$lib/server/cache';
type PopulatedPayment = IPayment & { _id: import('mongoose').Types.ObjectId };
@@ -30,14 +29,6 @@ export const GET: RequestHandler = async ({ locals }) => {
await dbConnect();
try {
// Try cache first
const cacheKey = `cospend:debts:${currentUser}`;
const cached = await cache.get(cacheKey);
if (cached) {
return json(JSON.parse(cached));
}
// Get all splits for the current user
const userSplits = await PaymentSplit.find({ username: currentUser })
.populate('paymentId')
@@ -46,7 +37,7 @@ export const GET: RequestHandler = async ({ locals }) => {
// Get all other users who have splits with payments involving the current user
const paymentIds = userSplits.map(split => (split.paymentId as unknown as PopulatedPayment)._id);
const allRelatedSplits = await PaymentSplit.find({
paymentId: { $in: paymentIds },
paymentId: { $in: paymentIds } as any,
username: { $ne: currentUser }
})
.populate('paymentId')
@@ -115,15 +106,10 @@ export const GET: RequestHandler = async ({ locals }) => {
totalIOwe: whoIOwe.reduce((sum, debt) => sum + debt.netAmount, 0)
};
// Cache for 15 minutes (as suggested in plan for debt breakdown)
await cache.set(cacheKey, JSON.stringify(result), 900);
return json(result);
} catch (e) {
console.error('Error calculating debt breakdown:', e);
throw error(500, 'Failed to calculate debt breakdown');
} finally {
// Connection will be reused
}
};
};
+8 -30
View File
@@ -4,7 +4,6 @@ import { PaymentSplit } from '$models/PaymentSplit';
import { dbConnect } from '$utils/db';
import { convertToCHF, isValidCurrencyCode } from '$lib/utils/currency';
import { error, json } from '@sveltejs/kit';
import cache, { invalidateCospendCaches } from '$lib/server/cache';
interface SplitInput {
username: string;
@@ -25,14 +24,6 @@ export const GET: RequestHandler = async ({ locals, url }) => {
await dbConnect();
try {
// Try cache first (include pagination params in key)
const cacheKey = `cospend:payments:list:${limit}:${offset}`;
const cached = await cache.get(cacheKey);
if (cached) {
return json(JSON.parse(cached));
}
const payments = await Payment.find()
.populate('splits')
.sort({ date: -1, createdAt: -1 })
@@ -40,16 +31,9 @@ export const GET: RequestHandler = async ({ locals, url }) => {
.skip(offset)
.lean();
const result = { payments };
// Cache for 10 minutes (shorter TTL since this changes frequently)
await cache.set(cacheKey, JSON.stringify(result), 600);
return json(result);
return json({ payments });
} catch (e) {
throw error(500, 'Failed to fetch payments');
} finally {
// Connection will be reused
}
};
@@ -89,7 +73,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const totalPersonal = splits.reduce((sum: number, split: SplitInput) => {
return sum + (split.personalAmount ?? 0);
}, 0);
if (totalPersonal > amount) {
throw error(400, 'Personal amounts cannot exceed total payment amount');
}
@@ -114,7 +98,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
}
await dbConnect();
try {
const payment = await Payment.create({
title,
@@ -135,7 +119,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const convertedSplits = splits.map((split: SplitInput) => {
let convertedAmount = split.amount;
let convertedPersonalAmount = split.personalAmount;
// Convert amounts if we have a foreign currency
if (inputCurrency !== 'CHF' && exchangeRate) {
convertedAmount = split.amount * exchangeRate;
@@ -143,7 +127,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
convertedPersonalAmount = split.personalAmount * exchangeRate;
}
}
return {
paymentId: payment._id,
username: split.username,
@@ -153,16 +137,12 @@ export const POST: RequestHandler = async ({ request, locals }) => {
};
});
const splitPromises = convertedSplits.map((split: { paymentId: unknown; username: string; amount: number; proportion?: number; personalAmount?: number }) => {
return PaymentSplit.create(split);
const splitPromises = convertedSplits.map((split) => {
return PaymentSplit.create(split as any);
});
await Promise.all(splitPromises);
// Invalidate caches for all affected users
const affectedUsernames = splits.map((split: SplitInput) => split.username);
await invalidateCospendCaches(affectedUsernames, payment._id.toString());
return json({
success: true,
payment: payment._id
@@ -171,7 +151,5 @@ export const POST: RequestHandler = async ({ request, locals }) => {
} catch (e) {
console.error('Error creating payment:', e);
throw error(500, 'Failed to create payment');
} finally {
// Connection will be reused
}
};
};
@@ -3,7 +3,6 @@ import { Payment } from '$models/Payment';
import { PaymentSplit } from '$models/PaymentSplit';
import { dbConnect } from '$utils/db';
import { error, json } from '@sveltejs/kit';
import cache, { invalidateCospendCaches } from '$lib/server/cache';
export const GET: RequestHandler = async ({ params, locals }) => {
const auth = await locals.auth();
@@ -16,31 +15,16 @@ export const GET: RequestHandler = async ({ params, locals }) => {
await dbConnect();
try {
// Try cache first
const cacheKey = `cospend:payment:${id}`;
const cached = await cache.get(cacheKey);
if (cached) {
return json(JSON.parse(cached));
}
const payment = await Payment.findById(id).populate('splits').lean();
if (!payment) {
throw error(404, 'Payment not found');
}
const result = { payment };
// Cache for 30 minutes
await cache.set(cacheKey, JSON.stringify(result), 1800);
return json(result);
return json({ payment });
} catch (e: unknown) {
if (e && typeof e === 'object' && 'status' in e && (e as { status: number }).status === 404) throw e;
throw error(500, 'Failed to fetch payment');
} finally {
// Connection will be reused
}
};
@@ -66,10 +50,6 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
throw error(403, 'Not authorized to edit this payment');
}
// Get old splits to invalidate caches for users who were in the original payment
const oldSplits = await PaymentSplit.find({ paymentId: id }).lean();
const oldUsernames = oldSplits.map(split => split.username);
const updatedPayment = await Payment.findByIdAndUpdate(
id,
{
@@ -82,12 +62,11 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
category: data.category || payment.category,
splitMethod: data.splitMethod
},
{ new: true }
{ returnDocument: 'after' }
);
let newUsernames: string[] = [];
if (data.splits) {
await PaymentSplit.deleteMany({ paymentId: id });
await PaymentSplit.deleteMany({ paymentId: id } as any);
const splitPromises = data.splits.map((split: { username: string; amount: number; proportion?: number; personalAmount?: number }) => {
return PaymentSplit.create({
@@ -96,23 +75,16 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
amount: split.amount,
proportion: split.proportion,
personalAmount: split.personalAmount
});
} as any);
});
await Promise.all(splitPromises);
newUsernames = data.splits.map((split: { username: string }) => split.username);
}
// Invalidate caches for all users (old and new)
const allAffectedUsers = [...new Set([...oldUsernames, ...newUsernames])];
await invalidateCospendCaches(allAffectedUsers, id);
return json({ success: true, payment: updatedPayment });
} catch (e: unknown) {
if (e && typeof e === 'object' && 'status' in e) throw e;
throw error(500, 'Failed to update payment');
} finally {
// Connection will be reused
}
};
@@ -137,21 +109,12 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
throw error(403, 'Not authorized to delete this payment');
}
// Get splits to invalidate caches for affected users
const splits = await PaymentSplit.find({ paymentId: id }).lean();
const affectedUsernames = splits.map(split => split.username);
await PaymentSplit.deleteMany({ paymentId: id });
await PaymentSplit.deleteMany({ paymentId: id } as any);
await Payment.findByIdAndDelete(id);
// Invalidate caches for all affected users
await invalidateCospendCaches(affectedUsernames, id);
return json({ success: true });
} catch (e: unknown) {
if (e && typeof e === 'object' && 'status' in e) throw e;
throw error(500, 'Failed to delete payment');
} finally {
// Connection will be reused
}
};
};
@@ -134,7 +134,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
const recurringPayment = await RecurringPayment.findByIdAndUpdate(
id,
updateData,
{ new: true, runValidators: true }
{ returnDocument: 'after', runValidators: true }
);
return json({
@@ -6,7 +6,6 @@ import { dbConnect } from '$utils/db';
import { error, json } from '@sveltejs/kit';
import { calculateNextExecutionDate } from '$lib/utils/recurring';
import { convertToCHF } from '$lib/utils/currency';
import { invalidateCospendCaches } from '$lib/server/cache';
export const POST: RequestHandler = async ({ locals }) => {
const auth = await locals.auth();
@@ -15,10 +14,10 @@ export const POST: RequestHandler = async ({ locals }) => {
}
await dbConnect();
try {
const now = new Date();
// Find all active recurring payments that are due
const duePayments = await RecurringPayment.find({
isActive: true,
@@ -42,8 +41,8 @@ export const POST: RequestHandler = async ({ locals }) => {
if (recurringPayment.currency !== 'CHF') {
try {
const conversion = await convertToCHF(
recurringPayment.amount,
recurringPayment.currency,
recurringPayment.amount,
recurringPayment.currency,
now.toISOString()
);
finalAmount = conversion.convertedAmount;
@@ -74,7 +73,7 @@ export const POST: RequestHandler = async ({ locals }) => {
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;
@@ -82,7 +81,7 @@ export const POST: RequestHandler = async ({ locals }) => {
convertedPersonalAmount = split.personalAmount * exchangeRate;
}
}
return {
paymentId: payment._id,
username: split.username,
@@ -94,15 +93,11 @@ export const POST: RequestHandler = async ({ locals }) => {
// Create payment splits
const splitPromises = convertedSplits.map((split) => {
return PaymentSplit.create(split);
return PaymentSplit.create(split as any);
});
await Promise.all(splitPromises);
// Invalidate caches for all affected users
const affectedUsernames = recurringPayment.splits.map((split) => split.username);
await invalidateCospendCaches(affectedUsernames, payment._id.toString());
// Calculate next execution date
const nextExecutionDate = calculateNextExecutionDate(recurringPayment, now);
@@ -133,7 +128,7 @@ export const POST: RequestHandler = async ({ locals }) => {
}
}
return json({
return json({
success: true,
executed: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
@@ -143,7 +138,5 @@ export const POST: RequestHandler = async ({ locals }) => {
} catch (e) {
console.error('Error executing recurring payments:', e);
throw error(500, 'Failed to execute recurring payments');
} finally {
// Connection will be reused
}
};
};
@@ -28,7 +28,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
await FavoriteIngredient.findOneAndUpdate(
{ createdBy: user.nickname, source, sourceId: String(sourceId) },
{ createdBy: user.nickname, source, sourceId: String(sourceId), name },
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
);
return json({ ok: true }, { status: 201 });
+1 -1
View File
@@ -56,7 +56,7 @@ export const PUT: RequestHandler = async ({ request, locals }) => {
const goal = await FitnessGoal.findOneAndUpdate(
{ username: user.nickname },
update,
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
).lean() as any;
const streak = await computeStreak(user.nickname, weeklyWorkouts);
@@ -60,7 +60,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
const template = await IntervalTemplate.findOneAndUpdate(
{ _id: params.id, createdBy: session.user.nickname },
{ name, steps },
{ new: true }
{ returnDocument: 'after' }
);
if (!template) {
@@ -47,7 +47,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
const measurement = await BodyMeasurement.findOneAndUpdate(
{ _id: params.id, createdBy: user.nickname },
updateData,
{ new: true }
{ returnDocument: 'after' }
);
if (!measurement) {
+1 -1
View File
@@ -85,7 +85,7 @@ export const PUT: RequestHandler = async ({ request, locals }) => {
const schedule = await WorkoutSchedule.findOneAndUpdate(
{ userId: user.nickname },
{ templateOrder },
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
);
return json({ schedule: { templateOrder: schedule.templateOrder } });
@@ -119,7 +119,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
createdBy: session.user.nickname
},
updateData,
{ new: true }
{ returnDocument: 'after' }
);
if (!workoutSession) {
@@ -87,7 +87,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
exercises: exercises ?? [],
isPublic
},
{ new: true }
{ returnDocument: 'after' }
);
if (!template) {
@@ -73,7 +73,7 @@ export const PUT: RequestHandler = async ({ request, locals }) => {
},
$setOnInsert: { userId }
},
{ upsert: true, new: true, lean: true }
{ upsert: true, returnDocument: 'after', lean: true }
);
// Broadcast to all other connected devices
@@ -64,7 +64,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const updated = await AngelusStreak.findOneAndUpdate(
{ username: session.user.nickname },
updateFields,
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
).lean() as any;
return json({
@@ -54,7 +54,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const updated = await RosaryStreak.findOneAndUpdate(
{ username: session.user.nickname },
updateFields,
{ upsert: true, new: true }
{ upsert: true, returnDocument: 'after' }
).lean() as any;
return json({
@@ -34,7 +34,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const overwrite = await NutritionOverwrite.findOneAndUpdate(
{ ingredientNameDe: data.ingredientNameDe },
data,
{ upsert: true, new: true, runValidators: true },
{ upsert: true, returnDocument: 'after', runValidators: true },
).lean();
invalidateOverwriteCache();
+1 -1
View File
@@ -7,7 +7,7 @@
import PaymentModal from '$lib/components/cospend/PaymentModal.svelte';
import Header from '$lib/components/Header.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
import { LayoutDashboard, Wallet, RefreshCw } from 'lucide-svelte';
import { LayoutDashboard, Wallet, RefreshCw } from '@lucide/svelte';
let { data, children } = $props();
+1 -1
View File
@@ -4,7 +4,7 @@
import Header from '$lib/components/Header.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
import { BarChart3, Clock, Dumbbell, ListChecks, Ruler, UtensilsCrossed } from 'lucide-svelte';
import { BarChart3, Clock, Dumbbell, ListChecks, Ruler, UtensilsCrossed } from '@lucide/svelte';
import { getWorkout } from '$lib/js/workout.svelte';
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
import WorkoutFab from '$lib/components/fitness/WorkoutFab.svelte';
@@ -1,7 +1,7 @@
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Search } from 'lucide-svelte';
import { Search } from '@lucide/svelte';
import { getFilterOptions, searchExercises, translateTerm } from '$lib/data/exercises';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
@@ -1,7 +1,7 @@
<script>
import { goto, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import { Clock, Weight, Trophy, Trash2, Pencil, Plus, Upload, Route, X, RefreshCw, Gauge, Flame, Info } from 'lucide-svelte';
import { Clock, Weight, Trophy, Trash2, Pencil, Plus, Upload, Route, X, RefreshCw, Gauge, Flame, Info } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
@@ -1,6 +1,6 @@
<script>
import { page } from '$app/stores';
import { Pencil, Trash2, ChevronDown } from 'lucide-svelte';
import { Pencil, Trash2, ChevronDown } from '@lucide/svelte';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
@@ -3,7 +3,7 @@
import { goto } from '$app/navigation';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
import { Trash2 } from 'lucide-svelte';
import { Trash2 } from '@lucide/svelte';
import SaveFab from '$lib/components/SaveFab.svelte';
const lang = $derived(detectFitnessLang($page.url.pathname));
@@ -44,7 +44,7 @@ export const load: PageServerLoad = async ({ fetch, url, locals }) => {
try {
await dbConnect();
const recipes = await Recipe.find(
{ _id: { $in: [...new Set(recipeIds)] } },
{ _id: { $in: [...new Set(recipeIds)] } as any },
{ _id: 1, short_name: 1, 'images.mediapath': 1 }
).lean();
for (const r of recipes as any[]) {
@@ -1,7 +1,7 @@
<script>
import { page } from '$app/stores';
import { goto, invalidateAll } from '$app/navigation';
import { ChevronLeft, ChevronRight, Plus, Trash2, ChevronDown, Settings, Coffee, Sun, Moon, Cookie, Utensils, Info, UtensilsCrossed } from 'lucide-svelte';
import { ChevronLeft, ChevronRight, Plus, Trash2, ChevronDown, Settings, Coffee, Sun, Moon, Cookie, Utensils, Info, UtensilsCrossed } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import AddButton from '$lib/components/AddButton.svelte';
import FoodSearch from '$lib/components/fitness/FoodSearch.svelte';
@@ -1,6 +1,6 @@
<script>
import { page } from '$app/stores';
import { ChevronLeft, ChevronDown } from 'lucide-svelte';
import { ChevronLeft, ChevronDown } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { NUTRIENT_META } from '$lib/data/dailyReferenceIntake';
@@ -1,7 +1,7 @@
<script>
import { page } from '$app/stores';
import { untrack } from 'svelte';
import { ChevronLeft, Plus, Trash2, Pencil, UtensilsCrossed, X } from 'lucide-svelte';
import { ChevronLeft, Plus, Trash2, Pencil, UtensilsCrossed, X } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
import { toast } from '$lib/js/toast.svelte';
import FoodSearch from '$lib/components/fitness/FoodSearch.svelte';
@@ -1,7 +1,7 @@
<script>
import { page } from '$app/stores';
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
import { Dumbbell, Route, Flame, Weight } from 'lucide-svelte';
import { Dumbbell, Route, Flame, Weight } from '@lucide/svelte';
import FitnessStreakAura from '$lib/components/fitness/FitnessStreakAura.svelte';
import { onMount } from 'svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
@@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { Plus, Trash2, Play, Pencil, X, Save, CalendarClock, ChevronUp, ChevronDown, ArrowRight, MapPin, Dumbbell, Timer } from 'lucide-svelte';
import { Plus, Trash2, Play, Pencil, X, Save, CalendarClock, ChevronUp, ChevronDown, ArrowRight, MapPin, Dumbbell, Timer } from '@lucide/svelte';
import { getWorkout } from '$lib/js/workout.svelte';
import { getWorkoutSync } from '$lib/js/workoutSync.svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
@@ -1,7 +1,7 @@
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Trash2, Play, Pause, Trophy, Clock, Dumbbell, Route, RefreshCw, Check, ChevronUp, ChevronDown, Flame, MapPin, Volume2, X, Timer, Plus, GripVertical } from 'lucide-svelte';
import { Trash2, Play, Pause, Trophy, Clock, Dumbbell, Route, RefreshCw, Check, ChevronUp, ChevronDown, Flame, MapPin, Volume2, X, Timer, Plus, GripVertical } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($page.url.pathname));
+1 -1
View File
@@ -2,7 +2,7 @@
import { page } from '$app/stores';
import Header from '$lib/components/Header.svelte';
import UserHeader from '$lib/components/UserHeader.svelte';
import { ClipboardList, Trophy } from 'lucide-svelte';
import { ClipboardList, Trophy } from '@lucide/svelte';
let { data, children } = $props();
let user = $derived(data.session?.user);
+1 -1
View File
@@ -4,7 +4,7 @@
import { de } from 'date-fns/locale';
import { Plus, Check, Pencil, Trash2, Tag, Users, RotateCcw, Calendar,
Sparkles, Wind, Bath, UtensilsCrossed, CookingPot, WashingMachine,
Flower2, Droplets, Leaf, ShoppingCart, Shirt, Brush } from 'lucide-svelte';
Flower2, Droplets, Leaf, ShoppingCart, Shirt, Brush } from '@lucide/svelte';
import { fly, scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
import TaskForm from '$lib/components/tasks/TaskForm.svelte';
+1 -1
View File
@@ -4,7 +4,7 @@
import { de } from 'date-fns/locale';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { Trash2 } from 'lucide-svelte';
import { Trash2 } from '@lucide/svelte';
import StickerCalendar from '$lib/components/tasks/StickerCalendar.svelte';
let { data } = $props();
+1 -8
View File
@@ -10,14 +10,7 @@ export default defineConfig({
exclude: ['barcode-detector']
},
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: ['log', 'debug'],
drop_debugger: true
}
},
rollupOptions: {
rolldownOptions: {
output: {
manualChunks: (id) => {
// Separate large dependencies into their own chunks