From 62504da252f41e612e6d28408a77e8112c0b50c4 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 22:41:55 +0000 Subject: [PATCH] Fix seed-test-data edge function --- supabase/functions/seed-test-data/index.ts | 381 ++++++++++++--------- 1 file changed, 217 insertions(+), 164 deletions(-) diff --git a/supabase/functions/seed-test-data/index.ts b/supabase/functions/seed-test-data/index.ts index 7b2d8022..699b8641 100644 --- a/supabase/functions/seed-test-data/index.ts +++ b/supabase/functions/seed-test-data/index.ts @@ -114,10 +114,10 @@ async function getExistingTestEntities( async function getPendingSubmissionItems( supabase: any, itemType: string -): Promise> { +): Promise> { const { data, error } = await supabase .from('submission_items') - .select('id, item_data') + .select('id, item_data_id') .eq('item_type', itemType) .eq('status', 'pending'); @@ -129,6 +129,39 @@ async function getPendingSubmissionItems( return data || []; } +// Get slug from relational submission table +async function getSubmissionSlug( + supabase: any, + itemType: string, + itemDataId: string +): Promise { + const tableMap: Record = { + park: 'park_submissions', + ride: 'ride_submissions', + manufacturer: 'company_submissions', + operator: 'company_submissions', + designer: 'company_submissions', + property_owner: 'company_submissions', + ride_model: 'ride_model_submissions' + }; + + const table = tableMap[itemType]; + if (!table) return null; + + const { data, error } = await supabase + .from(table) + .select('slug') + .eq('id', itemDataId) + .maybeSingle(); + + if (error) { + edgeLogger.error(`Error fetching slug from ${table}`, { error: error.message, itemDataId }); + return null; + } + + return data?.slug || null; +} + Deno.serve(async (req) => { const tracking = startRequest(); @@ -200,10 +233,9 @@ Deno.serve(async (req) => { } const startTime = Date.now(); - const sessionId = crypto.randomUUID(); // Unique ID for this generation session + const sessionId = crypto.randomUUID(); const summary = { parks: 0, rides: 0, companies: 0, rideModels: 0, photos: 0, totalPhotoItems: 0, conflicts: 0, versionChains: 0 }; - // Track submission item IDs for dependency resolution const createdSubmissionItems: Record = { operator: [], property_owner: [], @@ -218,7 +250,7 @@ Deno.serve(async (req) => { const createdParkSlugs: string[] = []; const createdRideSlugs: string[] = []; - // Load existing test entities from registry if includeDependencies is true + // Load existing test entities from registry if (includeDependencies) { edgeLogger.info('Loading existing test entities from registry', { requestId: tracking.requestId }); @@ -227,9 +259,7 @@ Deno.serve(async (req) => { 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'); - // Load pending submission items for dependency resolution const pendingOperators = await getPendingSubmissionItems(supabase, 'operator'); const pendingOwners = await getPendingSubmissionItems(supabase, 'property_owner'); const pendingManufacturers = await getPendingSubmissionItems(supabase, 'manufacturer'); @@ -237,7 +267,7 @@ Deno.serve(async (req) => { const pendingParks = await getPendingSubmissionItems(supabase, 'park'); const pendingRideModels = await getPendingSubmissionItems(supabase, 'ride_model'); - // Track both approved and pending items + // Track approved entities existingOperators.forEach(op => { createdCompanies.operator.push(op.slug); if (op.submission_item_id) createdSubmissionItems.operator.push(op.submission_item_id); @@ -260,7 +290,7 @@ Deno.serve(async (req) => { if (park.submission_item_id) createdSubmissionItems.park.push(park.submission_item_id); }); - // Add pending items to tracking + // Track pending items pendingOperators.forEach(item => createdSubmissionItems.operator.push(item.id)); pendingOwners.forEach(item => createdSubmissionItems.property_owner.push(item.id)); pendingManufacturers.forEach(item => createdSubmissionItems.manufacturer.push(item.id)); @@ -268,6 +298,15 @@ Deno.serve(async (req) => { pendingParks.forEach(item => createdSubmissionItems.park.push(item.id)); pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id)); + // Get slugs from pending parks for ride generation + for (const item of pendingParks) { + const slug = await getSubmissionSlug(supabase, 'park', item.item_data_id); + if (slug) { + createdParks.push(slug); + createdParkSlugs.push(slug); + } + } + edgeLogger.info('Loaded existing entities', { requestId: tracking.requestId, operators: existingOperators.length, @@ -275,25 +314,11 @@ Deno.serve(async (req) => { manufacturers: existingManufacturers.length, designers: existingDesigners.length, parks: existingParks.length, - pendingOperators: pendingOperators.length, - pendingOwners: pendingOwners.length, - pendingManufacturers: pendingManufacturers.length, - pendingDesigners: pendingDesigners.length, - pendingParks: pendingParks.length + parksForRides: createdParks.length }); - - // Add pending parks' slugs to createdParks for ride generation - pendingParks.forEach(item => { - if (item.item_data?.slug) { - createdParks.push(item.item_data.slug); - createdParkSlugs.push(item.item_data.slug); - } - }); - - edgeLogger.info('Parks available for rides', { requestId: tracking.requestId, count: createdParks.length }); } - // Helper to create submission + // Helper to create submission using relational tables async function createSubmission( userId: string, type: string, @@ -302,24 +327,13 @@ Deno.serve(async (req) => { ) { const submissionId = crypto.randomUUID(); const itemId = crypto.randomUUID(); - - const contentData = { - action: 'create', - metadata: { - is_test_data: true, - generated_at: new Date().toISOString(), - generator_version: '2.0.0', - preset, - fieldDensity - } - }; + // Create content_submission (WITHOUT content field) const submissionData: any = { id: submissionId, user_id: userId, submission_type: type, status: 'pending', - content: contentData, submitted_at: new Date().toISOString() }; @@ -339,17 +353,7 @@ Deno.serve(async (req) => { throw subError; } - const { error: itemError } = await supabase.from('submission_items').insert({ - id: itemId, - submission_id: submissionId, - item_type: type, - item_data: itemData, - status: 'pending', - order_index: 0, - depends_on: options.dependsOn || null - }); - if (itemError) throw itemError; - + // Insert into type-specific submission table const typeTableMap: Record = { park: 'park_submissions', ride: 'ride_submissions', @@ -361,24 +365,66 @@ Deno.serve(async (req) => { }; const table = typeTableMap[type]; + let typeDataId: string | null = null; + if (table) { const typeData = { ...itemData, submission_id: submissionId }; if (table === 'company_submissions') { typeData.company_type = type; } - const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single(); + const { data: insertedData, error: typeError } = await supabase + .from(table) + .insert(typeData) + .select('id') + .single(); + if (typeError) { - edgeLogger.error('Error inserting into type table', { table, type, error: typeError.message, data: typeData }); + edgeLogger.error('Error inserting into type table', { table, type, error: typeError.message }); throw typeError; } - return { submissionId, itemId, typeId: insertedData?.id }; + + typeDataId = insertedData?.id; } - return { submissionId, itemId, typeId: null }; + // Create submission_item (WITHOUT item_data, using item_data_id) + const { error: itemError } = await supabase.from('submission_items').insert({ + id: itemId, + submission_id: submissionId, + item_type: type, + item_data_id: typeDataId, + status: 'pending', + order_index: 0, + depends_on: options.dependsOn || null + }); + + if (itemError) { + edgeLogger.error('Error inserting submission_item', { type, error: itemError.message }); + throw itemError; + } + + // Add submission metadata for test data marking + await supabase.from('submission_metadata').insert({ + submission_id: submissionId, + metadata_key: 'is_test_data', + metadata_value: 'true' + }); + + await supabase.from('submission_metadata').insert({ + submission_id: submissionId, + metadata_key: 'generator_version', + metadata_value: '2.0.0' + }); + + await supabase.from('submission_metadata').insert({ + submission_id: submissionId, + metadata_key: 'preset', + metadata_value: preset + }); + + return { submissionId, itemId, typeDataId }; } - // Helper function to properly pluralize company types const pluralizeCompanyType = (singular: string): string => { const pluralMap: Record = { 'manufacturer': 'manufacturers', @@ -389,11 +435,9 @@ Deno.serve(async (req) => { return pluralMap[singular] || singular + 's'; }; - // Create companies FIRST so parks can reference operators/owners + // Create companies if (!stage || stage === 'companies') { const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner']; - - // First, determine which company types are selected const selectedCompanyTypes = companyTypes.filter(compType => entityTypes.includes(pluralizeCompanyType(compType)) ); @@ -405,79 +449,71 @@ Deno.serve(async (req) => { selectedCompanyTypes }); - // Calculate fair distribution: base amount per type + extras const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length); const extras = plan.companies % selectedCompanyTypes.length; - let companiesCreatedTotal = 0; - for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) { - const compType = selectedCompanyTypes[typeIndex]; - const pluralType = pluralizeCompanyType(compType); - - // Each type gets base amount, first N types get +1 extra - const count = basePerType + (typeIndex < extras ? 1 : 0); - - edgeLogger.info('Creating companies', { requestId: tracking.requestId, compType, count }); - - for (let i = 0; i < count; i++) { - const level = getPopulationLevel(fieldDensity, i); - const companyData: any = { - name: `Test ${compType.replace('_', ' ')} ${i + 1}`, - slug: `test-${compType}-${i + 1}`, - company_type: compType - }; - - if (level >= 1) { - companyData.description = `Leading ${compType.replace('_', ' ')} in the amusement industry.`; - companyData.person_type = compType === 'designer' && Math.random() > 0.5 ? 'individual' : 'company'; - } - - if (level >= 2) { - companyData.founded_year = randomInt(1950, 2020); - const location = randomItem(CITIES); - companyData.headquarters_location = `${location.city}, ${location.country}`; - } - - if (level >= 3) { - companyData.website_url = `https://test-${compType}-${i + 1}.example.com`; - companyData.logo_url = `https://cdn.thrillwiki.com/images/test-${compType}-${i + 1}-logo/logo`; - } - - if (level >= 4) { - companyData.card_image_id = `test-${compType}-card-${i + 1}`; - companyData.card_image_url = `https://cdn.thrillwiki.com/images/test-${compType}-card-${i + 1}/card`; - companyData.banner_image_id = `test-${compType}-banner-${i + 1}`; - companyData.banner_image_url = `https://cdn.thrillwiki.com/images/test-${compType}-banner-${i + 1}/banner`; - } - - const { itemId } = await createSubmission(user.id, compType, companyData); + const compType = selectedCompanyTypes[typeIndex]; + const count = basePerType + (typeIndex < extras ? 1 : 0); - // Track created submission item - createdSubmissionItems[compType].push(itemId); + edgeLogger.info('Creating companies', { requestId: tracking.requestId, compType, count }); - // 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, itemId, sessionId); + for (let i = 0; i < count; i++) { + const level = getPopulationLevel(fieldDensity, i); + const companyData: any = { + name: `Test ${compType.replace('_', ' ')} ${i + 1}`, + slug: `test-${compType}-${i + 1}`, + company_type: compType + }; + + if (level >= 1) { + companyData.description = `Leading ${compType.replace('_', ' ')} in the amusement industry.`; + companyData.person_type = compType === 'designer' && Math.random() > 0.5 ? 'individual' : 'company'; + } + + if (level >= 2) { + companyData.founded_year = randomInt(1950, 2020); + const location = randomItem(CITIES); + companyData.headquarters_location = `${location.city}, ${location.country}`; + } + + if (level >= 3) { + companyData.website_url = `https://test-${compType}-${i + 1}.example.com`; + companyData.logo_url = `https://cdn.thrillwiki.com/images/test-${compType}-${i + 1}-logo/logo`; + } + + if (level >= 4) { + companyData.card_image_id = `test-${compType}-card-${i + 1}`; + companyData.card_image_url = `https://cdn.thrillwiki.com/images/test-${compType}-card-${i + 1}/card`; + companyData.banner_image_id = `test-${compType}-banner-${i + 1}`; + companyData.banner_image_url = `https://cdn.thrillwiki.com/images/test-${compType}-banner-${i + 1}/banner`; + } + + const { itemId } = await createSubmission(user.id, compType, companyData); + + createdSubmissionItems[compType].push(itemId); + + 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, itemId, sessionId); + } + + createdCompanies[compType].push(companySlug); + summary.companies++; } - - createdCompanies[compType].push(companySlug); - summary.companies++; - companiesCreatedTotal++; } } - } // Create parks if ((!stage || stage === 'parks') && entityTypes.includes('parks')) { edgeLogger.info('Creating parks', { requestId: tracking.requestId, count: plan.parks }); + for (let i = 0; i < plan.parks; i++) { const level = getPopulationLevel(fieldDensity, i); const shouldConflict = includeConflicts && createdParkSlugs.length > 0 && Math.random() < 0.15; @@ -513,9 +549,13 @@ Deno.serve(async (req) => { let ownerItemId: string | undefined; if (level >= 3 && createdCompanies.operator.length > 0) { - const { data: operatorData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.operator)).maybeSingle(); + const { data: operatorData } = await supabase + .from('companies') + .select('id') + .eq('slug', randomItem(createdCompanies.operator)) + .maybeSingle(); if (operatorData) parkData.operator_id = operatorData.id; - // Track operator dependency + if (createdSubmissionItems.operator.length > 0) { operatorItemId = randomItem(createdSubmissionItems.operator); } @@ -526,9 +566,13 @@ Deno.serve(async (req) => { if (level >= 4) { if (createdCompanies.property_owner.length > 0) { - const { data: ownerData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.property_owner)).maybeSingle(); + const { data: ownerData } = await supabase + .from('companies') + .select('id') + .eq('slug', randomItem(createdCompanies.property_owner)) + .maybeSingle(); if (ownerData) parkData.property_owner_id = ownerData.id; - // Track owner dependency (prefer operator if both exist) + if (createdSubmissionItems.property_owner.length > 0 && !operatorItemId) { ownerItemId = randomItem(createdSubmissionItems.property_owner); } @@ -549,10 +593,8 @@ Deno.serve(async (req) => { const { itemId } = await createSubmission(user.id, 'park', parkData, options); - // Track created submission item createdSubmissionItems.park.push(itemId); - // Register newly created park in the registry const { data: approvedPark } = await supabase .from('parks') .select('id') @@ -571,9 +613,10 @@ Deno.serve(async (req) => { } } - // Create rides if ((!stage || stage === 'rides') && entityTypes.includes('rides') && includeDependencies && createdParks.length > 0) { + edgeLogger.info('Creating rides', { requestId: tracking.requestId, count: plan.rides }); + for (let i = 0; i < plan.rides; i++) { const level = getPopulationLevel(fieldDensity, i); const shouldConflict = includeConflicts && createdRideSlugs.length > 0 && Math.random() < 0.15; @@ -589,9 +632,12 @@ Deno.serve(async (req) => { } const parkSlug = randomItem(createdParks); - const { data: parkData } = await supabase.from('parks').select('id').eq('slug', parkSlug).maybeSingle(); + const { data: parkData } = await supabase + .from('parks') + .select('id') + .eq('slug', parkSlug) + .maybeSingle(); - // Get park dependency for depends_on const parkItemId = createdSubmissionItems.park.length > 0 ? randomItem(createdSubmissionItems.park) : undefined; const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride', 'dark_ride']); @@ -619,7 +665,11 @@ Deno.serve(async (req) => { if (level >= 3) { if (createdCompanies.manufacturer.length > 0) { - const { data: mfgData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.manufacturer)).maybeSingle(); + const { data: mfgData } = await supabase + .from('companies') + .select('id') + .eq('slug', randomItem(createdCompanies.manufacturer)) + .maybeSingle(); if (mfgData) rideData.manufacturer_id = mfgData.id; } if (category === 'roller_coaster') { @@ -633,7 +683,11 @@ Deno.serve(async (req) => { if (level >= 4) { if (createdCompanies.designer.length > 0) { - const { data: designerData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.designer)).maybeSingle(); + const { data: designerData } = await supabase + .from('companies') + .select('id') + .eq('slug', randomItem(createdCompanies.designer)) + .maybeSingle(); if (designerData) rideData.designer_id = designerData.id; } rideData.length_meters = randomInt(500, 2000); @@ -647,14 +701,13 @@ Deno.serve(async (req) => { dependsOn: parkItemId }; - const { submissionId, itemId, typeId } = await createSubmission(user.id, 'ride', rideData, options); + const { submissionId, itemId, typeDataId } = await createSubmission(user.id, 'ride', rideData, options); - // Add technical specs and stats for level 4 - if (level >= 4 && typeId && category === 'roller_coaster') { - // Add coaster stats + // Add relational data for level 4 rides + if (level >= 4 && typeDataId && category === 'roller_coaster') { for (let s = 0; s < randomInt(2, 3); s++) { await supabase.from('ride_submission_coaster_statistics').insert({ - ride_submission_id: typeId, + ride_submission_id: typeDataId, stat_name: randomItem(['Airtime Duration', 'Zero-G Time', 'Track Gauge']), stat_value: Math.random() * 100, unit: randomItem(['seconds', 'mm']), @@ -662,10 +715,9 @@ Deno.serve(async (req) => { }); } - // Add technical specs for (let t = 0; t < randomInt(3, 5); t++) { await supabase.from('ride_submission_technical_specifications').insert({ - ride_submission_id: typeId, + ride_submission_id: typeDataId, spec_name: randomItem(['Lift System', 'Brake System', 'Train Count', 'Track Material']), spec_value: randomItem(['Chain lift', 'Magnetic brakes', '3 trains', 'Steel tubular']), spec_type: 'system', @@ -673,10 +725,9 @@ Deno.serve(async (req) => { }); } - // Add former name if (Math.random() > 0.7) { await supabase.from('ride_submission_name_history').insert({ - ride_submission_id: typeId, + ride_submission_id: typeDataId, former_name: `Original Name ${i + 1}`, date_changed: randomDate(2010, 2020), reason: 'Rebranding', @@ -685,7 +736,6 @@ Deno.serve(async (req) => { } } - // Register newly created ride in the registry const { data: approvedRide } = await supabase .from('rides') .select('id') @@ -705,27 +755,32 @@ Deno.serve(async (req) => { // Create ride models if ((!stage || stage === 'rides') && entityTypes.includes('ride_models') && includeDependencies && createdSubmissionItems.manufacturer.length > 0) { + edgeLogger.info('Creating ride models', { requestId: tracking.requestId, count: plan.rideModels }); + for (let i = 0; i < plan.rideModels; i++) { const level = getPopulationLevel(fieldDensity, i); - const { data: mfgData } = await supabase.from('companies').select('id').eq('slug', randomItem(createdCompanies.manufacturer)).maybeSingle(); + const { data: mfgData } = await supabase + .from('companies') + .select('id') + .eq('slug', randomItem(createdCompanies.manufacturer)) + .maybeSingle(); - // Get manufacturer dependency for depends_on const mfgItemId = createdSubmissionItems.manufacturer.length > 0 ? randomItem(createdSubmissionItems.manufacturer) : undefined; - const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride']); - const rideType = randomItem(['spinning', 'launch', 'suspended', 'family', 'standard']); - - const modelData: any = { - name: `Test Model ${i + 1}`, - slug: `test-model-${i + 1}`, - category: category, - ride_type: rideType, - manufacturer_id: mfgData?.id || null - }; + const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride']); + const rideType = randomItem(['spinning', 'launch', 'suspended', 'family', 'standard']); + + const modelData: any = { + name: `Test Model ${i + 1}`, + slug: `test-model-${i + 1}`, + category: category, + ride_type: rideType, + manufacturer_id: mfgData?.id || null + }; - if (level >= 1) { - modelData.description = `Popular ${category.replace('_', ' ')} model.`; - } + if (level >= 1) { + modelData.description = `Popular ${category.replace('_', ' ')} model.`; + } if (level >= 2) { modelData.card_image_id = `test-model-card-${i + 1}`; @@ -743,10 +798,8 @@ Deno.serve(async (req) => { const { itemId } = await createSubmission(user.id, 'ride_model', modelData, options); - // Track created submission item createdSubmissionItems.ride_model.push(itemId); - // Register newly created ride model in the registry const modelSlug = `test-model-${i + 1}`; const { data: approvedModel } = await supabase .from('ride_models') @@ -764,6 +817,8 @@ Deno.serve(async (req) => { // Create photo submissions if ((!stage || stage === 'photos') && entityTypes.includes('photos') && plan.photos > 0) { + edgeLogger.info('Creating photo submissions', { requestId: tracking.requestId, count: plan.photos }); + const { data: approvedParks } = await supabase.from('parks').select('id').limit(Math.min(20, plan.photos)); const { data: approvedRides } = await supabase.from('rides').select('id, park_id').limit(Math.min(20, plan.photos)); @@ -788,24 +843,22 @@ Deno.serve(async (req) => { if (!entityId) continue; - // Create content_submission + // Create content_submission (WITHOUT content field) await supabase.from('content_submissions').insert({ id: submissionId, user_id: user.id, submission_type: 'photo', status: 'pending', - content: { - action: 'create', - metadata: { - is_test_data: true, - generated_at: new Date().toISOString(), - generator_version: '2.0.0', - preset - } - }, submitted_at: new Date().toISOString() }); + // Mark as test data + await supabase.from('submission_metadata').insert({ + submission_id: submissionId, + metadata_key: 'is_test_data', + metadata_value: 'true' + }); + // Create photo_submission await supabase.from('photo_submissions').insert({ id: photoSubmissionId,