diff --git a/supabase/functions/process-selective-approval/index.ts b/supabase/functions/process-selective-approval/index.ts index 84ba1a36..5abb68d7 100644 --- a/supabase/functions/process-selective-approval/index.ts +++ b/supabase/functions/process-selective-approval/index.ts @@ -837,107 +837,108 @@ serve(withRateLimit(async (req) => { // Process items in order for (const item of sortedItems) { - try { - edgeLogger.info('Processing item', { action: 'approval_process_item', itemId: item.id, itemType: item.item_type }); - - // Extract data from relational tables based on item_type - let itemData: any; - switch (item.item_type) { - case 'park': - itemData = { - ...(item as any).park_submission, - // Merge temp refs for this item - ...(tempRefsByItemId.get(item.id) || {}) - }; - break; - case 'ride': - itemData = { - ...(item as any).ride_submission, - ...(tempRefsByItemId.get(item.id) || {}) - }; - break; - case 'manufacturer': - case 'operator': - case 'property_owner': - case 'designer': - itemData = { - ...(item as any).company_submission, - ...(tempRefsByItemId.get(item.id) || {}) - }; - break; - case 'ride_model': - itemData = { - ...(item as any).ride_model_submission, - ...(tempRefsByItemId.get(item.id) || {}) - }; - break; - case 'photo': - // Combine photo_submission with its photo_items array - itemData = { - ...(item as any).photo_submission, - photos: (item as any).photo_submission?.photo_items || [], - ...(tempRefsByItemId.get(item.id) || {}) - }; - break; - default: - // For timeline/other items not yet migrated, fall back to item_data (JSONB) - itemData = item.item_data; - } - - if (!itemData && item.item_data) { - // Fallback to item_data if relational data not found (for backwards compatibility) + edgeLogger.info('Processing item', { action: 'approval_process_item', itemId: item.id, itemType: item.item_type }); + + // Extract data from relational tables based on item_type (OUTSIDE try-catch) + let itemData: any; + switch (item.item_type) { + case 'park': + itemData = { + ...(item as any).park_submission, + // Merge temp refs for this item + ...(tempRefsByItemId.get(item.id) || {}) + }; + break; + case 'ride': + itemData = { + ...(item as any).ride_submission, + ...(tempRefsByItemId.get(item.id) || {}) + }; + break; + case 'manufacturer': + case 'operator': + case 'property_owner': + case 'designer': + itemData = { + ...(item as any).company_submission, + ...(tempRefsByItemId.get(item.id) || {}) + }; + break; + case 'ride_model': + itemData = { + ...(item as any).ride_model_submission, + ...(tempRefsByItemId.get(item.id) || {}) + }; + break; + case 'photo': + // Combine photo_submission with its photo_items array + itemData = { + ...(item as any).photo_submission, + photos: (item as any).photo_submission?.photo_items || [], + ...(tempRefsByItemId.get(item.id) || {}) + }; + break; + default: + // For timeline/other items not yet migrated, fall back to item_data (JSONB) itemData = item.item_data; - } + } + + if (!itemData && item.item_data) { + // Fallback to item_data if relational data not found (for backwards compatibility) + itemData = item.item_data; + } - // Log if temp refs were found for this item - if (tempRefsByItemId.has(item.id)) { - edgeLogger.info('Item has temp refs', { - action: 'approval_item_temp_refs', - itemId: item.id, - itemType: item.item_type, - tempRefs: tempRefsByItemId.get(item.id), - requestId: tracking.requestId - }); - } - - // Validate entity data with strict validation, passing original_data for edits - const validation = validateEntityDataStrict(item.item_type, itemData, item.original_data); - - if (validation.blockingErrors.length > 0) { - edgeLogger.error('Blocking validation errors', { - action: 'approval_validation_fail', - itemId: item.id, - errors: validation.blockingErrors, - requestId: tracking.requestId - }); - - // Fail the entire batch if ANY item has blocking errors - return new Response(JSON.stringify({ - success: false, - message: 'Validation failed: Items have blocking errors that must be fixed', - errors: validation.blockingErrors, - failedItemId: item.id, - failedItemType: item.item_type, - requestId: tracking.requestId - }), { - status: 400, - headers: { - ...corsHeaders, - 'Content-Type': 'application/json', - 'X-Request-ID': tracking.requestId - } - }); - } - - if (validation.warnings.length > 0) { - edgeLogger.warn('Validation warnings', { - action: 'approval_validation_warning', - itemId: item.id, - warnings: validation.warnings - }); - // Continue processing - warnings don't block approval - } + // Log if temp refs were found for this item + if (tempRefsByItemId.has(item.id)) { + edgeLogger.info('Item has temp refs', { + action: 'approval_item_temp_refs', + itemId: item.id, + itemType: item.item_type, + tempRefs: tempRefsByItemId.get(item.id), + requestId: tracking.requestId + }); + } + + // Validate entity data BEFORE entering try-catch (so 400 returns immediately) + const validation = validateEntityDataStrict(item.item_type, itemData, item.original_data); + + if (validation.blockingErrors.length > 0) { + edgeLogger.error('Blocking validation errors', { + action: 'approval_validation_fail', + itemId: item.id, + errors: validation.blockingErrors, + requestId: tracking.requestId + }); + // Return 400 immediately - NOT caught by try-catch below + return new Response(JSON.stringify({ + success: false, + message: 'Validation failed: Items have blocking errors that must be fixed', + errors: validation.blockingErrors, + failedItemId: item.id, + failedItemType: item.item_type, + requestId: tracking.requestId + }), { + status: 400, + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + } + }); + } + + if (validation.warnings.length > 0) { + edgeLogger.warn('Validation warnings', { + action: 'approval_validation_warning', + itemId: item.id, + warnings: validation.warnings + }); + // Continue processing - warnings don't block approval + } + + // Now enter try-catch ONLY for database operations + try { // Set user context for versioning trigger // This allows create_relational_version() trigger to capture the submitter const { error: setUserIdError } = await supabase.rpc('set_config_value', {