diff --git a/supabase/functions/process-selective-approval/index.ts b/supabase/functions/process-selective-approval/index.ts index 2e08f2c7..f4277bd9 100644 --- a/supabase/functions/process-selective-approval/index.ts +++ b/supabase/functions/process-selective-approval/index.ts @@ -836,6 +836,12 @@ serve(withRateLimit(async (req) => { error?: string; isDependencyFailure?: boolean; }> = []; + // Track all created entities for rollback on failure + const createdEntities: Array<{ + entityId: string; + entityType: string; + tableName: string; + }> = []; // Process items in order for (const item of sortedItems) { @@ -1053,6 +1059,27 @@ serve(withRateLimit(async (req) => { if (entityId) { dependencyMap.set(item.id, entityId); + + // Track created entity for potential rollback + const tableMap: Record = { + 'park': 'parks', + 'ride': 'rides', + 'manufacturer': 'companies', + 'operator': 'companies', + 'property_owner': 'companies', + 'designer': 'companies', + 'ride_model': 'ride_models' + // photo operations don't create new entities in standard tables + }; + + const tableName = tableMap[item.item_type]; + if (tableName) { + createdEntities.push({ + entityId, + entityType: item.item_type, + tableName + }); + } } // Store result for batch update later @@ -1088,9 +1115,105 @@ serve(withRateLimit(async (req) => { error: errorMessage, isDependencyFailure: isDependencyError }); + + // CRITICAL: Rollback all previously created entities + if (createdEntities.length > 0) { + edgeLogger.error('Item failed - initiating rollback', { + action: 'approval_rollback_start', + failedItemId: item.id, + failedItemType: item.item_type, + createdEntitiesCount: createdEntities.length, + error: errorMessage, + requestId: tracking.requestId + }); + + // Delete all previously created entities in reverse order + for (let i = createdEntities.length - 1; i >= 0; i--) { + const entity = createdEntities[i]; + try { + const { error: deleteError } = await supabase + .from(entity.tableName) + .delete() + .eq('id', entity.entityId); + + if (deleteError) { + edgeLogger.error('Rollback delete failed', { + action: 'approval_rollback_delete_fail', + entityId: entity.entityId, + entityType: entity.entityType, + tableName: entity.tableName, + error: deleteError.message, + requestId: tracking.requestId + }); + } else { + edgeLogger.info('Rollback delete success', { + action: 'approval_rollback_delete_success', + entityId: entity.entityId, + entityType: entity.entityType, + requestId: tracking.requestId + }); + } + } catch (rollbackError: unknown) { + const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : 'Unknown rollback error'; + edgeLogger.error('Rollback exception', { + action: 'approval_rollback_exception', + entityId: entity.entityId, + error: rollbackMessage, + requestId: tracking.requestId + }); + } + } + + edgeLogger.info('Rollback complete', { + action: 'approval_rollback_complete', + deletedCount: createdEntities.length, + requestId: tracking.requestId + }); + } + + // Break the loop - don't process remaining items + break; } } + // Check if any item failed - if so, return early with failure + const failedResults = approvalResults.filter(r => !r.success); + if (failedResults.length > 0) { + const failedItem = failedResults[0]; + edgeLogger.error('Approval failed - transaction rolled back', { + action: 'approval_transaction_fail', + failedItemId: failedItem.itemId, + failedItemType: failedItem.itemType, + error: failedItem.error, + rolledBackEntities: createdEntities.length, + requestId: tracking.requestId + }); + + const duration = endRequest(tracking); + return new Response( + JSON.stringify({ + success: false, + message: 'Approval failed and all changes have been rolled back', + error: failedItem.error, + failedItemId: failedItem.itemId, + failedItemType: failedItem.itemType, + isDependencyFailure: failedItem.isDependencyFailure, + rolledBackEntities: createdEntities.length, + requestId: tracking.requestId, + duration + }), + { + status: 500, + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + } + } + ); + } + + // All items succeeded - proceed with batch updates // Batch update all approved items const approvedItemIds = approvalResults.filter(r => r.success).map(r => r.itemId); if (approvedItemIds.length > 0) {