Add Company Backfill Tool

Implement edge function and UI to backfill missing company data from submissions, including admin trigger and result reporting, and wire into admin settings.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 15:48:49 +00:00
parent 314db65591
commit 664c894bb1
5 changed files with 297 additions and 0 deletions

View 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>
);
}

View File

@@ -6632,6 +6632,7 @@ export type Database = {
Args: { target_user_id: string }
Returns: undefined
}
backfill_company_data: { 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 }

View File

@@ -16,6 +16,7 @@ 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 { 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 { useDocumentTitle } from '@/hooks/useDocumentTitle';
@@ -940,6 +941,7 @@ export default function AdminSettings() {
<ParkLocationBackfill />
<RideDataBackfill />
<CompanyDataBackfill />
</div>
</TabsContent>

View 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' } }
);
}
);

View File

@@ -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;
$$;