mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 15:27:00 -05:00
Compare commits
3 Commits
054348b9c4
...
664c894bb1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
664c894bb1 | ||
|
|
314db65591 | ||
|
|
d48e95ee7c |
116
src/components/admin/CompanyDataBackfill.tsx
Normal file
116
src/components/admin/CompanyDataBackfill.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
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 { Building2, AlertCircle, CheckCircle2 } from 'lucide-react';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
|
export function CompanyDataBackfill() {
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
const [result, setResult] = useState<{
|
||||||
|
success: boolean;
|
||||||
|
companies_updated: number;
|
||||||
|
headquarters_added: number;
|
||||||
|
website_added: number;
|
||||||
|
founded_year_added: number;
|
||||||
|
description_added: number;
|
||||||
|
logo_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-company-data'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invokeError) throw invokeError;
|
||||||
|
|
||||||
|
setResult(data);
|
||||||
|
|
||||||
|
const updates: string[] = [];
|
||||||
|
if (data.headquarters_added > 0) updates.push(`${data.headquarters_added} headquarters`);
|
||||||
|
if (data.website_added > 0) updates.push(`${data.website_added} websites`);
|
||||||
|
if (data.founded_year_added > 0) updates.push(`${data.founded_year_added} founding years`);
|
||||||
|
if (data.description_added > 0) updates.push(`${data.description_added} descriptions`);
|
||||||
|
if (data.logo_added > 0) updates.push(`${data.logo_added} logos`);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Backfill Complete',
|
||||||
|
description: `Updated ${data.companies_updated} companies: ${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">
|
||||||
|
<Building2 className="w-5 h-5" />
|
||||||
|
Company Data Backfill
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Backfill missing headquarters, website, founding year, description, and logo data for companies from their submission data
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert>
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
This tool will find companies (operators, manufacturers, designers) missing basic information and populate them using data from their approved submissions. Useful for fixing companies that were approved before all fields were 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>Companies updated: {result.companies_updated}</div>
|
||||||
|
<div>Headquarters added: {result.headquarters_added}</div>
|
||||||
|
<div>Websites added: {result.website_added}</div>
|
||||||
|
<div>Founding years added: {result.founded_year_added}</div>
|
||||||
|
<div>Descriptions added: {result.description_added}</div>
|
||||||
|
<div>Logos added: {result.logo_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-company-data-backfill"
|
||||||
|
>
|
||||||
|
<Building2 className="w-4 h-4 mr-2" />
|
||||||
|
{isRunning ? 'Running Backfill...' : 'Run Company Data Backfill'}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
100
src/components/admin/ParkLocationBackfill.tsx
Normal file
100
src/components/admin/ParkLocationBackfill.tsx
Normal file
@@ -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<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-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 (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<MapPin className="w-5 h-5" />
|
||||||
|
Park Location Backfill
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Backfill missing location data for approved parks from their submission data
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert>
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
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.
|
||||||
|
</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>Parks updated: {result.parks_updated}</div>
|
||||||
|
<div>Locations created: {result.locations_created}</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-park-location-backfill"
|
||||||
|
>
|
||||||
|
<MapPin className="w-4 h-4 mr-2" />
|
||||||
|
{isRunning ? 'Running Backfill...' : 'Run Location Backfill'}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6632,7 +6632,9 @@ export type Database = {
|
|||||||
Args: { target_user_id: string }
|
Args: { target_user_id: string }
|
||||||
Returns: undefined
|
Returns: undefined
|
||||||
}
|
}
|
||||||
|
backfill_company_data: { Args: never; Returns: Json }
|
||||||
backfill_park_locations: { Args: never; Returns: Json }
|
backfill_park_locations: { Args: never; Returns: Json }
|
||||||
|
backfill_ride_data: { Args: never; Returns: Json }
|
||||||
backfill_sort_orders: { Args: never; Returns: undefined }
|
backfill_sort_orders: { Args: never; Returns: undefined }
|
||||||
block_aal1_with_mfa: { Args: never; Returns: boolean }
|
block_aal1_with_mfa: { Args: never; Returns: boolean }
|
||||||
can_approve_submission_item: {
|
can_approve_submission_item: {
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import { useAdminSettings } from '@/hooks/useAdminSettings';
|
|||||||
import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility';
|
import { NovuMigrationUtility } from '@/components/admin/NovuMigrationUtility';
|
||||||
import { TestDataGenerator } from '@/components/admin/TestDataGenerator';
|
import { TestDataGenerator } from '@/components/admin/TestDataGenerator';
|
||||||
import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner';
|
import { IntegrationTestRunner } from '@/components/admin/IntegrationTestRunner';
|
||||||
|
import { ParkLocationBackfill } from '@/components/admin/ParkLocationBackfill';
|
||||||
|
import { RideDataBackfill } from '@/components/admin/RideDataBackfill';
|
||||||
|
import { CompanyDataBackfill } from '@/components/admin/CompanyDataBackfill';
|
||||||
import { Loader2, Save, Clock, Users, Bell, Shield, Settings, Trash2, Plug, AlertTriangle, Lock, TestTube, RefreshCw, Info, AlertCircle, Database } from 'lucide-react';
|
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';
|
import { useDocumentTitle } from '@/hooks/useDocumentTitle';
|
||||||
|
|
||||||
@@ -935,6 +938,10 @@ export default function AdminSettings() {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<ParkLocationBackfill />
|
||||||
|
<RideDataBackfill />
|
||||||
|
<CompanyDataBackfill />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
54
supabase/functions/backfill-company-data/index.ts
Normal file
54
supabase/functions/backfill-company-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-company-data',
|
||||||
|
requireAuth: true,
|
||||||
|
},
|
||||||
|
async (req, context, supabase) => {
|
||||||
|
edgeLogger.info('Starting company 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_company_data');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
edgeLogger.error('Error running company data backfill', {
|
||||||
|
error,
|
||||||
|
requestId: context.requestId
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeLogger.info('Company data backfill completed', {
|
||||||
|
results: data,
|
||||||
|
requestId: context.requestId
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
...data,
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
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,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;
|
||||||
|
$$;
|
||||||
@@ -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;
|
||||||
|
$$;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
-- Function to backfill missing company data from submission data
|
||||||
|
CREATE OR REPLACE FUNCTION backfill_company_data()
|
||||||
|
RETURNS jsonb
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_companies_updated INTEGER := 0;
|
||||||
|
v_headquarters_added INTEGER := 0;
|
||||||
|
v_website_added INTEGER := 0;
|
||||||
|
v_founded_year_added INTEGER := 0;
|
||||||
|
v_description_added INTEGER := 0;
|
||||||
|
v_logo_added INTEGER := 0;
|
||||||
|
v_company RECORD;
|
||||||
|
v_submission RECORD;
|
||||||
|
BEGIN
|
||||||
|
-- Find companies with missing data that have approved submissions
|
||||||
|
FOR v_company IN
|
||||||
|
SELECT DISTINCT c.id, c.name, c.slug,
|
||||||
|
c.headquarters_location, c.website_url, c.founded_year,
|
||||||
|
c.description, c.logo_url
|
||||||
|
FROM companies c
|
||||||
|
WHERE c.headquarters_location IS NULL
|
||||||
|
OR c.website_url IS NULL
|
||||||
|
OR c.founded_year IS NULL
|
||||||
|
OR c.description IS NULL
|
||||||
|
OR c.logo_url IS NULL
|
||||||
|
LOOP
|
||||||
|
-- Find the most recent approved submission for this company with the missing data
|
||||||
|
SELECT
|
||||||
|
cs.headquarters_location,
|
||||||
|
cs.website_url,
|
||||||
|
cs.founded_year,
|
||||||
|
cs.description,
|
||||||
|
cs.logo_url
|
||||||
|
INTO v_submission
|
||||||
|
FROM company_submissions cs
|
||||||
|
JOIN content_submissions sub ON sub.id = cs.submission_id
|
||||||
|
WHERE cs.company_id = v_company.id
|
||||||
|
AND sub.status = 'approved'
|
||||||
|
AND (
|
||||||
|
(v_company.headquarters_location IS NULL AND cs.headquarters_location IS NOT NULL) OR
|
||||||
|
(v_company.website_url IS NULL AND cs.website_url IS NOT NULL) OR
|
||||||
|
(v_company.founded_year IS NULL AND cs.founded_year IS NOT NULL) OR
|
||||||
|
(v_company.description IS NULL AND cs.description IS NOT NULL) OR
|
||||||
|
(v_company.logo_url IS NULL AND cs.logo_url IS NOT NULL)
|
||||||
|
)
|
||||||
|
ORDER BY sub.created_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- If we found submission data, update the company
|
||||||
|
IF FOUND THEN
|
||||||
|
DECLARE
|
||||||
|
v_updated BOOLEAN := FALSE;
|
||||||
|
BEGIN
|
||||||
|
-- Update headquarters_location if missing
|
||||||
|
IF v_company.headquarters_location IS NULL AND v_submission.headquarters_location IS NOT NULL THEN
|
||||||
|
UPDATE companies
|
||||||
|
SET headquarters_location = v_submission.headquarters_location
|
||||||
|
WHERE id = v_company.id;
|
||||||
|
v_headquarters_added := v_headquarters_added + 1;
|
||||||
|
v_updated := TRUE;
|
||||||
|
RAISE NOTICE 'Added headquarters for company: % (id: %)', v_company.name, v_company.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Update website_url if missing
|
||||||
|
IF v_company.website_url IS NULL AND v_submission.website_url IS NOT NULL THEN
|
||||||
|
UPDATE companies
|
||||||
|
SET website_url = v_submission.website_url
|
||||||
|
WHERE id = v_company.id;
|
||||||
|
v_website_added := v_website_added + 1;
|
||||||
|
v_updated := TRUE;
|
||||||
|
RAISE NOTICE 'Added website for company: % (id: %)', v_company.name, v_company.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Update founded_year if missing
|
||||||
|
IF v_company.founded_year IS NULL AND v_submission.founded_year IS NOT NULL THEN
|
||||||
|
UPDATE companies
|
||||||
|
SET founded_year = v_submission.founded_year
|
||||||
|
WHERE id = v_company.id;
|
||||||
|
v_founded_year_added := v_founded_year_added + 1;
|
||||||
|
v_updated := TRUE;
|
||||||
|
RAISE NOTICE 'Added founded year for company: % (id: %)', v_company.name, v_company.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Update description if missing
|
||||||
|
IF v_company.description IS NULL AND v_submission.description IS NOT NULL THEN
|
||||||
|
UPDATE companies
|
||||||
|
SET description = v_submission.description
|
||||||
|
WHERE id = v_company.id;
|
||||||
|
v_description_added := v_description_added + 1;
|
||||||
|
v_updated := TRUE;
|
||||||
|
RAISE NOTICE 'Added description for company: % (id: %)', v_company.name, v_company.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Update logo_url if missing
|
||||||
|
IF v_company.logo_url IS NULL AND v_submission.logo_url IS NOT NULL THEN
|
||||||
|
UPDATE companies
|
||||||
|
SET logo_url = v_submission.logo_url
|
||||||
|
WHERE id = v_company.id;
|
||||||
|
v_logo_added := v_logo_added + 1;
|
||||||
|
v_updated := TRUE;
|
||||||
|
RAISE NOTICE 'Added logo for company: % (id: %)', v_company.name, v_company.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF v_updated THEN
|
||||||
|
v_companies_updated := v_companies_updated + 1;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN jsonb_build_object(
|
||||||
|
'success', true,
|
||||||
|
'companies_updated', v_companies_updated,
|
||||||
|
'headquarters_added', v_headquarters_added,
|
||||||
|
'website_added', v_website_added,
|
||||||
|
'founded_year_added', v_founded_year_added,
|
||||||
|
'description_added', v_description_added,
|
||||||
|
'logo_added', v_logo_added
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
Reference in New Issue
Block a user