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:
@@ -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}`
|
||||
};
|
||||
|
||||
|
||||
@@ -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>());
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?.()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user