import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4'; import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts'; const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; interface SeedOptions { preset: 'small' | 'medium' | 'large' | 'stress'; entityTypes: string[]; fieldDensity?: 'mixed' | 'minimal' | 'standard' | 'maximum'; includeDependencies?: boolean; includeConflicts?: boolean; includeVersionChains?: boolean; includeEscalated?: boolean; includeExpiredLocks?: boolean; stage?: 'companies' | 'parks' | 'rides' | 'photos'; } interface SeedPlan { parks: number; rides: number; companies: number; rideModels: number; photos: number; } const PRESETS: Record = { small: { parks: 5, rides: 10, companies: 3, rideModels: 2, photos: 5 }, medium: { parks: 20, rides: 50, companies: 20, rideModels: 10, photos: 25 }, large: { parks: 100, rides: 250, companies: 100, rideModels: 50, photos: 100 }, stress: { parks: 400, rides: 1000, companies: 400, rideModels: 200, photos: 500 } }; const CITIES = [ { city: 'Orlando', state: 'Florida', country: 'USA' }, { city: 'Anaheim', state: 'California', country: 'USA' }, { city: 'Paris', state: 'Île-de-France', country: 'France' }, { city: 'Tokyo', state: 'Tokyo', country: 'Japan' }, { city: 'Berlin', state: 'Berlin', country: 'Germany' } ]; // Helper functions function randomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } function randomItem(array: T[]): T { return array[randomInt(0, array.length - 1)]; } function randomDate(startYear: number, endYear: number): string { const year = randomInt(startYear, endYear); const month = randomInt(1, 12); const day = randomInt(1, 28); return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; } function getPopulationLevel(fieldDensity: string, index: number): number { if (fieldDensity === 'mixed') { const rand = Math.random(); if (rand < 0.1) return 0; // 10% minimal if (rand < 0.3) return 1; // 20% basic if (rand < 0.7) return 2; // 40% standard if (rand < 0.9) return 3; // 20% complete return 4; // 10% maximum } if (fieldDensity === 'minimal') return 0; if (fieldDensity === 'standard') return 2; if (fieldDensity === 'maximum') return 4; return 2; } function randomFloat(min: number, max: number, decimals = 2): number { const value = Math.random() * (max - min) + min; return parseFloat(value.toFixed(decimals)); } function randomDatePrecision(): 'day' | 'month' | 'year' { return randomItem(['day', 'month', 'year']); } function shouldPopulateField( fieldDensity: string, index: number, fieldImportance: 'low' | 'medium' | 'high' ): boolean { const level = getPopulationLevel(fieldDensity, index); if (fieldImportance === 'high') return level >= 1; if (fieldImportance === 'medium') return level >= 2; if (fieldImportance === 'low') return level >= 3; return false; } // Category-specific field generators function generateWaterRideFields(fieldDensity: string, index: number) { if (!shouldPopulateField(fieldDensity, index, 'medium')) return {}; return { water_depth_cm: randomInt(30, 300), splash_height_meters: randomFloat(1, 20, 1), wetness_level: randomItem(['dry', 'light', 'moderate', 'soaked']), flume_type: randomItem(['log', 'tube', 'raft', 'boat']), boat_capacity: randomInt(2, 20) }; } function generateDarkRideFields(fieldDensity: string, index: number) { if (!shouldPopulateField(fieldDensity, index, 'medium')) return {}; return { theme_name: randomItem(['Space Adventure', 'Haunted Mansion', 'Underwater Journey', 'Fantasy Quest']), story_description: 'An immersive journey through a themed environment with exciting scenes.', show_duration_seconds: randomInt(180, 600), animatronics_count: randomInt(5, 50), projection_type: randomItem(['2d', '3d', 'holographic', 'mixed']), ride_system: randomItem(['omnimover', 'tracked', 'trackless', 'boat']), scenes_count: randomInt(5, 20) }; } function generateFlatRideFields(fieldDensity: string, index: number) { if (!shouldPopulateField(fieldDensity, index, 'medium')) return {}; return { rotation_type: randomItem(['horizontal', 'vertical', 'both', 'none']), motion_pattern: randomItem(['circular', 'pendulum', 'spinning', 'wave', 'random']), platform_count: randomInt(1, 8), swing_angle_degrees: randomInt(45, 180), rotation_speed_rpm: randomInt(5, 30), arm_length_meters: randomFloat(5, 25, 1), max_height_reached_meters: randomFloat(10, 80, 1) }; } function generateKiddieRideFields(fieldDensity: string, index: number) { if (!shouldPopulateField(fieldDensity, index, 'medium')) return {}; return { min_age: randomInt(2, 5), max_age: randomInt(8, 12), educational_theme: randomItem(['counting', 'colors', 'animals', 'shapes', 'letters']), character_theme: randomItem(['dinosaurs', 'princesses', 'superheroes', 'animals', 'vehicles']) }; } function generateTransportationRideFields(fieldDensity: string, index: number) { if (!shouldPopulateField(fieldDensity, index, 'medium')) return {}; return { transport_type: randomItem(['monorail', 'train', 'skyway', 'gondola', 'ferry']), route_length_meters: randomInt(500, 5000), stations_count: randomInt(2, 8), vehicle_capacity: randomInt(20, 200), vehicles_count: randomInt(2, 12), round_trip_duration_seconds: randomInt(300, 1800) }; } function generateSubmissionNotes( fieldDensity: string, index: number, entityType: string ): string | undefined { if (!shouldPopulateField(fieldDensity, index, 'low')) return undefined; const notes = [ `Updated ${entityType} information from official source`, `Added comprehensive data for testing purposes`, `Verified information through multiple sources`, `Historical data added for completeness`, `Technical specifications verified with manufacturer` ]; return randomItem(notes); } // Registry helper functions for cascading test data async function registerTestEntity( supabase: any, 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 }); if (error && error.code !== '23505') { // Ignore unique constraint violations edgeLogger.error(`Error registering ${entityType} ${slug}`, { error: error.message, entityType, slug }); } } // Validate that submission item IDs still exist in the database async function validateSubmissionItemIds( supabase: any, itemIds: string[] ): Promise { if (itemIds.length === 0) return []; const { data, error } = await supabase .from('submission_items') .select('id') .in('id', itemIds); if (error) { edgeLogger.error('Error validating submission item IDs', { error: error.message }); return []; } return data ? data.map(item => item.id) : []; } async function getExistingTestEntities( supabase: any, entityType: string ): Promise> { const { data, error } = await supabase .from('test_data_registry') .select('entity_slug, entity_id, submission_item_id') .eq('entity_type', entityType); if (error) { edgeLogger.error(`Error fetching existing ${entityType}`, { error: error.message, entityType }); return []; } return data || []; } async function getPendingSubmissionItems( supabase: any, itemType: string ): Promise> { // Determine which FK column to select based on itemType let fkColumn: string; if (itemType === 'park') { fkColumn = 'park_submission_id'; } else if (itemType === 'ride') { fkColumn = 'ride_submission_id'; } else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(itemType)) { fkColumn = 'company_submission_id'; } else if (itemType === 'ride_model') { fkColumn = 'ride_model_submission_id'; } else { fkColumn = 'photo_submission_id'; } const { data, error } = await supabase .from('submission_items') .select(`id, ${fkColumn}`) .eq('item_type', itemType) .eq('status', 'pending'); if (error) { edgeLogger.error(`Error fetching pending ${itemType} items`, { error: error.message, itemType }); return []; } // Map the response to maintain backward compatibility return (data || []).map(item => ({ id: item.id, item_data_id: item[fkColumn] })); } // 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(); if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabase = createClient(supabaseUrl, supabaseServiceKey); const authHeader = req.headers.get('Authorization'); if (!authHeader) { return new Response(JSON.stringify({ error: 'No authorization header' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } const token = authHeader.replace('Bearer ', ''); const { data: { user }, error: userError } = await supabase.auth.getUser(token); if (userError || !user) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } const { data: isMod, error: modError } = await supabase.rpc('is_moderator', { _user_id: user.id }); if (modError || !isMod) { return new Response(JSON.stringify({ error: 'Insufficient permissions. Moderator role required.' }), { status: 403, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } const { preset = 'small', entityTypes = [], fieldDensity = 'mixed', includeDependencies = true, includeConflicts = false, includeVersionChains = false, includeEscalated = false, includeExpiredLocks = false, stage }: SeedOptions = await req.json(); edgeLogger.info('Seed data generation started', { requestId: tracking.requestId, entityTypes, preset, fieldDensity, includeDependencies, includeConflicts, includeVersionChains, includeEscalated, includeExpiredLocks }); const plan = PRESETS[preset]; if (!plan) { return new Response(JSON.stringify({ error: 'Invalid preset' }), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } const startTime = Date.now(); const sessionId = crypto.randomUUID(); const summary = { parks: 0, rides: 0, companies: 0, rideModels: 0, photos: 0, totalPhotoItems: 0, conflicts: 0, versionChains: 0 }; 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[] = []; const createdRideSlugs: string[] = []; // Load existing test entities from registry if (includeDependencies) { edgeLogger.info('Loading existing test entities from registry', { requestId: tracking.requestId }); 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 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 approved entities 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); }); // 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)); 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)); // Validate all loaded submission item IDs to remove stale references const allManufacturerIds = [...createdSubmissionItems.manufacturer]; const validManufacturerIds = await validateSubmissionItemIds(supabase, allManufacturerIds); createdSubmissionItems.manufacturer = validManufacturerIds; const allOperatorIds = [...createdSubmissionItems.operator]; const validOperatorIds = await validateSubmissionItemIds(supabase, allOperatorIds); createdSubmissionItems.operator = validOperatorIds; const allOwnerIds = [...createdSubmissionItems.property_owner]; const validOwnerIds = await validateSubmissionItemIds(supabase, allOwnerIds); createdSubmissionItems.property_owner = validOwnerIds; const allDesignerIds = [...createdSubmissionItems.designer]; const validDesignerIds = await validateSubmissionItemIds(supabase, allDesignerIds); createdSubmissionItems.designer = validDesignerIds; const allParkIds = [...createdSubmissionItems.park]; const validParkIds = await validateSubmissionItemIds(supabase, allParkIds); createdSubmissionItems.park = validParkIds; const allRideModelIds = [...createdSubmissionItems.ride_model]; const validRideModelIds = await validateSubmissionItemIds(supabase, allRideModelIds); createdSubmissionItems.ride_model = validRideModelIds; // Log if stale IDs were found if (allManufacturerIds.length !== validManufacturerIds.length) { edgeLogger.warn('Removed stale manufacturer submission items', { requestId: tracking.requestId, total: allManufacturerIds.length, valid: validManufacturerIds.length, removed: allManufacturerIds.length - validManufacturerIds.length }); } if (allOperatorIds.length !== validOperatorIds.length) { edgeLogger.warn('Removed stale operator submission items', { requestId: tracking.requestId, total: allOperatorIds.length, valid: validOperatorIds.length, removed: allOperatorIds.length - validOperatorIds.length }); } if (allOwnerIds.length !== validOwnerIds.length) { edgeLogger.warn('Removed stale property_owner submission items', { requestId: tracking.requestId, total: allOwnerIds.length, valid: validOwnerIds.length, removed: allOwnerIds.length - validOwnerIds.length }); } if (allDesignerIds.length !== validDesignerIds.length) { edgeLogger.warn('Removed stale designer submission items', { requestId: tracking.requestId, total: allDesignerIds.length, valid: validDesignerIds.length, removed: allDesignerIds.length - validDesignerIds.length }); } if (allParkIds.length !== validParkIds.length) { edgeLogger.warn('Removed stale park submission items', { requestId: tracking.requestId, total: allParkIds.length, valid: validParkIds.length, removed: allParkIds.length - validParkIds.length }); } if (allRideModelIds.length !== validRideModelIds.length) { edgeLogger.warn('Removed stale ride_model submission items', { requestId: tracking.requestId, total: allRideModelIds.length, valid: validRideModelIds.length, removed: allRideModelIds.length - validRideModelIds.length }); } // 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, owners: existingOwners.length, manufacturers: existingManufacturers.length, designers: existingDesigners.length, parks: existingParks.length, parksForRides: createdParks.length }); } // Helper to create submission using relational tables async function createSubmission( userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean; dependsOn?: string } = {} ) { const submissionId = crypto.randomUUID(); const itemId = crypto.randomUUID(); // Create content_submission (WITHOUT content field) const submissionData: any = { id: submissionId, user_id: userId, submission_type: type, status: 'pending', submitted_at: new Date().toISOString() }; if (options.escalated) { submissionData.escalated = true; submissionData.escalation_reason = 'Test escalation for priority testing'; } if (options.expiredLock) { submissionData.assigned_to = userId; submissionData.locked_until = new Date(Date.now() - 1000 * 60 * 30).toISOString(); } const { error: subError } = await supabase.from('content_submissions').insert(submissionData); if (subError) { edgeLogger.error('Error inserting content_submission', { type, error: subError.message }); throw subError; } // Insert into type-specific submission table const typeTableMap: 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 = 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(); if (typeError) { edgeLogger.error('Error inserting into type table', { table, type, error: typeError.message }); throw typeError; } typeDataId = insertedData?.id; } // Create submission_item using type-specific foreign keys const submissionItemData: any = { id: itemId, submission_id: submissionId, item_type: type, status: 'pending', order_index: 0, depends_on: options.dependsOn || null }; // Add the appropriate foreign key based on type if (type === 'park') { submissionItemData.park_submission_id = typeDataId; } else if (type === 'ride') { submissionItemData.ride_submission_id = typeDataId; } else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(type)) { submissionItemData.company_submission_id = typeDataId; } else if (type === 'ride_model') { submissionItemData.ride_model_submission_id = typeDataId; } else if (type === 'photo') { submissionItemData.photo_submission_id = typeDataId; } const { error: itemError } = await supabase.from('submission_items').insert(submissionItemData); 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 }; } const pluralizeCompanyType = (singular: string): string => { const pluralMap: Record = { 'manufacturer': 'manufacturers', 'operator': 'operators', 'designer': 'designers', 'property_owner': 'property_owners' }; return pluralMap[singular] || singular + 's'; }; // Create companies if (!stage || stage === 'companies') { const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner']; const selectedCompanyTypes = companyTypes.filter(compType => entityTypes.includes(pluralizeCompanyType(compType)) ); edgeLogger.info('Company generation started', { requestId: tracking.requestId, entityTypes, planCompanies: plan.companies, selectedCompanyTypes }); const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length); const extras = plan.companies % selectedCompanyTypes.length; for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) { const compType = selectedCompanyTypes[typeIndex]; 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}`; // Add full founded date with precision companyData.founded_date = `${companyData.founded_year}-01-01`; companyData.founded_date_precision = randomDatePrecision(); // Add defunct date for some companies (10% chance at level 3+) if (level >= 3 && Math.random() > 0.9) { const defunctYear = randomInt(companyData.founded_year + 10, 2024); companyData.defunct_date = `${defunctYear}-12-31`; companyData.defunct_date_precision = randomDatePrecision(); } } 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`; companyData.source_url = `https://example.com/source/${compType}/${i + 1}`; const notes = generateSubmissionNotes(fieldDensity, i, compType); if (notes) { companyData.submission_notes = notes; } } 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++; } } } // 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; const shouldVersionChain = includeVersionChains && createdParkSlugs.length > 0 && Math.random() < 0.15; let slug = `test-park-${i + 1}`; if (shouldConflict) { slug = randomItem(createdParkSlugs); summary.conflicts++; } else if (shouldVersionChain) { slug = randomItem(createdParkSlugs); summary.versionChains++; } const parkData: any = { name: shouldVersionChain ? `Test Park ${slug} (Updated)` : `Test Park ${i + 1}`, slug: slug, park_type: randomItem(['theme_park', 'amusement_park', 'water_park']), status: 'operating' }; if (level >= 1) { parkData.opening_date = randomDate(1950, 2024); parkData.opening_date_precision = randomDatePrecision(); parkData.description = `A ${parkData.park_type === 'theme_park' ? 'themed' : 'exciting'} park for all ages with various attractions.`; } if (level >= 2) { parkData.website_url = `https://test-park-${i + 1}.example.com`; 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; 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://cdn.thrillwiki.com/images/test-park-card-${i + 1}/card`; } 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(); if (ownerData) parkData.property_owner_id = ownerData.id; if (createdSubmissionItems.property_owner.length > 0 && !operatorItemId) { ownerItemId = randomItem(createdSubmissionItems.property_owner); } } if (Math.random() > 0.9) { parkData.closing_date = randomDate(2000, 2024); parkData.closing_date_precision = randomDatePrecision(); parkData.status = 'closed'; } parkData.source_url = `https://example.com/source/park/${i + 1}`; const notes = generateSubmissionNotes(fieldDensity, i, 'park'); if (notes) { parkData.submission_notes = notes; } parkData.banner_image_id = `test-park-banner-${i + 1}`; parkData.banner_image_url = `https://cdn.thrillwiki.com/images/test-park-banner-${i + 1}/banner`; } const options = { escalated: includeEscalated && Math.random() < 0.1, expiredLock: includeExpiredLocks && Math.random() < 0.1, dependsOn: operatorItemId || ownerItemId }; const { itemId } = await createSubmission(user.id, 'park', parkData, options); createdSubmissionItems.park.push(itemId); const { data: approvedPark } = await supabase .from('parks') .select('id') .eq('slug', slug) .maybeSingle(); if (approvedPark) { await registerTestEntity(supabase, 'park', slug, approvedPark.id, itemId, sessionId); } createdParks.push(slug); if (!shouldConflict && !shouldVersionChain) { createdParkSlugs.push(slug); } summary.parks++; } } // 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; const shouldVersionChain = includeVersionChains && createdRideSlugs.length > 0 && Math.random() < 0.15; let slug = `test-ride-${i + 1}`; if (shouldConflict) { slug = randomItem(createdRideSlugs); summary.conflicts++; } else if (shouldVersionChain) { slug = randomItem(createdRideSlugs); summary.versionChains++; } const parkSlug = randomItem(createdParks); const { data: parkData } = await supabase .from('parks') .select('id') .eq('slug', parkSlug) .maybeSingle(); const parkItemId = createdSubmissionItems.park.length > 0 ? randomItem(createdSubmissionItems.park) : undefined; const category = randomItem(['roller_coaster', 'flat_ride', 'water_ride', 'dark_ride', 'kiddie_ride', 'transport_ride', 'family_ride']); const rideData: any = { name: shouldVersionChain ? `Test Ride ${slug} (Updated)` : `Test Ride ${i + 1}`, slug: slug, category: category, status: 'operating', park_id: parkData?.id || null }; if (level >= 1) { rideData.opening_date = randomDate(2000, 2024); rideData.opening_date_precision = randomDatePrecision(); rideData.description = `An exciting ${category.replace('_', ' ')} attraction.`; rideData.height_requirement = randomInt(100, 140); } if (level >= 2) { rideData.max_speed_kmh = randomInt(40, 120); rideData.max_height_meters = randomInt(20, 100); rideData.duration_seconds = randomInt(60, 240); rideData.capacity_per_hour = randomInt(500, 2000); rideData.intensity_level = randomItem(['family', 'moderate', 'high', 'extreme']); } if (level >= 3) { if (createdCompanies.manufacturer.length > 0) { 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') { rideData.inversions = randomInt(0, 7); rideData.coaster_type = randomItem(['steel', 'wooden', 'hybrid']); rideData.seating_type = randomItem(['sit_down', 'inverted', 'flying', 'stand_up']); // Add material arrays for roller coasters if (shouldPopulateField(fieldDensity, i, 'medium')) { rideData.track_material = [randomItem(['steel', 'wood', 'hybrid'])]; rideData.support_material = [randomItem(['steel', 'wood', 'concrete'])]; rideData.propulsion_method = [randomItem(['chain_lift', 'cable_lift', 'launch', 'gravity', 'tire_drive'])]; } } rideData.card_image_id = `test-ride-card-${i + 1}`; rideData.card_image_url = `https://cdn.thrillwiki.com/images/test-ride-card-${i + 1}/card`; } // Add category-specific fields based on ride type if (category === 'water_ride') { Object.assign(rideData, generateWaterRideFields(fieldDensity, i)); } else if (category === 'dark_ride') { Object.assign(rideData, generateDarkRideFields(fieldDensity, i)); } else if (category === 'flat_ride') { Object.assign(rideData, generateFlatRideFields(fieldDensity, i)); } else if (category === 'kiddie_ride') { Object.assign(rideData, generateKiddieRideFields(fieldDensity, i)); } else if (category === 'transport_ride') { Object.assign(rideData, generateTransportationRideFields(fieldDensity, i)); } if (level >= 4) { if (createdCompanies.designer.length > 0) { 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); rideData.drop_height_meters = randomInt(10, 80); rideData.max_g_force = (Math.random() * 4 + 2).toFixed(1); rideData.source_url = `https://example.com/source/ride/${i + 1}`; const notes = generateSubmissionNotes(fieldDensity, i, 'ride'); if (notes) { rideData.submission_notes = notes; } // Add closing date for some rides if (Math.random() > 0.95) { rideData.closing_date = randomDate(2010, 2024); rideData.closing_date_precision = randomDatePrecision(); rideData.status = 'closed'; } rideData.banner_image_id = `test-ride-banner-${i + 1}`; rideData.banner_image_url = `https://cdn.thrillwiki.com/images/test-ride-banner-${i + 1}/banner`; } const options = { dependsOn: parkItemId }; const { submissionId, itemId, typeDataId } = await createSubmission(user.id, 'ride', rideData, options); // 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: typeDataId, stat_name: randomItem(['Airtime Duration', 'Zero-G Time', 'Track Gauge']), stat_value: Math.random() * 100, unit: randomItem(['seconds', 'mm']), category: 'technical' }); } for (let t = 0; t < randomInt(3, 5); t++) { await supabase.from('ride_submission_technical_specifications').insert({ 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', display_order: t }); } if (Math.random() > 0.7) { await supabase.from('ride_submission_name_history').insert({ ride_submission_id: typeDataId, former_name: `Original Name ${i + 1}`, date_changed: randomDate(2010, 2020), reason: 'Rebranding', order_index: 0 }); } } const { data: approvedRide } = await supabase .from('rides') .select('id') .eq('slug', slug) .maybeSingle(); if (approvedRide) { await registerTestEntity(supabase, 'ride', slug, approvedRide.id, itemId, sessionId); } if (!shouldConflict && !shouldVersionChain) { createdRideSlugs.push(slug); } summary.rides++; } } // 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); // Ensure we have valid manufacturer submission items if (createdSubmissionItems.manufacturer.length === 0) { edgeLogger.error('No valid manufacturers available for ride model', { requestId: tracking.requestId, modelIndex: i }); continue; // Skip this ride model } const { data: mfgData } = await supabase .from('companies') .select('id') .eq('slug', randomItem(createdCompanies.manufacturer)) .maybeSingle(); const mfgItemId = randomItem(createdSubmissionItems.manufacturer); 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 >= 2) { modelData.card_image_id = `test-model-card-${i + 1}`; modelData.card_image_url = `https://cdn.thrillwiki.com/images/test-model-card-${i + 1}/card`; modelData.source_url = `https://example.com/source/ride-model/${i + 1}`; const notes = generateSubmissionNotes(fieldDensity, i, 'ride model'); if (notes) { modelData.submission_notes = notes; } } if (level >= 3) { modelData.banner_image_id = `test-model-banner-${i + 1}`; modelData.banner_image_url = `https://cdn.thrillwiki.com/images/test-model-banner-${i + 1}/banner`; } const options = { dependsOn: mfgItemId }; const { itemId } = await createSubmission(user.id, 'ride_model', modelData, options); createdSubmissionItems.ride_model.push(itemId); 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, itemId, sessionId); } summary.rideModels++; } } // 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)); for (let i = 0; i < plan.photos; i++) { const photoCount = randomInt(1, Math.min(10, Math.ceil(plan.photos / 50) + 3)); const submissionId = crypto.randomUUID(); const photoSubmissionId = crypto.randomUUID(); let entityType = 'park'; let entityId = null; let parentId = null; if (Math.random() > 0.5 && approvedParks && approvedParks.length > 0) { entityType = 'park'; entityId = randomItem(approvedParks).id; } else if (approvedRides && approvedRides.length > 0) { entityType = 'ride'; const ride = randomItem(approvedRides); entityId = ride.id; parentId = ride.park_id; } if (!entityId) continue; // Create content_submission (WITHOUT content field) await supabase.from('content_submissions').insert({ id: submissionId, user_id: user.id, submission_type: 'photo', status: 'pending', 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, submission_id: submissionId, entity_type: entityType, entity_id: entityId, parent_id: parentId, title: Math.random() > 0.5 ? `${entityType} Photos Collection ${i + 1}` : null }); // Create photo_submission_items for (let p = 0; p < photoCount; p++) { const imageId = `test-photo-${crypto.randomUUID()}`; await supabase.from('photo_submission_items').insert({ photo_submission_id: photoSubmissionId, cloudflare_image_id: imageId, cloudflare_image_url: `https://cdn.thrillwiki.com/images/${imageId}/public`, caption: Math.random() > 0.3 ? `Test photo ${p + 1} - Great view of the ${entityType}` : null, title: Math.random() > 0.7 ? `Photo ${p + 1}` : null, filename: `photo-${p + 1}.jpg`, order_index: p, file_size: randomInt(500000, 5000000), mime_type: 'image/jpeg', date_taken: Math.random() > 0.5 ? randomDate(2015, 2024) : null }); summary.totalPhotoItems++; } summary.photos++; } } const executionTime = Date.now() - startTime; edgeLogger.info('Seed data generation completed', { requestId: tracking.requestId, duration: executionTime, summary, stage: stage || 'all' }); return new Response( JSON.stringify({ success: true, summary, time: (executionTime / 1000).toFixed(2), stage: stage || 'all', requestId: tracking.requestId }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } catch (error) { const duration = endRequest(tracking); edgeLogger.error('Seed error', { requestId: tracking.requestId, duration, error: error.message }); return new Response( JSON.stringify({ error: error.message, requestId: tracking.requestId }), { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } });