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:
@@ -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"
|
||||
|
||||
@@ -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
@@ -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": [
|
||||
|
||||
Generated
+871
-1123
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
@@ -144,7 +144,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bocken"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -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,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,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,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,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,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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -100,7 +100,7 @@ class RecurringPaymentScheduler {
|
||||
amount: split.amount,
|
||||
proportion: split.proportion,
|
||||
personalAmount: split.personalAmount
|
||||
});
|
||||
} as any);
|
||||
});
|
||||
|
||||
await Promise.all(splitPromises);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user