4a931c7e30
CI / update (push) Successful in 1m54s
Add type annotations, JSDoc types, null checks, and proper generics to eliminate all svelte-check errors. Key changes include: - Type $state(null) variables to avoid 'never' inference - Add JSDoc typedefs for plain <script> components - Fix mongoose model typing with Model<any> to avoid union complexity - Add App.Error/App.PageState interfaces in app.d.ts - Fix tuple types to array types in types.ts - Type catch block errors and API handler params - Add null safety for DOM queries and optional chaining - Add standard line-clamp property alongside -webkit- prefix
155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
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 {
|
|
extractRecipeFromFormData,
|
|
validateRecipeData,
|
|
serializeRecipeForDatabase
|
|
} from '$utils/recipeFormHelpers';
|
|
|
|
export const load: PageServerLoad = async ({locals, params}) => {
|
|
// Add is German-only - redirect to German version
|
|
if (params.recipeLang === 'recipes') {
|
|
throw redirect(301, '/rezepte/add');
|
|
}
|
|
|
|
const session = await locals.auth();
|
|
return {
|
|
user: session?.user
|
|
};
|
|
};
|
|
|
|
export const actions = {
|
|
default: async ({ request, locals, params }) => {
|
|
// Check authentication
|
|
const auth = await locals.auth();
|
|
if (!auth) {
|
|
return fail(401, {
|
|
error: 'You must be logged in to add recipes',
|
|
requiresAuth: true
|
|
});
|
|
}
|
|
|
|
try {
|
|
const formData = await request.formData();
|
|
console.log('[RecipeAdd] Form data received');
|
|
|
|
// Extract recipe data from FormData
|
|
const recipeData = extractRecipeFromFormData(formData);
|
|
console.log('[RecipeAdd] Recipe data extracted:', {
|
|
short_name: recipeData.short_name,
|
|
name: recipeData.name
|
|
});
|
|
|
|
// Validate required fields
|
|
const validationErrors = validateRecipeData(recipeData);
|
|
if (validationErrors.length > 0) {
|
|
console.error('[RecipeAdd] Validation errors:', validationErrors);
|
|
return fail(400, {
|
|
error: validationErrors.join(', '),
|
|
errors: validationErrors,
|
|
values: Object.fromEntries(formData)
|
|
});
|
|
}
|
|
|
|
// Handle optional image upload
|
|
const recipeImage = formData.get('recipe_image') as File | null;
|
|
console.log('[RecipeAdd] Recipe image from form:', {
|
|
hasImage: !!recipeImage,
|
|
size: recipeImage?.size,
|
|
name: recipeImage?.name,
|
|
type: recipeImage?.type
|
|
});
|
|
|
|
if (recipeImage && recipeImage.size > 0) {
|
|
try {
|
|
console.log('[RecipeAdd] Starting image processing...');
|
|
// Process and save the image
|
|
const { filename, color } = await processAndSaveRecipeImage(
|
|
recipeImage,
|
|
recipeData.short_name,
|
|
IMAGE_DIR
|
|
);
|
|
|
|
console.log('[RecipeAdd] Image processed successfully, filename:', filename);
|
|
recipeData.images = [{
|
|
mediapath: filename,
|
|
alt: '',
|
|
caption: '',
|
|
color
|
|
}];
|
|
} catch (imageError: any) {
|
|
console.error('[RecipeAdd] Image processing error:', imageError);
|
|
return fail(400, {
|
|
error: `Failed to process image: ${imageError.message}`,
|
|
errors: ['Image processing failed'],
|
|
values: Object.fromEntries(formData)
|
|
});
|
|
}
|
|
} else {
|
|
console.log('[RecipeAdd] No image uploaded, using placeholder');
|
|
// No image uploaded - use placeholder based on short_name
|
|
recipeData.images = [{
|
|
mediapath: `${recipeData.short_name}.webp`,
|
|
alt: '',
|
|
caption: ''
|
|
}];
|
|
}
|
|
|
|
// Serialize for database
|
|
const recipe_json = serializeRecipeForDatabase(recipeData);
|
|
|
|
// Connect to database and create recipe
|
|
await dbConnect();
|
|
|
|
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: any) {
|
|
// Re-throw redirects (they're not errors)
|
|
if (dbError?.status >= 300 && dbError?.status < 400) {
|
|
throw dbError;
|
|
}
|
|
|
|
console.error('Database error creating recipe:', dbError);
|
|
|
|
// Check for duplicate key error
|
|
if (dbError.code === 11000) {
|
|
return fail(400, {
|
|
error: `A recipe with the short name "${recipeData.short_name}" already exists. Please choose a different short name.`,
|
|
errors: ['Duplicate short_name'],
|
|
values: Object.fromEntries(formData)
|
|
});
|
|
}
|
|
|
|
return fail(500, {
|
|
error: `Failed to create recipe: ${dbError.message || 'Unknown database error'}`,
|
|
errors: [dbError.message],
|
|
values: Object.fromEntries(formData)
|
|
});
|
|
}
|
|
} catch (error: any) {
|
|
// Re-throw redirects (they're not errors)
|
|
if (error?.status >= 300 && error?.status < 400) {
|
|
throw error;
|
|
}
|
|
|
|
console.error('Error processing recipe submission:', error);
|
|
|
|
return fail(500, {
|
|
error: `Failed to process recipe: ${error.message || 'Unknown error'}`,
|
|
errors: [error.message]
|
|
});
|
|
}
|
|
}
|
|
} satisfies Actions;
|