mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:31:12 -05:00
Backfill Ride Data
Add edge function and UI for ride data backfill, integrating admin UI to trigger backfill, plus supporting types and wiring to backfill_ride_data function. Includes RideDataBackfill component, admin page integration, and initial server-side function scaffold for updating rides from submissions.
This commit is contained in:
110
src/components/admin/RideDataBackfill.tsx
Normal file
110
src/components/admin/RideDataBackfill.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
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 { Hammer, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
export function RideDataBackfill() {
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [result, setResult] = useState<{
|
||||
success: boolean;
|
||||
rides_updated: number;
|
||||
manufacturer_added: number;
|
||||
designer_added: number;
|
||||
ride_model_added: number;
|
||||
} | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleBackfill = async () => {
|
||||
setIsRunning(true);
|
||||
setError(null);
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const { data, error: invokeError } = await supabase.functions.invoke(
|
||||
'backfill-ride-data'
|
||||
);
|
||||
|
||||
if (invokeError) throw invokeError;
|
||||
|
||||
setResult(data);
|
||||
|
||||
const updates: string[] = [];
|
||||
if (data.manufacturer_added > 0) updates.push(`${data.manufacturer_added} manufacturers`);
|
||||
if (data.designer_added > 0) updates.push(`${data.designer_added} designers`);
|
||||
if (data.ride_model_added > 0) updates.push(`${data.ride_model_added} ride models`);
|
||||
|
||||
toast({
|
||||
title: 'Backfill Complete',
|
||||
description: `Updated ${data.rides_updated} rides: ${updates.join(', ')}`,
|
||||
});
|
||||
} 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 (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Hammer className="w-5 h-5" />
|
||||
Ride Data Backfill
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Backfill missing manufacturer, designer, and ride model data for approved rides from their submission data
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
This tool will find rides missing manufacturer, designer, or ride model information and populate them using data from their approved submissions. Useful for fixing rides that were approved before relationship data was properly handled.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{result && (
|
||||
<Alert className="border-green-200 bg-green-50 dark:bg-green-950 dark:border-green-800">
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
||||
<AlertDescription className="text-green-900 dark:text-green-100">
|
||||
<div className="font-medium">Backfill completed successfully!</div>
|
||||
<div className="mt-2 space-y-1">
|
||||
<div>Rides updated: {result.rides_updated}</div>
|
||||
<div>Manufacturers added: {result.manufacturer_added}</div>
|
||||
<div>Designers added: {result.designer_added}</div>
|
||||
<div>Ride models added: {result.ride_model_added}</div>
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleBackfill}
|
||||
disabled={isRunning}
|
||||
className="w-full"
|
||||
trackingLabel="run-ride-data-backfill"
|
||||
>
|
||||
<Hammer className="w-4 h-4 mr-2" />
|
||||
{isRunning ? 'Running Backfill...' : 'Run Ride Data Backfill'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -6633,6 +6633,7 @@ export type Database = {
|
||||
Returns: undefined
|
||||
}
|
||||
backfill_park_locations: { Args: never; Returns: Json }
|
||||
backfill_ride_data: { Args: never; Returns: Json }
|
||||
backfill_sort_orders: { Args: never; Returns: undefined }
|
||||
block_aal1_with_mfa: { Args: never; Returns: boolean }
|
||||
can_approve_submission_item: {
|
||||
|
||||
@@ -15,6 +15,7 @@ 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 { RideDataBackfill } from '@/components/admin/RideDataBackfill';
|
||||
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';
|
||||
|
||||
@@ -938,6 +939,7 @@ export default function AdminSettings() {
|
||||
</Card>
|
||||
|
||||
<ParkLocationBackfill />
|
||||
<RideDataBackfill />
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
54
supabase/functions/backfill-ride-data/index.ts
Normal file
54
supabase/functions/backfill-ride-data/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { createEdgeFunction } from '../_shared/edgeFunctionWrapper.ts';
|
||||
import { edgeLogger } from '../_shared/logger.ts';
|
||||
|
||||
export default createEdgeFunction(
|
||||
{
|
||||
name: 'backfill-ride-data',
|
||||
requireAuth: true,
|
||||
},
|
||||
async (req, context, supabase) => {
|
||||
edgeLogger.info('Starting ride data backfill', { requestId: context.requestId });
|
||||
|
||||
// Check if user is superuser
|
||||
const { data: profile, error: profileError } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('role')
|
||||
.eq('id', context.user.id)
|
||||
.single();
|
||||
|
||||
if (profileError || profile?.role !== 'superuser') {
|
||||
edgeLogger.warn('Unauthorized backfill attempt', {
|
||||
userId: context.user.id,
|
||||
requestId: context.requestId
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Unauthorized: Superuser access required' }),
|
||||
{ status: 403, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
|
||||
// Execute the backfill function
|
||||
const { data, error } = await supabase.rpc('backfill_ride_data');
|
||||
|
||||
if (error) {
|
||||
edgeLogger.error('Error running ride data backfill', {
|
||||
error,
|
||||
requestId: context.requestId
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
edgeLogger.info('Ride data backfill completed', {
|
||||
results: data,
|
||||
requestId: context.requestId
|
||||
});
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
...data,
|
||||
}),
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,92 @@
|
||||
-- Function to backfill missing ride data from submission data
|
||||
CREATE OR REPLACE FUNCTION backfill_ride_data()
|
||||
RETURNS jsonb
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_rides_updated INTEGER := 0;
|
||||
v_manufacturer_added INTEGER := 0;
|
||||
v_designer_added INTEGER := 0;
|
||||
v_ride_model_added INTEGER := 0;
|
||||
v_ride RECORD;
|
||||
v_submission RECORD;
|
||||
BEGIN
|
||||
-- Find rides with missing manufacturer, designer, or ride_model that have approved submissions
|
||||
FOR v_ride IN
|
||||
SELECT DISTINCT r.id, r.name, r.slug, r.manufacturer_id, r.designer_id, r.ride_model_id
|
||||
FROM rides r
|
||||
WHERE r.manufacturer_id IS NULL
|
||||
OR r.designer_id IS NULL
|
||||
OR r.ride_model_id IS NULL
|
||||
LOOP
|
||||
-- Find the most recent approved submission for this ride with the missing data
|
||||
SELECT
|
||||
rs.manufacturer_id,
|
||||
rs.designer_id,
|
||||
rs.ride_model_id
|
||||
INTO v_submission
|
||||
FROM ride_submissions rs
|
||||
JOIN content_submissions cs ON cs.id = rs.submission_id
|
||||
WHERE rs.ride_id = v_ride.id
|
||||
AND cs.status = 'approved'
|
||||
AND (
|
||||
(v_ride.manufacturer_id IS NULL AND rs.manufacturer_id IS NOT NULL) OR
|
||||
(v_ride.designer_id IS NULL AND rs.designer_id IS NOT NULL) OR
|
||||
(v_ride.ride_model_id IS NULL AND rs.ride_model_id IS NOT NULL)
|
||||
)
|
||||
ORDER BY cs.created_at DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- If we found submission data, update the ride
|
||||
IF FOUND THEN
|
||||
DECLARE
|
||||
v_updated BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- Update manufacturer if missing
|
||||
IF v_ride.manufacturer_id IS NULL AND v_submission.manufacturer_id IS NOT NULL THEN
|
||||
UPDATE rides
|
||||
SET manufacturer_id = v_submission.manufacturer_id
|
||||
WHERE id = v_ride.id;
|
||||
v_manufacturer_added := v_manufacturer_added + 1;
|
||||
v_updated := TRUE;
|
||||
RAISE NOTICE 'Added manufacturer for ride: % (id: %)', v_ride.name, v_ride.id;
|
||||
END IF;
|
||||
|
||||
-- Update designer if missing
|
||||
IF v_ride.designer_id IS NULL AND v_submission.designer_id IS NOT NULL THEN
|
||||
UPDATE rides
|
||||
SET designer_id = v_submission.designer_id
|
||||
WHERE id = v_ride.id;
|
||||
v_designer_added := v_designer_added + 1;
|
||||
v_updated := TRUE;
|
||||
RAISE NOTICE 'Added designer for ride: % (id: %)', v_ride.name, v_ride.id;
|
||||
END IF;
|
||||
|
||||
-- Update ride_model if missing
|
||||
IF v_ride.ride_model_id IS NULL AND v_submission.ride_model_id IS NOT NULL THEN
|
||||
UPDATE rides
|
||||
SET ride_model_id = v_submission.ride_model_id
|
||||
WHERE id = v_ride.id;
|
||||
v_ride_model_added := v_ride_model_added + 1;
|
||||
v_updated := TRUE;
|
||||
RAISE NOTICE 'Added ride model for ride: % (id: %)', v_ride.name, v_ride.id;
|
||||
END IF;
|
||||
|
||||
IF v_updated THEN
|
||||
v_rides_updated := v_rides_updated + 1;
|
||||
END IF;
|
||||
END;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
RETURN jsonb_build_object(
|
||||
'success', true,
|
||||
'rides_updated', v_rides_updated,
|
||||
'manufacturer_added', v_manufacturer_added,
|
||||
'designer_added', v_designer_added,
|
||||
'ride_model_added', v_ride_model_added
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user