fix: replace any types with proper types across codebase

Replace ~100 `any` usages with proper types: use existing interfaces
(RecipeModelType, BriefRecipeType, IPayment, etc.), Record<string, unknown>
for dynamic objects, unknown for catch clauses with proper narrowing,
and inline types for callbacks. Remaining `any` types are in Svelte
components and cases where mongoose document mutation requires casts.
This commit is contained in:
2026-03-02 20:14:51 +01:00
parent b83d793f61
commit 19e46b2b3a
37 changed files with 236 additions and 146 deletions
+32 -3
View File
@@ -26,8 +26,37 @@ function parseTimeToISO8601(timeString: string): string | undefined {
return undefined;
}
export function generateRecipeJsonLd(data: any) {
const jsonLd: any = {
import type { RecipeModelType } from '$types/types';
interface HowToStep {
"@type": "HowToStep";
name: string;
text: string;
}
interface RecipeJsonLd {
"@context": string;
"@type": string;
name: string;
description: string;
author: { "@type": string; name: string };
datePublished?: string;
dateModified?: string;
recipeCategory: string;
keywords?: string;
image: { "@type": string; url: string; width: number; height: number };
recipeIngredient: string[];
recipeInstructions: HowToStep[];
url: string;
recipeYield?: string;
prepTime?: string;
cookTime?: string;
totalTime?: string;
[key: string]: unknown;
}
export function generateRecipeJsonLd(data: RecipeModelType) {
const jsonLd: RecipeJsonLd = {
"@context": "https://schema.org",
"@type": "Recipe",
"name": data.name?.replace(/<[^>]*>/g, ''), // Strip HTML tags
@@ -47,7 +76,7 @@ export function generateRecipeJsonLd(data: any) {
"height": 800
},
"recipeIngredient": [] as string[],
"recipeInstructions": [] as any[],
"recipeInstructions": [] as HowToStep[],
"url": `https://bocken.org/rezepte/${data.short_name}`
};
+1 -1
View File
@@ -3,7 +3,7 @@
* Extracts duplicated search state logic from multiple pages.
*/
type Recipe = { _id: string; [key: string]: any };
type Recipe = { _id: string; [key: string]: unknown };
export function createSearchFilter<T extends Recipe>(getRecipes: () => T[]) {
let matchedRecipeIds = $state(new Set<string>());
+1 -1
View File
@@ -96,7 +96,7 @@ export async function listOllamaModels(): Promise<string[]> {
}
const data = await response.json();
return data.models?.map((m: any) => m.name) || [];
return data.models?.map((m: { name: string }) => m.name) || [];
} catch (error) {
console.error('Failed to list Ollama models:', error);
return [];
+1 -1
View File
@@ -312,7 +312,7 @@ export async function invalidateRecipeCaches(): Promise<void> {
*/
export async function invalidateCospendCaches(usernames: string[], paymentId?: string): Promise<void> {
try {
const invalidations: Promise<any>[] = [];
const invalidations: Promise<void>[] = [];
// Invalidate balance and debts caches for all affected users
for (const username of usernames) {
+17 -12
View File
@@ -2,13 +2,18 @@
* Utility functions for handling user favorites on the server side
*/
export async function getUserFavorites(fetch: any, locals: any): Promise<string[]> {
import type { BriefRecipeType } from '$types/types';
import type { Session } from '@auth/sveltekit';
type BriefRecipeWithFavorite = BriefRecipeType & { isFavorite: boolean };
export async function getUserFavorites(fetch: typeof globalThis.fetch, locals: App.Locals): Promise<string[]> {
const session = await locals.auth();
if (!session?.user?.nickname) {
return [];
}
try {
const favRes = await fetch('/api/rezepte/favorites');
if (favRes.ok) {
@@ -19,17 +24,17 @@ export async function getUserFavorites(fetch: any, locals: any): Promise<string[
// Silently fail if favorites can't be loaded
console.error('Error loading user favorites:', e);
}
return [];
}
export function addFavoriteStatusToRecipes(recipes: any[], userFavorites: string[]): any[] {
export function addFavoriteStatusToRecipes(recipes: BriefRecipeType[], userFavorites: string[]): BriefRecipeWithFavorite[] {
// Safety check: ensure recipes is an array
if (!Array.isArray(recipes)) {
console.error('addFavoriteStatusToRecipes: recipes is not an array:', recipes);
return [];
}
return recipes.map(recipe => ({
...recipe,
isFavorite: userFavorites.some(favId => favId.toString() === recipe._id.toString())
@@ -37,18 +42,18 @@ export function addFavoriteStatusToRecipes(recipes: any[], userFavorites: string
}
export async function loadRecipesWithFavorites(
fetch: any,
locals: any,
recipeLoader: () => Promise<any>
): Promise<{ recipes: any[], session: any }> {
fetch: typeof globalThis.fetch,
locals: App.Locals,
recipeLoader: () => Promise<BriefRecipeType[]>
): Promise<{ recipes: BriefRecipeWithFavorite[], session: Session | null }> {
const [recipes, userFavorites, session] = await Promise.all([
recipeLoader(),
getUserFavorites(fetch, locals),
locals.auth()
]);
return {
recipes: addFavoriteStatusToRecipes(recipes, userFavorites),
session
};
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import type { BriefRecipeType } from '$types/types';
import type { BriefRecipeType, RecipeModelType } from '$types/types';
/**
* Check whether a recipeLang param refers to the English version.
@@ -30,7 +30,7 @@ export function briefQueryConfig(recipeLang: string) {
* For English, extracts from translations.en and adds germanShortName.
* For German, passes through root-level fields.
*/
export function toBrief(recipe: any, recipeLang: string): BriefRecipeType {
export function toBrief(recipe: RecipeModelType, recipeLang: string): BriefRecipeType {
if (isEnglish(recipeLang)) {
return {
_id: recipe._id,
+4 -3
View File
@@ -27,8 +27,8 @@ class RecurringPaymentScheduler {
await this.processRecurringPayments();
}, {
timezone: 'Europe/Zurich' // Adjust timezone as needed
} as any);
timezone: 'Europe/Zurich'
});
console.log('[Scheduler] Recurring payments scheduler started (runs every minute)');
}
@@ -154,7 +154,8 @@ class RecurringPaymentScheduler {
return {
isRunning: this.isRunning,
isScheduled: this.task !== null,
nextRun: (this.task as any)?.nextDate?.()?.toISOString?.()
// node-cron's ScheduledTask type doesn't expose nextDate, but it exists at runtime
nextRun: (this.task as unknown as { nextDate?: () => { toISOString: () => string } })?.nextDate?.()?.toISOString?.()
};
}
}
+18 -13
View File
@@ -1,11 +1,16 @@
// Utility functions for identifying and handling settlement payments
import type { IPayment } from '$models/Payment';
import type { IPaymentSplit } from '$models/PaymentSplit';
type PaymentWithSplits = IPayment & { splits?: IPaymentSplit[] };
/**
* Identifies if a payment is a settlement payment based on category
*/
export function isSettlementPayment(payment: any): boolean {
export function isSettlementPayment(payment: PaymentWithSplits | null | undefined): boolean {
if (!payment) return false;
// Check if category is settlement
return payment.category === 'settlement';
}
@@ -20,44 +25,44 @@ export function getSettlementIcon(): string {
/**
* Gets appropriate styling classes for settlement payments
*/
export function getSettlementClasses(payment: any): string[] {
export function getSettlementClasses(payment: PaymentWithSplits): string[] {
if (!isSettlementPayment(payment)) {
return [];
}
return ['settlement-payment'];
}
/**
* Gets settlement-specific display text
*/
export function getSettlementDisplayText(payment: any): string {
export function getSettlementDisplayText(payment: PaymentWithSplits): string {
if (!isSettlementPayment(payment)) {
return '';
}
return 'Settlement';
}
/**
* Gets the other user in a settlement (the one who didn't pay)
*/
export function getSettlementReceiver(payment: any): string {
export function getSettlementReceiver(payment: PaymentWithSplits): string {
if (!isSettlementPayment(payment) || !payment.splits) {
return '';
}
// Find the user who has a positive amount (the receiver)
const receiver = payment.splits.find((split: any) => split.amount > 0);
const receiver = payment.splits.find((split) => split.amount > 0);
if (receiver && receiver.username) {
return receiver.username;
}
// Fallback: find the user who is not the payer
const otherUser = payment.splits.find((split: any) => split.username !== payment.paidBy);
const otherUser = payment.splits.find((split) => split.username !== payment.paidBy);
if (otherUser && otherUser.username) {
return otherUser.username;
}
return '';
}
}