diff --git a/src/components/admin/TestDataGenerator.tsx b/src/components/admin/TestDataGenerator.tsx
index d31587ec..1362376d 100644
--- a/src/components/admin/TestDataGenerator.tsx
+++ b/src/components/admin/TestDataGenerator.tsx
@@ -181,6 +181,39 @@ export function TestDataGenerator() {
)}
+
+ {/* Dependency warnings */}
+ {entityTypes.rides && stats.parks === 0 && !entityTypes.parks && (
+
+
+
+ Missing Dependency: You've selected "rides" but no parks exist.
+ Rides require parks to be created first. Either enable "parks" or generate parks separately first.
+
+
+ )}
+
+ {entityTypes.ride_models && stats.manufacturers === 0 && !entityTypes.manufacturers && (
+
+
+
+ Missing Dependency: You've selected "ride models" but no manufacturers exist.
+ Ride models require manufacturers to be created first. Either enable "manufacturers" or generate manufacturers separately first.
+
+
+ )}
+
+ {(entityTypes.parks || entityTypes.rides) &&
+ stats.operators === 0 && stats.property_owners === 0 &&
+ !entityTypes.operators && !entityTypes.property_owners && (
+
+
+
+ Optional Dependencies: Parks and rides can optionally link to operators and property owners.
+ Consider enabling "operators" and "property_owners" for more realistic test data with proper dependency chains.
+
+
+ )}
)}
diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts
index 0aed41f9..9ffef2ae 100644
--- a/src/integrations/supabase/types.ts
+++ b/src/integrations/supabase/types.ts
@@ -2288,6 +2288,7 @@ export type Database = {
entity_type: string
id: string
metadata: Json | null
+ submission_item_id: string | null
test_session_id: string | null
}
Insert: {
@@ -2297,6 +2298,7 @@ export type Database = {
entity_type: string
id?: string
metadata?: Json | null
+ submission_item_id?: string | null
test_session_id?: string | null
}
Update: {
@@ -2306,9 +2308,18 @@ export type Database = {
entity_type?: string
id?: string
metadata?: Json | null
+ submission_item_id?: string | null
test_session_id?: string | null
}
- Relationships: []
+ Relationships: [
+ {
+ foreignKeyName: "test_data_registry_submission_item_id_fkey"
+ columns: ["submission_item_id"]
+ isOneToOne: false
+ referencedRelation: "submission_items"
+ referencedColumns: ["id"]
+ },
+ ]
}
user_blocks: {
Row: {
diff --git a/supabase/functions/seed-test-data/index.ts b/supabase/functions/seed-test-data/index.ts
index b9bed1fe..d471280f 100644
--- a/supabase/functions/seed-test-data/index.ts
+++ b/supabase/functions/seed-test-data/index.ts
@@ -76,12 +76,14 @@ async function registerTestEntity(
entityType: string,
slug: string,
entityId: string,
+ submissionItemId: string,
sessionId: string
) {
const { error } = await supabase.from('test_data_registry').insert({
entity_type: entityType,
entity_slug: slug,
entity_id: entityId,
+ submission_item_id: submissionItemId,
test_session_id: sessionId
});
@@ -93,10 +95,10 @@ async function registerTestEntity(
async function getExistingTestEntities(
supabase: any,
entityType: string
-): Promise> {
+): Promise> {
const { data, error } = await supabase
.from('test_data_registry')
- .select('entity_slug, entity_id')
+ .select('entity_slug, entity_id, submission_item_id')
.eq('entity_type', entityType);
if (error) {
@@ -107,6 +109,24 @@ async function getExistingTestEntities(
return data || [];
}
+async function getPendingSubmissionItems(
+ supabase: any,
+ itemType: string
+): Promise> {
+ const { data, error } = await supabase
+ .from('submission_items')
+ .select('id, item_data')
+ .eq('item_type', itemType)
+ .eq('status', 'pending');
+
+ if (error) {
+ console.error(`Error fetching pending ${itemType} items:`, error);
+ return [];
+ }
+
+ return data || [];
+}
+
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
@@ -165,6 +185,17 @@ 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 };
+
+ // Track submission item IDs for dependency resolution
+ const createdSubmissionItems: Record = {
+ operator: [],
+ property_owner: [],
+ manufacturer: [],
+ designer: [],
+ park: [],
+ ride_model: []
+ };
+
const createdParks: string[] = [];
const createdCompanies: Record = { manufacturer: [], operator: [], designer: [], property_owner: [] };
const createdParkSlugs: string[] = [];
@@ -181,20 +212,56 @@ Deno.serve(async (req) => {
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));
+ // 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');
+ const pendingDesigners = await getPendingSubmissionItems(supabase, 'designer');
+ const pendingParks = await getPendingSubmissionItems(supabase, 'park');
+ const pendingRideModels = await getPendingSubmissionItems(supabase, 'ride_model');
+
+ // Track both approved and pending items
+ existingOperators.forEach(op => {
+ createdCompanies.operator.push(op.slug);
+ if (op.submission_item_id) createdSubmissionItems.operator.push(op.submission_item_id);
+ });
+ existingOwners.forEach(own => {
+ createdCompanies.property_owner.push(own.slug);
+ if (own.submission_item_id) createdSubmissionItems.property_owner.push(own.submission_item_id);
+ });
+ existingManufacturers.forEach(mfg => {
+ createdCompanies.manufacturer.push(mfg.slug);
+ if (mfg.submission_item_id) createdSubmissionItems.manufacturer.push(mfg.submission_item_id);
+ });
+ existingDesigners.forEach(des => {
+ createdCompanies.designer.push(des.slug);
+ if (des.submission_item_id) createdSubmissionItems.designer.push(des.submission_item_id);
+ });
existingParks.forEach(park => {
createdParks.push(park.slug);
createdParkSlugs.push(park.slug);
+ if (park.submission_item_id) createdSubmissionItems.park.push(park.submission_item_id);
});
+ // Add pending items to tracking
+ 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));
+ pendingDesigners.forEach(item => createdSubmissionItems.designer.push(item.id));
+ pendingParks.forEach(item => createdSubmissionItems.park.push(item.id));
+ pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id));
+
console.log(`Loaded ${existingOperators.length} operators, ${existingOwners.length} owners, ${existingManufacturers.length} manufacturers, ${existingDesigners.length} designers, ${existingParks.length} parks`);
+ console.log(`Loaded ${pendingOperators.length} pending operators, ${pendingOwners.length} pending owners, ${pendingManufacturers.length} pending manufacturers, ${pendingDesigners.length} pending designers, ${pendingParks.length} pending parks`);
}
// Helper to create submission
- async function createSubmission(userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean } = {}) {
+ async function createSubmission(
+ userId: string,
+ type: string,
+ itemData: any,
+ options: { escalated?: boolean; expiredLock?: boolean; dependsOn?: string } = {}
+ ) {
const submissionId = crypto.randomUUID();
const itemId = crypto.randomUUID();
@@ -237,7 +304,8 @@ Deno.serve(async (req) => {
item_type: type,
item_data: itemData,
status: 'pending',
- order_index: 0
+ order_index: 0,
+ depends_on: options.dependsOn || null
});
if (itemError) throw itemError;
@@ -260,10 +328,10 @@ Deno.serve(async (req) => {
const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single();
if (typeError) throw typeError;
- return { submissionId, typeId: insertedData?.id };
+ return { submissionId, itemId, typeId: insertedData?.id };
}
- return { submissionId, typeId: null };
+ return { submissionId, itemId, typeId: null };
}
// Create parks
@@ -299,9 +367,16 @@ Deno.serve(async (req) => {
parkData.phone = `+1-555-${randomInt(100, 999)}-${randomInt(1000, 9999)}`;
}
+ let operatorItemId: string | undefined;
+ 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();
if (operatorData) parkData.operator_id = operatorData.id;
+ // Track operator dependency
+ if (createdSubmissionItems.operator.length > 0) {
+ operatorItemId = randomItem(createdSubmissionItems.operator);
+ }
parkData.email = `info@test-park-${i + 1}.example.com`;
parkData.card_image_id = `test-park-card-${i + 1}`;
parkData.card_image_url = `https://imagedelivery.net/test/park-${i + 1}/card`;
@@ -311,6 +386,10 @@ Deno.serve(async (req) => {
if (createdCompanies.property_owner.length > 0) {
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);
+ }
}
if (Math.random() > 0.9) {
parkData.closing_date = randomDate(2000, 2024);
@@ -322,10 +401,14 @@ Deno.serve(async (req) => {
const options = {
escalated: includeEscalated && Math.random() < 0.1,
- expiredLock: includeExpiredLocks && Math.random() < 0.1
+ expiredLock: includeExpiredLocks && Math.random() < 0.1,
+ dependsOn: operatorItemId || ownerItemId
};
- await createSubmission(user.id, 'park', parkData, options);
+ 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
@@ -335,7 +418,7 @@ Deno.serve(async (req) => {
.maybeSingle();
if (approvedPark) {
- await registerTestEntity(supabase, 'park', slug, approvedPark.id, sessionId);
+ await registerTestEntity(supabase, 'park', slug, approvedPark.id, itemId, sessionId);
}
createdParks.push(slug);
@@ -382,7 +465,10 @@ Deno.serve(async (req) => {
companyData.banner_image_url = `https://imagedelivery.net/test/${compType}-${i + 1}/banner`;
}
- await createSubmission(user.id, compType, companyData);
+ const { itemId } = await createSubmission(user.id, compType, companyData);
+
+ // Track created submission item
+ createdSubmissionItems[compType].push(itemId);
// Register newly created company in the registry
const companySlug = `test-${compType}-${i + 1}`;
@@ -393,7 +479,7 @@ Deno.serve(async (req) => {
.maybeSingle();
if (approvedCompany) {
- await registerTestEntity(supabase, compType, companySlug, approvedCompany.id, sessionId);
+ await registerTestEntity(supabase, compType, companySlug, approvedCompany.id, itemId, sessionId);
}
createdCompanies[compType].push(companySlug);
@@ -420,6 +506,9 @@ Deno.serve(async (req) => {
const parkSlug = randomItem(createdParks);
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']);
const rideData: any = {
@@ -470,7 +559,11 @@ Deno.serve(async (req) => {
rideData.banner_image_url = `https://imagedelivery.net/test/ride-${i + 1}/banner`;
}
- const { submissionId, typeId } = await createSubmission(user.id, 'ride', rideData);
+ const options = {
+ dependsOn: parkItemId
+ };
+
+ const { submissionId, itemId, typeId } = await createSubmission(user.id, 'ride', rideData, options);
// Add technical specs and stats for level 4
if (level >= 4 && typeId && category === 'roller_coaster') {
@@ -516,7 +609,7 @@ Deno.serve(async (req) => {
.maybeSingle();
if (approvedRide) {
- await registerTestEntity(supabase, 'ride', slug, approvedRide.id, sessionId);
+ await registerTestEntity(supabase, 'ride', slug, approvedRide.id, itemId, sessionId);
}
if (!shouldConflict && !shouldVersionChain) {
@@ -531,6 +624,9 @@ Deno.serve(async (req) => {
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();
+
+ // 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 modelData: any = {
@@ -555,7 +651,14 @@ Deno.serve(async (req) => {
modelData.banner_image_url = `https://imagedelivery.net/test/model-${i + 1}/banner`;
}
- await createSubmission(user.id, 'ride_model', modelData);
+ const options = {
+ dependsOn: mfgItemId
+ };
+
+ 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}`;
@@ -566,7 +669,7 @@ Deno.serve(async (req) => {
.maybeSingle();
if (approvedModel) {
- await registerTestEntity(supabase, 'ride_model', modelSlug, approvedModel.id, sessionId);
+ await registerTestEntity(supabase, 'ride_model', modelSlug, approvedModel.id, itemId, sessionId);
}
summary.rideModels++;
diff --git a/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql b/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql
new file mode 100644
index 00000000..fa26397e
--- /dev/null
+++ b/supabase/migrations/20251010164045_dd0baeac-d6a3-449c-b42a-0a1e5ba48df3.sql
@@ -0,0 +1,10 @@
+-- Add submission_item_id to test_data_registry for proper dependency tracking
+ALTER TABLE test_data_registry
+ ADD COLUMN IF NOT EXISTS submission_item_id UUID REFERENCES submission_items(id) ON DELETE CASCADE;
+
+-- Add index for fast lookups
+CREATE INDEX IF NOT EXISTS idx_test_data_registry_item_id
+ ON test_data_registry(submission_item_id);
+
+-- Update existing records to have NULL submission_item_id (for backwards compatibility)
+-- New records will populate this field
\ No newline at end of file