diff --git a/src/components/admin/ParkLocationBackfill.tsx b/src/components/admin/ParkLocationBackfill.tsx new file mode 100644 index 00000000..42eff262 --- /dev/null +++ b/src/components/admin/ParkLocationBackfill.tsx @@ -0,0 +1,100 @@ +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { supabase } from '@/lib/supabaseClient'; +import { MapPin, AlertCircle, CheckCircle2 } from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; + +export function ParkLocationBackfill() { + const [isRunning, setIsRunning] = useState(false); + const [result, setResult] = useState<{ + success: boolean; + parks_updated: number; + locations_created: number; + } | null>(null); + const [error, setError] = useState(null); + const { toast } = useToast(); + + const handleBackfill = async () => { + setIsRunning(true); + setError(null); + setResult(null); + + try { + const { data, error: invokeError } = await supabase.functions.invoke( + 'backfill-park-locations' + ); + + if (invokeError) throw invokeError; + + setResult(data); + toast({ + title: 'Backfill Complete', + description: `Updated ${data.parks_updated} parks with ${data.locations_created} new locations`, + }); + } catch (err: any) { + const errorMessage = err.message || 'Failed to run backfill'; + setError(errorMessage); + toast({ + title: 'Backfill Failed', + description: errorMessage, + variant: 'destructive', + }); + } finally { + setIsRunning(false); + } + }; + + return ( + + + + + Park Location Backfill + + + Backfill missing location data for approved parks from their submission data + + + + + + + This tool will find parks without location data and populate them using the location information from their approved submissions. This is useful for fixing parks that were approved before the location creation fix was implemented. + + + + {result && ( + + + +
Backfill completed successfully!
+
+
Parks updated: {result.parks_updated}
+
Locations created: {result.locations_created}
+
+
+
+ )} + + {error && ( + + + {error} + + )} + + +
+
+ ); +} diff --git a/src/pages/AdminSettings.tsx b/src/pages/AdminSettings.tsx index 0409d3d4..65ac86f0 100644 --- a/src/pages/AdminSettings.tsx +++ b/src/pages/AdminSettings.tsx @@ -14,6 +14,7 @@ import { useAdminSettings } from '@/hooks/useAdminSettings'; import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility'; import { TestDataGenerator } from '@/components/admin/TestDataGenerator'; import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner'; +import { ParkLocationBackfill } from '@/components/admin/ParkLocationBackfill'; import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock, TestTube, RefreshCw, Info, AlertCircle, Database } from 'lucide-react'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; @@ -935,6 +936,8 @@ export default function AdminSettings() { )} + + diff --git a/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql b/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql new file mode 100644 index 00000000..5f994ac0 --- /dev/null +++ b/supabase/migrations/20251111154000_088c612b-4053-4cf4-aa62-05c648fa6275.sql @@ -0,0 +1,78 @@ +-- Fix search_path for backfill_park_locations function +CREATE OR REPLACE FUNCTION backfill_park_locations() +RETURNS jsonb +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_parks_updated INTEGER := 0; + v_locations_created INTEGER := 0; + v_park RECORD; + v_submission RECORD; + v_location_id UUID; +BEGIN + -- Find parks without locations that have approved submissions with location data + FOR v_park IN + SELECT DISTINCT p.id, p.name, p.slug + FROM parks p + WHERE p.location_id IS NULL + LOOP + -- Find the most recent approved submission for this park with location data + SELECT + psl.country, + psl.state_province, + psl.city, + psl.street_address, + psl.postal_code, + psl.latitude, + psl.longitude + INTO v_submission + FROM park_submissions ps + JOIN park_submission_locations psl ON ps.id = psl.park_submission_id + WHERE ps.park_id = v_park.id + AND ps.status = 'approved' + AND psl.country IS NOT NULL + ORDER BY ps.created_at DESC + LIMIT 1; + + -- If we found location data, create a location record + IF FOUND THEN + INSERT INTO locations ( + country, + state_province, + city, + street_address, + postal_code, + latitude, + longitude + ) VALUES ( + v_submission.country, + v_submission.state_province, + v_submission.city, + v_submission.street_address, + v_submission.postal_code, + v_submission.latitude, + v_submission.longitude + ) + RETURNING id INTO v_location_id; + + -- Update the park with the new location + UPDATE parks + SET location_id = v_location_id + WHERE id = v_park.id; + + v_parks_updated := v_parks_updated + 1; + v_locations_created := v_locations_created + 1; + + RAISE NOTICE 'Backfilled location for park: % (id: %)', v_park.name, v_park.id; + END IF; + END LOOP; + + RETURN jsonb_build_object( + 'success', true, + 'parks_updated', v_parks_updated, + 'locations_created', v_locations_created + ); +END; +$$; \ No newline at end of file