feat: Implement comprehensive request tracking and state management

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 12:51:44 +00:00
parent 12433e49e3
commit 74860c6774
8 changed files with 400 additions and 77 deletions

View File

@@ -1,5 +1,7 @@
import { supabase } from '@/integrations/supabase/client';
import { invokeWithTracking } from './edgeFunctionTracking';
import type { UploadedImage } from '@/components/upload/EntityMultiImageUploader';
import { logger } from './logger';
export interface CloudflareUploadResponse {
result: {
@@ -25,16 +27,28 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
if (image.isLocal && image.file) {
const fileName = image.file.name;
// Step 1: Get upload URL from our Supabase Edge Function
const { data: uploadUrlData, error: urlError } = await supabase.functions.invoke('upload-image', {
body: { action: 'get-upload-url' }
});
// Step 1: Get upload URL from our Supabase Edge Function (with tracking)
const { data: uploadUrlData, error: urlError, requestId } = await invokeWithTracking(
'upload-image',
{ action: 'get-upload-url' }
);
if (urlError || !uploadUrlData?.uploadURL) {
console.error(`imageUploadHelper.uploadPendingImages: Failed to get upload URL for "${fileName}":`, urlError);
logger.error('Failed to get upload URL', {
action: 'upload_pending_images',
fileName,
requestId,
error: urlError?.message || 'Unknown error',
});
throw new Error(`Failed to get upload URL for "${fileName}": ${urlError?.message || 'Unknown error'}`);
}
logger.info('Got upload URL', {
action: 'upload_pending_images',
fileName,
requestId,
});
// Step 2: Upload file directly to Cloudflare
const formData = new FormData();
formData.append('file', image.file);
@@ -46,17 +60,31 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
if (!uploadResponse.ok) {
const errorText = await uploadResponse.text();
console.error(`imageUploadHelper.uploadPendingImages: Upload failed for "${fileName}" (status ${uploadResponse.status}):`, errorText);
logger.error('Cloudflare upload failed', {
action: 'upload_pending_images',
fileName,
status: uploadResponse.status,
error: errorText,
});
throw new Error(`Upload failed for "${fileName}" (status ${uploadResponse.status}): ${errorText}`);
}
const result: CloudflareUploadResponse = await uploadResponse.json();
if (!result.success || !result.result) {
console.error(`imageUploadHelper.uploadPendingImages: Cloudflare upload unsuccessful for "${fileName}"`);
logger.error('Cloudflare upload unsuccessful', {
action: 'upload_pending_images',
fileName,
});
throw new Error(`Cloudflare upload returned unsuccessful response for "${fileName}"`);
}
logger.info('Image uploaded successfully', {
action: 'upload_pending_images',
fileName,
imageId: result.result.id,
});
// Clean up object URL
URL.revokeObjectURL(image.url);
@@ -106,13 +134,18 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
// If any uploads failed, clean up ONLY newly uploaded images and throw error
if (errors.length > 0) {
if (newlyUploadedImageIds.length > 0) {
console.error(`imageUploadHelper.uploadPendingImages: Some uploads failed. Cleaning up ${newlyUploadedImageIds.length} newly uploaded images...`);
logger.error('Some uploads failed, cleaning up', {
action: 'upload_pending_images',
newlyUploadedCount: newlyUploadedImageIds.length,
failureCount: errors.length,
});
// Attempt cleanup in parallel with detailed error tracking
const cleanupResults = await Promise.allSettled(
newlyUploadedImageIds.map(imageId =>
supabase.functions.invoke('upload-image', {
body: { action: 'delete', imageId }
invokeWithTracking('upload-image', {
action: 'delete',
imageId,
})
)
);
@@ -120,13 +153,17 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
// Track cleanup failures for better debugging
const cleanupFailures = cleanupResults.filter(r => r.status === 'rejected');
if (cleanupFailures.length > 0) {
console.error(
`imageUploadHelper.uploadPendingImages: Failed to cleanup ${cleanupFailures.length} of ${newlyUploadedImageIds.length} images.`,
'These images may remain orphaned in Cloudflare:',
newlyUploadedImageIds.filter((_, i) => cleanupResults[i].status === 'rejected')
);
logger.error('Failed to cleanup images', {
action: 'upload_pending_images_cleanup',
cleanupFailures: cleanupFailures.length,
totalCleanup: newlyUploadedImageIds.length,
orphanedImages: newlyUploadedImageIds.filter((_, i) => cleanupResults[i].status === 'rejected'),
});
} else {
console.log(`imageUploadHelper.uploadPendingImages: Successfully cleaned up ${newlyUploadedImageIds.length} images.`);
logger.info('Successfully cleaned up images', {
action: 'upload_pending_images_cleanup',
cleanedCount: newlyUploadedImageIds.length,
});
}
}