diff --git a/src/lib/entitySubmissionHelpers.ts b/src/lib/entitySubmissionHelpers.ts index f5890a96..d5d7021f 100644 --- a/src/lib/entitySubmissionHelpers.ts +++ b/src/lib/entitySubmissionHelpers.ts @@ -347,6 +347,29 @@ async function submitCompositeCreation( depends_on: null // Will be set by RPC based on refs }); + // Pre-validation to catch issues early with actionable error messages + if (uploadedPrimary.type === 'park') { + if (!primaryData.name) throw new Error('Park name is required'); + if (!primaryData.slug) throw new Error('Park slug is required'); + if (!primaryData.park_type) throw new Error('Park type is required'); + if (!primaryData.status) throw new Error('Park status is required'); + } else if (uploadedPrimary.type === 'ride') { + if (!primaryData.name) throw new Error('Ride name is required'); + if (!primaryData.slug) throw new Error('Ride slug is required'); + if (!primaryData.status) throw new Error('Ride status is required'); + } + + // Validate dependencies + for (const dep of uploadedDependencies) { + if (dep.type === 'company') { + if (!dep.data.name) throw new Error(`${dep.companyType || 'Company'} name is required`); + if (!dep.data.slug) throw new Error(`${dep.companyType || 'Company'} slug is required`); + if (!dep.data.company_type && !dep.companyType) { + throw new Error('Company type is required'); + } + } + } + // Use RPC to create submission with items atomically const { data: result, error } = await supabase.rpc('create_submission_with_items', { p_user_id: userId, @@ -356,11 +379,30 @@ async function submitCompositeCreation( }); if (error) { - handleError(error, { + // Extract Supabase error details for better error logging + const supabaseError = error as { message?: string; code?: string; details?: string; hint?: string }; + const errorMessage = supabaseError.message || 'Unknown error'; + const errorCode = supabaseError.code; + const errorDetails = supabaseError.details; + const errorHint = supabaseError.hint; + + // Create proper Error instance with enhanced context + const enhancedError = new Error( + `Composite submission failed: ${errorMessage}${errorDetails ? `\nDetails: ${errorDetails}` : ''}${errorHint ? `\nHint: ${errorHint}` : ''}` + ); + + handleError(enhancedError, { action: 'Composite submission', - metadata: { primaryType: uploadedPrimary.type, dependencyCount: dependencies.length }, + metadata: { + primaryType: uploadedPrimary.type, + dependencyCount: dependencies.length, + supabaseCode: errorCode, + supabaseDetails: errorDetails, + supabaseHint: errorHint + }, }); - throw new Error(`Failed to create composite submission: ${error instanceof Error ? error.message : 'Unknown error'}`); + + throw enhancedError; } return { submitted: true, submissionId: result }; diff --git a/src/lib/errorHandler.ts b/src/lib/errorHandler.ts index 87a9f250..923e58cd 100644 --- a/src/lib/errorHandler.ts +++ b/src/lib/errorHandler.ts @@ -30,23 +30,60 @@ export const handleError = ( const errorId = (context.metadata?.requestId as string) || crypto.randomUUID(); const shortErrorId = errorId.slice(0, 8); - const errorMessage = error instanceof AppError - ? error.userMessage || error.message - : error instanceof Error - ? error.message - : 'An unexpected error occurred'; + // Enhanced error message and stack extraction + let errorMessage: string; + let stack: string | undefined; + let errorName = 'UnknownError'; + + if (error instanceof Error) { + errorMessage = error instanceof AppError + ? error.userMessage || error.message + : error.message; + stack = error.stack; + errorName = error.name; + } else if (error && typeof error === 'object') { + // Handle Supabase errors (plain objects with message/code/details) + const supabaseError = error as { + message?: string; + code?: string; + details?: string; + hint?: string; + }; + + errorMessage = supabaseError.message || 'An unexpected error occurred'; + errorName = 'SupabaseError'; + + // Create synthetic stack trace for Supabase errors to aid debugging + if (supabaseError.code || supabaseError.details || supabaseError.hint) { + const stackParts = [ + `SupabaseError: ${errorMessage}`, + supabaseError.code ? ` Code: ${supabaseError.code}` : null, + supabaseError.details ? ` Details: ${supabaseError.details}` : null, + supabaseError.hint ? ` Hint: ${supabaseError.hint}` : null, + ` at ${context.action}`, + ` Reference ID: ${errorId}` + ].filter(Boolean); + + stack = stackParts.join('\n'); + } + } else if (typeof error === 'string') { + errorMessage = error; + } else { + errorMessage = 'An unexpected error occurred'; + } // Log to console/monitoring with enhanced debugging - const stack = error instanceof Error ? error.stack : undefined; logger.error('Error occurred', { ...context, - error: error instanceof Error ? error.message : String(error), + error: errorMessage, stack, errorId, + errorName, errorType: typeof error, errorConstructor: error?.constructor?.name, hasStack: !!stack, + isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack), }); // Additional debug logging when stack is missing @@ -72,9 +109,9 @@ export const handleError = ( p_endpoint: context.action, p_method: 'ERROR', p_status_code: 500, - p_error_type: error instanceof Error ? error.name : 'UnknownError', + p_error_type: errorName, p_error_message: errorMessage, - p_error_stack: error instanceof Error ? error.stack : undefined, + p_error_stack: stack, p_user_agent: navigator.userAgent, p_breadcrumbs: JSON.stringify(breadcrumbs), p_timezone: envContext.timezone,