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:
@@ -125,12 +125,13 @@ export const POST = (async ({ request, locals }) => {
|
||||
unhashedFilename: unhashedFilename,
|
||||
color
|
||||
});
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
// Re-throw errors that already have status codes
|
||||
if (err.status) throw err;
|
||||
if (err && typeof err === 'object' && 'status' in err) throw err;
|
||||
|
||||
// Log and throw generic error for unexpected failures
|
||||
console.error('[API:ImgAdd] Upload error:', err);
|
||||
throw error(500, `Failed to upload image: ${err.message || 'Unknown error'}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw error(500, `Failed to upload image: ${message || 'Unknown error'}`);
|
||||
}
|
||||
}) satisfies RequestHandler;
|
||||
|
||||
@@ -2,12 +2,14 @@ import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import { Recipe } from '$models/Recipe';
|
||||
import { dbConnect } from '$utils/db';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RecipeModelType } from '$types/types';
|
||||
import type { RecipeModelType, IngredientItem, InstructionItem } from '$types/types';
|
||||
import { isEnglish } from '$lib/server/recipeHelpers';
|
||||
|
||||
type RecipeItem = (IngredientItem | InstructionItem) & { baseRecipeRef?: Record<string, unknown>; resolvedRecipe?: Record<string, unknown> };
|
||||
|
||||
/** Recursively map populated baseRecipeRef to resolvedRecipe field */
|
||||
function mapBaseRecipeRefs(items: any[]): any[] {
|
||||
return items.map((item: any) => {
|
||||
function mapBaseRecipeRefs(items: RecipeItem[]): RecipeItem[] {
|
||||
return items.map((item) => {
|
||||
if (item.type === 'reference' && item.baseRecipeRef) {
|
||||
const resolvedRecipe = { ...item.baseRecipeRef };
|
||||
if (resolvedRecipe.ingredients) {
|
||||
@@ -84,6 +86,7 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||
}
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- mongoose query builder requires chained .populate() calls
|
||||
let dbQuery: any = Recipe.findOne(query);
|
||||
for (const p of populatePaths) {
|
||||
dbQuery = dbQuery.populate(p);
|
||||
@@ -100,7 +103,7 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||
}
|
||||
|
||||
const t = rawRecipe.translations.en;
|
||||
let recipe: any = {
|
||||
let recipe: Record<string, unknown> = {
|
||||
_id: rawRecipe._id,
|
||||
short_name: t.short_name,
|
||||
name: t.name,
|
||||
@@ -128,17 +131,17 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||
};
|
||||
|
||||
if (recipe.ingredients) {
|
||||
recipe.ingredients = mapBaseRecipeRefs(recipe.ingredients);
|
||||
recipe.ingredients = mapBaseRecipeRefs(recipe.ingredients as RecipeItem[]);
|
||||
}
|
||||
if (recipe.instructions) {
|
||||
recipe.instructions = mapBaseRecipeRefs(recipe.instructions);
|
||||
recipe.instructions = mapBaseRecipeRefs(recipe.instructions as RecipeItem[]);
|
||||
}
|
||||
|
||||
// Merge English alt/caption with original image paths
|
||||
const imagesArray = Array.isArray(rawRecipe.images) ? rawRecipe.images : (rawRecipe.images ? [rawRecipe.images] : []);
|
||||
if (imagesArray.length > 0) {
|
||||
const translatedImages = t.images || [];
|
||||
recipe.images = imagesArray.map((img: any, index: number) => ({
|
||||
recipe.images = imagesArray.map((img: { mediapath: string; alt?: string; caption?: string; color?: string }, index: number) => ({
|
||||
mediapath: img.mediapath,
|
||||
alt: translatedImages[index]?.alt || img.alt || '',
|
||||
caption: translatedImages[index]?.caption || img.caption || '',
|
||||
|
||||
@@ -17,7 +17,7 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||
recipes = JSON.parse(cached);
|
||||
} else {
|
||||
await dbConnect();
|
||||
const dbRecipes: any[] = await Recipe.find(approvalFilter, projection).lean();
|
||||
const dbRecipes = await Recipe.find(approvalFilter, projection).lean();
|
||||
recipes = dbRecipes.map(r => toBrief(r, params.recipeLang!));
|
||||
await cache.set(cacheKey, JSON.stringify(recipes), 3600);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export const GET: RequestHandler = async ({ url, params, locals }) => {
|
||||
const favoritesOnly = url.searchParams.get('favorites') === 'true';
|
||||
|
||||
try {
|
||||
let dbQuery: any = { ...approvalFilter };
|
||||
let dbQuery: Record<string, unknown> = { ...approvalFilter };
|
||||
|
||||
if (category) {
|
||||
dbQuery[`${prefix}category`] = category;
|
||||
@@ -49,9 +49,10 @@ export const GET: RequestHandler = async ({ url, params, locals }) => {
|
||||
let recipes: BriefRecipeType[] = dbRecipes.map(r => toBrief(r, params.recipeLang!));
|
||||
|
||||
// Handle favorites filter
|
||||
if (favoritesOnly && (locals as any).session?.user) {
|
||||
const session = await locals.auth();
|
||||
if (favoritesOnly && session?.user) {
|
||||
const { UserFavorites } = await import('$models/UserFavorites');
|
||||
const userFavorites = await UserFavorites.findOne({ username: (locals as any).session.user.username });
|
||||
const userFavorites = await UserFavorites.findOne({ username: session.user.nickname });
|
||||
if (userFavorites?.favorites) {
|
||||
const favoriteIds = userFavorites.favorites;
|
||||
recipes = recipes.filter(recipe => favoriteIds.some(id => id.toString() === recipe._id?.toString()));
|
||||
|
||||
@@ -52,25 +52,27 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
translationMetadata,
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
console.error('Translation API error:', err);
|
||||
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
||||
// Handle specific error cases
|
||||
if (err.message?.includes('DeepL API')) {
|
||||
throw error(503, `Translation service error: ${err.message}`);
|
||||
if (message?.includes('DeepL API')) {
|
||||
throw error(503, `Translation service error: ${message}`);
|
||||
}
|
||||
|
||||
if (err.message?.includes('API key not configured')) {
|
||||
if (message?.includes('API key not configured')) {
|
||||
throw error(500, 'Translation service is not configured. Please add DEEPL_API_KEY to environment variables.');
|
||||
}
|
||||
|
||||
// Re-throw SvelteKit errors
|
||||
if (err.status) {
|
||||
if (err && typeof err === 'object' && 'status' in err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Generic error
|
||||
throw error(500, `Translation failed: ${err.message || 'Unknown error'}`);
|
||||
throw error(500, `Translation failed: ${message || 'Unknown error'}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,11 +90,12 @@ export const GET: RequestHandler = async () => {
|
||||
service: 'DeepL Translation API',
|
||||
status: isConfigured ? 'ready' : 'not configured',
|
||||
});
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return json({
|
||||
configured: false,
|
||||
status: 'error',
|
||||
error: err.message,
|
||||
error: message,
|
||||
}, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { PaymentSplit } from '$models/PaymentSplit';
|
||||
import { Payment } from '$models/Payment';
|
||||
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 };
|
||||
|
||||
interface DebtSummary {
|
||||
username: string;
|
||||
netAmount: number; // positive = you owe them, negative = they owe you
|
||||
@@ -42,7 +44,7 @@ export const GET: RequestHandler = async ({ locals }) => {
|
||||
.lean();
|
||||
|
||||
// Get all other users who have splits with payments involving the current user
|
||||
const paymentIds = userSplits.map(split => (split.paymentId as any)._id);
|
||||
const paymentIds = userSplits.map(split => (split.paymentId as unknown as PopulatedPayment)._id);
|
||||
const allRelatedSplits = await PaymentSplit.find({
|
||||
paymentId: { $in: paymentIds },
|
||||
username: { $ne: currentUser }
|
||||
@@ -55,12 +57,12 @@ export const GET: RequestHandler = async ({ locals }) => {
|
||||
|
||||
// Process current user's splits to understand what they owe/are owed
|
||||
for (const split of userSplits) {
|
||||
const payment = split.paymentId as any;
|
||||
const payment = split.paymentId as unknown as PopulatedPayment;
|
||||
if (!payment) continue;
|
||||
|
||||
// Find other participants in this payment
|
||||
const otherSplits = allRelatedSplits.filter(s =>
|
||||
(s.paymentId as any)._id.toString() === (split.paymentId as any)._id.toString()
|
||||
(s.paymentId as unknown as PopulatedPayment)._id.toString() === payment._id.toString()
|
||||
);
|
||||
|
||||
for (const otherSplit of otherSplits) {
|
||||
|
||||
@@ -76,7 +76,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
}
|
||||
];
|
||||
|
||||
const results = await Payment.aggregate(pipeline as any);
|
||||
const results = await Payment.aggregate(pipeline);
|
||||
|
||||
// Transform data into chart-friendly format
|
||||
const monthsMap = new Map();
|
||||
@@ -91,7 +91,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
}
|
||||
|
||||
// Populate data
|
||||
results.forEach((result: any) => {
|
||||
results.forEach((result: { _id: { yearMonth: string; category: string }; totalAmount: number }) => {
|
||||
const { yearMonth, category } = result._id;
|
||||
const amount = result.totalAmount;
|
||||
|
||||
@@ -112,7 +112,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
let firstMonthWithData = 0;
|
||||
for (let i = 0; i < allMonths.length; i++) {
|
||||
const monthData = monthsMap.get(allMonths[i]);
|
||||
const hasData = Object.values(monthData).some((value: any) => value > 0);
|
||||
const hasData = Object.values(monthData).some((value) => (value as number) > 0);
|
||||
if (hasData) {
|
||||
firstMonthWithData = i;
|
||||
break;
|
||||
|
||||
@@ -6,6 +6,13 @@ import { convertToCHF, isValidCurrencyCode } from '$lib/utils/currency';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import cache, { invalidateCospendCaches } from '$lib/server/cache';
|
||||
|
||||
interface SplitInput {
|
||||
username: string;
|
||||
amount: number;
|
||||
proportion?: number;
|
||||
personalAmount?: number;
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ locals, url }) => {
|
||||
const auth = await locals.auth();
|
||||
if (!auth || !auth.user?.nickname) {
|
||||
@@ -79,7 +86,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
|
||||
// Validate personal + equal split method
|
||||
if (splitMethod === 'personal_equal' && splits) {
|
||||
const totalPersonal = splits.reduce((sum: number, split: any) => {
|
||||
const totalPersonal = splits.reduce((sum: number, split: SplitInput) => {
|
||||
return sum + (parseFloat(split.personalAmount) || 0);
|
||||
}, 0);
|
||||
|
||||
@@ -125,7 +132,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
});
|
||||
|
||||
// Convert split amounts to CHF if needed
|
||||
const convertedSplits = splits.map((split: any) => {
|
||||
const convertedSplits = splits.map((split: SplitInput) => {
|
||||
let convertedAmount = split.amount;
|
||||
let convertedPersonalAmount = split.personalAmount;
|
||||
|
||||
@@ -146,14 +153,14 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
};
|
||||
});
|
||||
|
||||
const splitPromises = convertedSplits.map((split: any) => {
|
||||
const splitPromises = convertedSplits.map((split: { paymentId: unknown; username: string; amount: number; proportion?: number; personalAmount?: number }) => {
|
||||
return PaymentSplit.create(split);
|
||||
});
|
||||
|
||||
await Promise.all(splitPromises);
|
||||
|
||||
// Invalidate caches for all affected users
|
||||
const affectedUsernames = splits.map((split: any) => split.username);
|
||||
const affectedUsernames = splits.map((split: SplitInput) => split.username);
|
||||
await invalidateCospendCaches(affectedUsernames, payment._id.toString());
|
||||
|
||||
return json({
|
||||
|
||||
@@ -36,8 +36,8 @@ export const GET: RequestHandler = async ({ params, locals }) => {
|
||||
await cache.set(cacheKey, JSON.stringify(result), 1800);
|
||||
|
||||
return json(result);
|
||||
} catch (e: any) {
|
||||
if (e.status === 404) throw e;
|
||||
} 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
|
||||
@@ -89,7 +89,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
if (data.splits) {
|
||||
await PaymentSplit.deleteMany({ paymentId: id });
|
||||
|
||||
const splitPromises = data.splits.map((split: any) => {
|
||||
const splitPromises = data.splits.map((split: { username: string; amount: number; proportion?: number; personalAmount?: number }) => {
|
||||
return PaymentSplit.create({
|
||||
paymentId: id,
|
||||
username: split.username,
|
||||
@@ -100,7 +100,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
});
|
||||
|
||||
await Promise.all(splitPromises);
|
||||
newUsernames = data.splits.map((split: any) => split.username);
|
||||
newUsernames = data.splits.map((split: { username: string }) => split.username);
|
||||
}
|
||||
|
||||
// Invalidate caches for all users (old and new)
|
||||
@@ -108,8 +108,8 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
await invalidateCospendCaches(allAffectedUsers, id);
|
||||
|
||||
return json({ success: true, payment: updatedPayment });
|
||||
} catch (e: any) {
|
||||
if (e.status) throw e;
|
||||
} 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
|
||||
@@ -148,8 +148,8 @@ export const DELETE: RequestHandler = async ({ params, locals }) => {
|
||||
await invalidateCospendCaches(affectedUsernames, id);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (e: any) {
|
||||
if (e.status) throw e;
|
||||
} 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { RecurringPayment } from '$models/RecurringPayment';
|
||||
import { RecurringPayment, type IRecurringPayment } from '$models/RecurringPayment';
|
||||
import { dbConnect } from '$utils/db';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { calculateNextExecutionDate, validateCronExpression } from '$lib/utils/recurring';
|
||||
@@ -17,7 +17,7 @@ export const GET: RequestHandler = async ({ locals, url }) => {
|
||||
await dbConnect();
|
||||
|
||||
try {
|
||||
const query: any = {};
|
||||
const query: Record<string, unknown> = {};
|
||||
if (activeOnly) {
|
||||
query.isActive = true;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
|
||||
// Validate personal + equal split method
|
||||
if (splitMethod === 'personal_equal' && splits) {
|
||||
const totalPersonal = splits.reduce((sum: number, split: any) => {
|
||||
const totalPersonal = splits.reduce((sum: number, split: { personalAmount?: number }) => {
|
||||
return sum + (parseFloat(split.personalAmount) || 0);
|
||||
}, 0);
|
||||
|
||||
@@ -121,7 +121,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
...recurringPaymentData,
|
||||
frequency,
|
||||
cronExpression
|
||||
} as any, recurringPaymentData.startDate);
|
||||
} as IRecurringPayment, recurringPaymentData.startDate);
|
||||
|
||||
const recurringPayment = await RecurringPayment.create(recurringPaymentData);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
throw error(404, 'Recurring payment not found');
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
const updateData: Record<string, unknown> = {};
|
||||
|
||||
if (title !== undefined) updateData.title = title;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
@@ -113,7 +113,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
|
||||
// Validate personal + equal split method
|
||||
if (splitMethod === 'personal_equal' && splits && amount) {
|
||||
const totalPersonal = splits.reduce((sum: number, split: any) => {
|
||||
const totalPersonal = splits.reduce((sum: number, split: { personalAmount?: number }) => {
|
||||
return sum + (parseFloat(split.personalAmount) || 0);
|
||||
}, 0);
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
path: publicPath
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
if (err.status) throw err;
|
||||
} catch (err: unknown) {
|
||||
if (err && typeof err === 'object' && 'status' in err) throw err;
|
||||
console.error('Upload error:', err);
|
||||
throw error(500, 'Failed to upload file');
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
const offset = parseInt(url.searchParams.get('offset') || '0');
|
||||
|
||||
// Build query
|
||||
let query: any = { isActive: true };
|
||||
let query: Record<string, unknown> = { isActive: true };
|
||||
|
||||
// Text search
|
||||
if (search) {
|
||||
|
||||
@@ -55,7 +55,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
|
||||
return json({ error: 'At least one exercise is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (name) updateData.name = name;
|
||||
if (exercises) updateData.exercises = exercises;
|
||||
if (startTime) updateData.startTime = new Date(startTime);
|
||||
|
||||
@@ -15,7 +15,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
||||
|
||||
const includePublic = url.searchParams.get('include_public') === 'true';
|
||||
|
||||
let query: any = {
|
||||
let query: Record<string, unknown> = {
|
||||
$or: [
|
||||
{ createdBy: session.user.nickname }
|
||||
]
|
||||
|
||||
@@ -22,7 +22,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
}
|
||||
|
||||
// Build query based on filter
|
||||
let query: any = { images: { $exists: true, $ne: [] } };
|
||||
let query: Record<string, unknown> = { images: { $exists: true, $ne: [] } };
|
||||
|
||||
if (filter === 'missing') {
|
||||
// Find recipes with images but missing alt text
|
||||
|
||||
@@ -16,7 +16,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
||||
const body = await request.json();
|
||||
const { filter = 'missing', limit = 50 } = body;
|
||||
|
||||
let query: any = { images: { $exists: true, $ne: [] } };
|
||||
let query: Record<string, unknown> = { images: { $exists: true, $ne: [] } };
|
||||
|
||||
if (filter === 'missing') {
|
||||
query = {
|
||||
|
||||
Reference in New Issue
Block a user