From a30511fb50711a0adbbd99cda187dd9220afd706 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:15:12 +0000 Subject: [PATCH] Refactor: Approve lovable tool use --- src/components/admin/TestDataGenerator.tsx | 46 ++++++- src/integrations/supabase/types.ts | 30 +++++ src/lib/testDataGenerator.ts | 79 ++++++++---- supabase/functions/seed-test-data/index.ts | 112 +++++++++++++++++- ...6_5aeef702-0135-42e6-8d05-3f47a8d688c7.sql | 35 ++++++ 5 files changed, 276 insertions(+), 26 deletions(-) create mode 100644 supabase/migrations/20251010131346_5aeef702-0135-42e6-8d05-3f47a8d688c7.sql diff --git a/src/components/admin/TestDataGenerator.tsx b/src/components/admin/TestDataGenerator.tsx index 3c775045..d31587ec 100644 --- a/src/components/admin/TestDataGenerator.tsx +++ b/src/components/admin/TestDataGenerator.tsx @@ -43,7 +43,18 @@ export function TestDataGenerator() { }); const [loading, setLoading] = useState(false); const [results, setResults] = useState(null); - const [stats, setStats] = useState<{ total: number; pending: number; approved: number } | null>(null); + const [stats, setStats] = useState<{ + total: number; + pending: number; + approved: number; + operators: number; + property_owners: number; + manufacturers: number; + designers: number; + parks: number; + rides: number; + ride_models: number; + } | null>(null); const selectedEntityTypes = Object.entries(entityTypes) .filter(([_, enabled]) => enabled) @@ -141,10 +152,35 @@ export function TestDataGenerator() { {stats && ( -
- Total Test Data: {stats.total} - Pending: {stats.pending} - Approved: {stats.approved} +
+
+ Total Test Data: {stats.total} + Pending: {stats.pending} + Approved: {stats.approved} +
+ + {(stats.operators > 0 || stats.property_owners > 0 || stats.manufacturers > 0 || + stats.designers > 0 || stats.parks > 0 || stats.rides > 0 || stats.ride_models > 0) && ( + + +
+

Available Test Dependencies:

+
    + {stats.operators > 0 &&
  • • {stats.operators} test operator{stats.operators > 1 ? 's' : ''}
  • } + {stats.property_owners > 0 &&
  • • {stats.property_owners} test property owner{stats.property_owners > 1 ? 's' : ''}
  • } + {stats.manufacturers > 0 &&
  • • {stats.manufacturers} test manufacturer{stats.manufacturers > 1 ? 's' : ''}
  • } + {stats.designers > 0 &&
  • • {stats.designers} test designer{stats.designers > 1 ? 's' : ''}
  • } + {stats.parks > 0 &&
  • • {stats.parks} test park{stats.parks > 1 ? 's' : ''}
  • } + {stats.rides > 0 &&
  • • {stats.rides} test ride{stats.rides > 1 ? 's' : ''}
  • } + {stats.ride_models > 0 &&
  • • {stats.ride_models} test ride model{stats.ride_models > 1 ? 's' : ''}
  • } +
+

+ Enable "Include Dependencies" to link new entities to these existing test entities. +

+
+
+
+ )}
)} diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index d127ad78..c3e188a7 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -2226,6 +2226,36 @@ export type Database = { }, ] } + test_data_registry: { + Row: { + created_at: string + entity_id: string + entity_slug: string + entity_type: string + id: string + metadata: Json | null + test_session_id: string | null + } + Insert: { + created_at?: string + entity_id: string + entity_slug: string + entity_type: string + id?: string + metadata?: Json | null + test_session_id?: string | null + } + Update: { + created_at?: string + entity_id?: string + entity_slug?: string + entity_type?: string + id?: string + metadata?: Json | null + test_session_id?: string | null + } + Relationships: [] + } user_blocks: { Row: { blocked_id: string diff --git a/src/lib/testDataGenerator.ts b/src/lib/testDataGenerator.ts index 5bd91bde..11fa39b7 100644 --- a/src/lib/testDataGenerator.ts +++ b/src/lib/testDataGenerator.ts @@ -220,35 +220,57 @@ export async function clearTestData(): Promise<{ deleted: number }> { .eq('content->metadata->>is_test_data', 'true'); if (fetchError) throw fetchError; - if (!testSubmissions || testSubmissions.length === 0) { - return { deleted: 0 }; + + const submissionCount = testSubmissions?.length || 0; + + // Delete submissions if found + if (submissionCount > 0) { + const batchSize = 100; + let totalDeleted = 0; + + for (let i = 0; i < testSubmissions.length; i += batchSize) { + const batch = testSubmissions.slice(i, i + batchSize); + const ids = batch.map(s => s.id); + + const { error: deleteError } = await supabase + .from('content_submissions') + .delete() + .in('id', ids); + + if (deleteError) throw deleteError; + totalDeleted += ids.length; + } } - // Delete in batches of 100 - const batchSize = 100; - let totalDeleted = 0; - - for (let i = 0; i < testSubmissions.length; i += batchSize) { - const batch = testSubmissions.slice(i, i + batchSize); - const ids = batch.map(s => s.id); - - const { error: deleteError } = await supabase - .from('content_submissions') - .delete() - .in('id', ids); - - if (deleteError) throw deleteError; - totalDeleted += ids.length; + // Clear the test data registry + const { error: registryError } = await supabase + .from('test_data_registry') + .delete() + .neq('id', '00000000-0000-0000-0000-000000000000'); // Delete all records + + if (registryError) { + console.error('Error clearing test data registry:', registryError); } - return { deleted: totalDeleted }; + return { deleted: submissionCount }; } catch (error) { console.error('Error clearing test data:', error); throw error; } } -export async function getTestDataStats(): Promise<{ total: number; pending: number; approved: number }> { +export async function getTestDataStats(): Promise<{ + total: number; + pending: number; + approved: number; + operators: number; + property_owners: number; + manufacturers: number; + designers: number; + parks: number; + rides: number; + ride_models: number; +}> { // Use proper JSON path query for nested metadata const { data, error } = await supabase .from('content_submissions') @@ -257,10 +279,27 @@ export async function getTestDataStats(): Promise<{ total: number; pending: numb if (error) throw error; + // Get registry counts for available dependencies + const { data: registryData } = await supabase + .from('test_data_registry') + .select('entity_type'); + + const registryCounts = registryData?.reduce((acc, row) => { + acc[row.entity_type] = (acc[row.entity_type] || 0) + 1; + return acc; + }, {} as Record) || {}; + const stats = { total: data?.length || 0, pending: data?.filter(s => s.status === 'pending').length || 0, - approved: data?.filter(s => s.status === 'approved').length || 0 + approved: data?.filter(s => s.status === 'approved').length || 0, + operators: registryCounts['operator'] || 0, + property_owners: registryCounts['property_owner'] || 0, + manufacturers: registryCounts['manufacturer'] || 0, + designers: registryCounts['designer'] || 0, + parks: registryCounts['park'] || 0, + rides: registryCounts['ride'] || 0, + ride_models: registryCounts['ride_model'] || 0 }; return stats; diff --git a/supabase/functions/seed-test-data/index.ts b/supabase/functions/seed-test-data/index.ts index 53cbaa50..b9bed1fe 100644 --- a/supabase/functions/seed-test-data/index.ts +++ b/supabase/functions/seed-test-data/index.ts @@ -70,6 +70,43 @@ function getPopulationLevel(fieldDensity: string, index: number): number { return 2; } +// Registry helper functions for cascading test data +async function registerTestEntity( + supabase: any, + entityType: string, + slug: string, + entityId: string, + sessionId: string +) { + const { error } = await supabase.from('test_data_registry').insert({ + entity_type: entityType, + entity_slug: slug, + entity_id: entityId, + test_session_id: sessionId + }); + + if (error && error.code !== '23505') { // Ignore unique constraint violations + console.error(`Error registering ${entityType} ${slug}:`, error); + } +} + +async function getExistingTestEntities( + supabase: any, + entityType: string +): Promise> { + const { data, error } = await supabase + .from('test_data_registry') + .select('entity_slug, entity_id') + .eq('entity_type', entityType); + + if (error) { + console.error(`Error fetching existing ${entityType}:`, error); + return []; + } + + return data || []; +} + Deno.serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); @@ -126,12 +163,36 @@ Deno.serve(async (req) => { } const startTime = Date.now(); + const sessionId = crypto.randomUUID(); // Unique ID for this generation session const summary = { parks: 0, rides: 0, companies: 0, rideModels: 0, photos: 0, totalPhotoItems: 0, conflicts: 0, versionChains: 0 }; const createdParks: string[] = []; const createdCompanies: Record = { manufacturer: [], operator: [], designer: [], property_owner: [] }; const createdParkSlugs: string[] = []; const createdRideSlugs: string[] = []; + // Load existing test entities from registry if includeDependencies is true + if (includeDependencies) { + console.log('Loading existing test entities from registry...'); + + const existingOperators = await getExistingTestEntities(supabase, 'operator'); + const existingOwners = await getExistingTestEntities(supabase, 'property_owner'); + const existingManufacturers = await getExistingTestEntities(supabase, 'manufacturer'); + const existingDesigners = await getExistingTestEntities(supabase, 'designer'); + const existingParks = await getExistingTestEntities(supabase, 'park'); + const existingRideModels = await getExistingTestEntities(supabase, 'ride_model'); + + existingOperators.forEach(op => createdCompanies.operator.push(op.slug)); + existingOwners.forEach(own => createdCompanies.property_owner.push(own.slug)); + existingManufacturers.forEach(mfg => createdCompanies.manufacturer.push(mfg.slug)); + existingDesigners.forEach(des => createdCompanies.designer.push(des.slug)); + existingParks.forEach(park => { + createdParks.push(park.slug); + createdParkSlugs.push(park.slug); + }); + + console.log(`Loaded ${existingOperators.length} operators, ${existingOwners.length} owners, ${existingManufacturers.length} manufacturers, ${existingDesigners.length} designers, ${existingParks.length} parks`); + } + // Helper to create submission async function createSubmission(userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean } = {}) { const submissionId = crypto.randomUUID(); @@ -265,6 +326,18 @@ Deno.serve(async (req) => { }; await createSubmission(user.id, 'park', parkData, options); + + // Register newly created park in the registry + const { data: approvedPark } = await supabase + .from('parks') + .select('id') + .eq('slug', slug) + .maybeSingle(); + + if (approvedPark) { + await registerTestEntity(supabase, 'park', slug, approvedPark.id, sessionId); + } + createdParks.push(slug); if (!shouldConflict && !shouldVersionChain) { createdParkSlugs.push(slug); @@ -310,7 +383,20 @@ Deno.serve(async (req) => { } await createSubmission(user.id, compType, companyData); - createdCompanies[compType].push(`test-${compType}-${i + 1}`); + + // Register newly created company in the registry + const companySlug = `test-${compType}-${i + 1}`; + const { data: approvedCompany } = await supabase + .from('companies') + .select('id') + .eq('slug', companySlug) + .maybeSingle(); + + if (approvedCompany) { + await registerTestEntity(supabase, compType, companySlug, approvedCompany.id, sessionId); + } + + createdCompanies[compType].push(companySlug); summary.companies++; } } @@ -422,6 +508,17 @@ Deno.serve(async (req) => { } } + // Register newly created ride in the registry + const { data: approvedRide } = await supabase + .from('rides') + .select('id') + .eq('slug', slug) + .maybeSingle(); + + if (approvedRide) { + await registerTestEntity(supabase, 'ride', slug, approvedRide.id, sessionId); + } + if (!shouldConflict && !shouldVersionChain) { createdRideSlugs.push(slug); } @@ -459,6 +556,19 @@ Deno.serve(async (req) => { } await createSubmission(user.id, 'ride_model', modelData); + + // Register newly created ride model in the registry + const modelSlug = `test-model-${i + 1}`; + const { data: approvedModel } = await supabase + .from('ride_models') + .select('id') + .eq('slug', modelSlug) + .maybeSingle(); + + if (approvedModel) { + await registerTestEntity(supabase, 'ride_model', modelSlug, approvedModel.id, sessionId); + } + summary.rideModels++; } } diff --git a/supabase/migrations/20251010131346_5aeef702-0135-42e6-8d05-3f47a8d688c7.sql b/supabase/migrations/20251010131346_5aeef702-0135-42e6-8d05-3f47a8d688c7.sql new file mode 100644 index 00000000..e72516ae --- /dev/null +++ b/supabase/migrations/20251010131346_5aeef702-0135-42e6-8d05-3f47a8d688c7.sql @@ -0,0 +1,35 @@ +-- Create test data registry table for tracking test entities across runs +CREATE TABLE IF NOT EXISTS test_data_registry ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + entity_type TEXT NOT NULL, + entity_slug TEXT NOT NULL, + entity_id UUID NOT NULL, + test_session_id TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + metadata JSONB DEFAULT '{}'::jsonb +); + +-- Create indexes for efficient queries +CREATE INDEX IF NOT EXISTS idx_test_data_registry_type ON test_data_registry(entity_type); +CREATE INDEX IF NOT EXISTS idx_test_data_registry_slug ON test_data_registry(entity_slug); +CREATE INDEX IF NOT EXISTS idx_test_data_registry_session ON test_data_registry(test_session_id); +CREATE INDEX IF NOT EXISTS idx_test_data_registry_entity_id ON test_data_registry(entity_id); + +-- Add unique constraint to prevent duplicate entries +CREATE UNIQUE INDEX IF NOT EXISTS idx_test_data_registry_unique ON test_data_registry(entity_type, entity_slug); + +-- Enable RLS +ALTER TABLE test_data_registry ENABLE ROW LEVEL SECURITY; + +-- Create RLS policies +CREATE POLICY "Moderators can manage test data registry" +ON test_data_registry +FOR ALL +TO authenticated +USING (is_moderator(auth.uid())); + +CREATE POLICY "Moderators can view test data registry" +ON test_data_registry +FOR SELECT +TO authenticated +USING (is_moderator(auth.uid())); \ No newline at end of file