debug: add comprehensive logging to recipe image upload flow
All checks were successful
CI / update (push) Successful in 1m23s

Add detailed console logging throughout the image upload pipeline to help
diagnose upload issues:
- Log file metadata and validation steps in imageValidation.ts
- Log image processing and file saving operations in imageProcessing.ts
- Log form data and processing steps in recipe add page action
- Log API request details and upload progress in img/add endpoint

All logs are prefixed with component name ([ImageValidation], [ImageProcessing],
[RecipeAdd], [API:ImgAdd]) for easy filtering and debugging.
This commit is contained in:
2026-01-20 12:27:01 +01:00
parent 8af2fc3f3b
commit d62315ad01
4 changed files with 145 additions and 38 deletions

View File

@@ -36,13 +36,19 @@ export const actions = {
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,
title: recipeData.title
});
// 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,
@@ -52,8 +58,16 @@ export const actions = {
// 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 } = await processAndSaveRecipeImage(
recipeImage,
@@ -61,13 +75,14 @@ export const actions = {
IMAGE_DIR
);
console.log('[RecipeAdd] Image processed successfully, filename:', filename);
recipeData.images = [{
mediapath: filename,
alt: '',
caption: ''
}];
} catch (imageError: any) {
console.error('Image processing error:', imageError);
console.error('[RecipeAdd] Image processing error:', imageError);
return fail(400, {
error: `Failed to process image: ${imageError.message}`,
errors: ['Image processing failed'],
@@ -75,6 +90,7 @@ export const actions = {
});
}
} 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`,

View File

@@ -19,11 +19,15 @@ import { validateImageFile } from '$utils/imageValidation';
* @route POST /api/rezepte/img/add
*/
export const POST = (async ({ request, locals }) => {
console.log('[API:ImgAdd] Image upload request received');
// Check authentication
const auth = await locals.auth();
if (!auth) {
console.error('[API:ImgAdd] Authentication required');
throw error(401, 'Authentication required to upload images');
}
console.log('[API:ImgAdd] Authentication passed');
try {
const formData = await request.formData();
@@ -32,71 +36,99 @@ export const POST = (async ({ request, locals }) => {
const image = formData.get('image') as File;
const name = formData.get('name')?.toString().trim();
console.log('[API:ImgAdd] Form data:', {
hasImage: !!image,
imageSize: image?.size,
imageName: image?.name,
imageType: image?.type,
recipeName: name
});
if (!image) {
console.error('[API:ImgAdd] No image file provided');
throw error(400, 'No image file provided');
}
if (!name) {
console.error('[API:ImgAdd] Image name is required');
throw error(400, 'Image name is required');
}
// Comprehensive security validation
console.log('[API:ImgAdd] Starting validation...');
const validationResult = await validateImageFile(image);
if (!validationResult.valid) {
console.error('[API:ImgAdd] Validation failed:', validationResult.error);
throw error(400, validationResult.error || 'Invalid image file');
}
console.log('[API:ImgAdd] Validation passed');
// Convert File to Buffer for processing
const arrayBuffer = await image.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
console.log('[API:ImgAdd] Buffer created, size:', buffer.length, 'bytes');
// Generate content hash for cache busting
const imageHash = generateImageHashFromBuffer(buffer);
const hashedFilename = getHashedFilename(name, imageHash);
const unhashedFilename = name + '.webp';
console.log('[API:ImgAdd] Generated filenames:', {
hashed: hashedFilename,
unhashed: unhashedFilename
});
// Process image with Sharp - convert to WebP format
// Save full size - both hashed and unhashed versions
console.log('[API:ImgAdd] Processing full size image...');
const fullBuffer = await sharp(buffer)
.toFormat('webp')
.webp({ quality: 90 }) // High quality for full size
.toBuffer();
console.log('[API:ImgAdd] Full size buffer created, size:', fullBuffer.length, 'bytes');
await sharp(fullBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'full', hashedFilename)
);
await sharp(fullBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'full', unhashedFilename)
);
const fullHashedPath = path.join(IMAGE_DIR, 'rezepte', 'full', hashedFilename);
const fullUnhashedPath = path.join(IMAGE_DIR, 'rezepte', 'full', unhashedFilename);
console.log('[API:ImgAdd] Saving full size to:', { fullHashedPath, fullUnhashedPath });
await sharp(fullBuffer).toFile(fullHashedPath);
await sharp(fullBuffer).toFile(fullUnhashedPath);
console.log('[API:ImgAdd] Full size images saved ✓');
// Save thumbnail (800px width) - both hashed and unhashed versions
console.log('[API:ImgAdd] Processing thumbnail...');
const thumbBuffer = await sharp(buffer)
.resize({ width: 800 })
.toFormat('webp')
.webp({ quality: 85 })
.toBuffer();
console.log('[API:ImgAdd] Thumbnail buffer created, size:', thumbBuffer.length, 'bytes');
await sharp(thumbBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'thumb', hashedFilename)
);
await sharp(thumbBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'thumb', unhashedFilename)
);
const thumbHashedPath = path.join(IMAGE_DIR, 'rezepte', 'thumb', hashedFilename);
const thumbUnhashedPath = path.join(IMAGE_DIR, 'rezepte', 'thumb', unhashedFilename);
console.log('[API:ImgAdd] Saving thumbnail to:', { thumbHashedPath, thumbUnhashedPath });
await sharp(thumbBuffer).toFile(thumbHashedPath);
await sharp(thumbBuffer).toFile(thumbUnhashedPath);
console.log('[API:ImgAdd] Thumbnail images saved ✓');
// Save placeholder (20px width) - both hashed and unhashed versions
console.log('[API:ImgAdd] Processing placeholder...');
const placeholderBuffer = await sharp(buffer)
.resize({ width: 20 })
.toFormat('webp')
.webp({ quality: 60 })
.toBuffer();
console.log('[API:ImgAdd] Placeholder buffer created, size:', placeholderBuffer.length, 'bytes');
await sharp(placeholderBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'placeholder', hashedFilename)
);
await sharp(placeholderBuffer).toFile(
path.join(IMAGE_DIR, 'rezepte', 'placeholder', unhashedFilename)
);
const placeholderHashedPath = path.join(IMAGE_DIR, 'rezepte', 'placeholder', hashedFilename);
const placeholderUnhashedPath = path.join(IMAGE_DIR, 'rezepte', 'placeholder', unhashedFilename);
console.log('[API:ImgAdd] Saving placeholder to:', { placeholderHashedPath, placeholderUnhashedPath });
await sharp(placeholderBuffer).toFile(placeholderHashedPath);
await sharp(placeholderBuffer).toFile(placeholderUnhashedPath);
console.log('[API:ImgAdd] Placeholder images saved ✓');
console.log('[API:ImgAdd] Upload completed successfully ✓');
return json({
success: true,
msg: 'Image uploaded successfully',
@@ -108,7 +140,7 @@ export const POST = (async ({ request, locals }) => {
if (err.status) throw err;
// Log and throw generic error for unexpected failures
console.error('Image upload error:', err);
console.error('[API:ImgAdd] Upload error:', err);
throw error(500, `Failed to upload image: ${err.message || 'Unknown error'}`);
}
}) satisfies RequestHandler;