import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; interface ApprovalRequest { itemIds: string[]; userId: string; submissionId: string; } serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { const supabase = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' ); const { itemIds, userId, submissionId }: ApprovalRequest = await req.json(); console.log('Processing selective approval:', { itemIds, userId, submissionId }); // Fetch all items for the submission const { data: items, error: fetchError } = await supabase .from('submission_items') .select('*') .in('id', itemIds); if (fetchError) { throw new Error(`Failed to fetch items: ${fetchError.message}`); } // Topologically sort items by dependencies const sortedItems = topologicalSort(items); const dependencyMap = new Map(); const approvalResults = []; // Process items in order for (const item of sortedItems) { try { console.log(`Processing item ${item.id} of type ${item.item_type}`); // Resolve dependencies in item data const resolvedData = resolveDependencies(item.item_data, dependencyMap); let entityId: string | null = null; // Create entity based on type switch (item.item_type) { case 'park': entityId = await createPark(supabase, resolvedData); break; case 'ride': entityId = await createRide(supabase, resolvedData); break; case 'manufacturer': case 'operator': case 'property_owner': case 'designer': entityId = await createCompany(supabase, resolvedData, item.item_type); break; case 'ride_model': entityId = await createRideModel(supabase, resolvedData); break; case 'photo': await approvePhotos(supabase, resolvedData, item.id); entityId = item.id; // Use item ID as entity ID for photos break; default: throw new Error(`Unknown item type: ${item.item_type}`); } if (entityId) { dependencyMap.set(item.id, entityId); } // Update item status const { error: updateError } = await supabase .from('submission_items') .update({ status: 'approved', approved_entity_id: entityId, updated_at: new Date().toISOString() }) .eq('id', item.id); if (updateError) { throw new Error(`Failed to update item status: ${updateError.message}`); } approvalResults.push({ itemId: item.id, entityId, itemType: item.item_type, success: true }); console.log(`Successfully approved item ${item.id} -> entity ${entityId}`); } catch (error) { console.error(`Error processing item ${item.id}:`, error); approvalResults.push({ itemId: item.id, itemType: item.item_type, success: false, error: error.message }); } } // Update submission status const allApproved = approvalResults.every(r => r.success); const { error: submissionError } = await supabase .from('content_submissions') .update({ status: allApproved ? 'approved' : 'partially_approved', reviewer_id: userId, reviewed_at: new Date().toISOString() }) .eq('id', submissionId); if (submissionError) { console.error('Failed to update submission status:', submissionError); } return new Response( JSON.stringify({ success: true, results: approvalResults, submissionStatus: allApproved ? 'approved' : 'partially_approved' }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } catch (error) { console.error('Error in process-selective-approval:', error); return new Response( JSON.stringify({ error: error.message }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } }); // Helper functions function topologicalSort(items: any[]): any[] { const sorted: any[] = []; const visited = new Set(); const visiting = new Set(); const visit = (item: any) => { if (visited.has(item.id)) return; if (visiting.has(item.id)) { throw new Error(`Circular dependency detected for item ${item.id}`); } visiting.add(item.id); if (item.depends_on) { const parent = items.find(i => i.id === item.depends_on); if (parent) { visit(parent); } } visiting.delete(item.id); visited.add(item.id); sorted.push(item); }; items.forEach(item => visit(item)); return sorted; } function resolveDependencies(data: any, dependencyMap: Map): any { if (typeof data !== 'object' || data === null) { return data; } if (Array.isArray(data)) { return data.map(item => resolveDependencies(item, dependencyMap)); } const resolved: any = {}; for (const [key, value] of Object.entries(data)) { if (typeof value === 'string' && dependencyMap.has(value)) { resolved[key] = dependencyMap.get(value); } else { resolved[key] = resolveDependencies(value, dependencyMap); } } return resolved; } async function createPark(supabase: any, data: any): Promise { const { data: park, error } = await supabase .from('parks') .insert(data) .select('id') .single(); if (error) throw new Error(`Failed to create park: ${error.message}`); return park.id; } async function createRide(supabase: any, data: any): Promise { const { data: ride, error } = await supabase .from('rides') .insert(data) .select('id') .single(); if (error) throw new Error(`Failed to create ride: ${error.message}`); return ride.id; } async function createCompany(supabase: any, data: any, companyType: string): Promise { const companyData = { ...data, company_type: companyType }; const { data: company, error } = await supabase .from('companies') .insert(companyData) .select('id') .single(); if (error) throw new Error(`Failed to create company: ${error.message}`); return company.id; } async function createRideModel(supabase: any, data: any): Promise { const { data: model, error } = await supabase .from('ride_models') .insert(data) .select('id') .single(); if (error) throw new Error(`Failed to create ride model: ${error.message}`); return model.id; } async function approvePhotos(supabase: any, data: any, submissionItemId: string): Promise { const photos = data.photos || []; for (const photo of photos) { const photoData = { entity_id: data.entity_id, entity_type: data.context, cloudflare_image_id: extractImageId(photo.url), cloudflare_image_url: photo.url, title: photo.title, caption: photo.caption, date_taken: photo.date, order_index: photo.order, submission_id: submissionItemId }; const { error } = await supabase.from('photos').insert(photoData); if (error) { console.error('Failed to insert photo:', error); throw new Error(`Failed to insert photo: ${error.message}`); } } } function extractImageId(url: string): string { const matches = url.match(/\/([^\/]+)\/public$/); return matches ? matches[1] : url; }