mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 06:11:11 -05:00
Extend createEdgeFunction usage to novu-webhook, seed-test-data, and sitemap by removing manual boilerplate (CORS, auth, tracking, error handling) and replacing logging with span-based tracing; wire in EdgeFunctionContext for supabase, user, span, and requestId; preserve core logic including webhook validation, data seeding utilities, and sitemap caching.
1213 lines
44 KiB
TypeScript
1213 lines
44 KiB
TypeScript
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
|
import { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts';
|
|
import { corsHeaders } from '../_shared/cors.ts';
|
|
import { addSpanEvent } from '../_shared/logger.ts';
|
|
|
|
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<string, SeedPlan> = {
|
|
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<T>(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,
|
|
span: any
|
|
) {
|
|
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
|
|
addSpanEvent(span, 'registry_error', {
|
|
error: error.message,
|
|
entityType,
|
|
slug
|
|
});
|
|
}
|
|
}
|
|
|
|
// Validate that submission item IDs still exist in the database
|
|
async function validateSubmissionItemIds(
|
|
supabase: any,
|
|
itemIds: string[],
|
|
span: any
|
|
): Promise<string[]> {
|
|
if (itemIds.length === 0) return [];
|
|
|
|
const { data, error } = await supabase
|
|
.from('submission_items')
|
|
.select('id')
|
|
.in('id', itemIds);
|
|
|
|
if (error) {
|
|
addSpanEvent(span, 'validation_error', { error: error.message });
|
|
return [];
|
|
}
|
|
|
|
return data ? data.map(item => item.id) : [];
|
|
}
|
|
|
|
async function getExistingTestEntities(
|
|
supabase: any,
|
|
entityType: string,
|
|
span: any
|
|
): Promise<Array<{ slug: string; entity_id: string; submission_item_id: string }>> {
|
|
const { data, error } = await supabase
|
|
.from('test_data_registry')
|
|
.select('entity_slug, entity_id, submission_item_id')
|
|
.eq('entity_type', entityType);
|
|
|
|
if (error) {
|
|
addSpanEvent(span, 'fetch_entities_error', {
|
|
error: error.message,
|
|
entityType
|
|
});
|
|
return [];
|
|
}
|
|
|
|
return data || [];
|
|
}
|
|
|
|
async function getPendingSubmissionItems(
|
|
supabase: any,
|
|
itemType: string,
|
|
span: any
|
|
): Promise<Array<{ id: string; item_data_id: string }>> {
|
|
// 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) {
|
|
addSpanEvent(span, 'fetch_pending_error', {
|
|
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,
|
|
span: any
|
|
): Promise<string | null> {
|
|
const tableMap: Record<string, string> = {
|
|
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) {
|
|
addSpanEvent(span, 'fetch_slug_error', {
|
|
error: error.message,
|
|
itemDataId
|
|
});
|
|
return null;
|
|
}
|
|
|
|
return data?.slug || null;
|
|
}
|
|
|
|
serve(createEdgeFunction({
|
|
name: 'seed-test-data',
|
|
requiredRoles: ['superuser', 'admin', 'moderator'],
|
|
useServiceRole: true,
|
|
corsHeaders,
|
|
}, async (req, { span, supabase, userId, requestId }: EdgeFunctionContext) => {
|
|
|
|
const {
|
|
preset = 'small',
|
|
entityTypes = [],
|
|
fieldDensity = 'mixed',
|
|
includeDependencies = true,
|
|
includeConflicts = false,
|
|
includeVersionChains = false,
|
|
includeEscalated = false,
|
|
includeExpiredLocks = false,
|
|
stage
|
|
}: SeedOptions = await req.json();
|
|
|
|
addSpanEvent(span, 'seed_generation_started', {
|
|
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<string, string[]> = {
|
|
operator: [],
|
|
property_owner: [],
|
|
manufacturer: [],
|
|
designer: [],
|
|
park: [],
|
|
ride_model: []
|
|
};
|
|
|
|
const createdParks: string[] = [];
|
|
const createdCompanies: Record<string, string[]> = { manufacturer: [], operator: [], designer: [], property_owner: [] };
|
|
const createdParkSlugs: string[] = [];
|
|
const createdRideSlugs: string[] = [];
|
|
|
|
// Load existing test entities from registry
|
|
if (includeDependencies) {
|
|
addSpanEvent(span, 'loading_existing_entities');
|
|
|
|
const existingOperators = await getExistingTestEntities(supabase, 'operator', span);
|
|
const existingOwners = await getExistingTestEntities(supabase, 'property_owner', span);
|
|
const existingManufacturers = await getExistingTestEntities(supabase, 'manufacturer', span);
|
|
const existingDesigners = await getExistingTestEntities(supabase, 'designer', span);
|
|
const existingParks = await getExistingTestEntities(supabase, 'park', span);
|
|
|
|
const pendingOperators = await getPendingSubmissionItems(supabase, 'operator', span);
|
|
const pendingOwners = await getPendingSubmissionItems(supabase, 'property_owner', span);
|
|
const pendingManufacturers = await getPendingSubmissionItems(supabase, 'manufacturer', span);
|
|
const pendingDesigners = await getPendingSubmissionItems(supabase, 'designer', span);
|
|
const pendingParks = await getPendingSubmissionItems(supabase, 'park', span);
|
|
const pendingRideModels = await getPendingSubmissionItems(supabase, 'ride_model', span);
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
addSpanEvent(span, 'loaded_existing_entities', {
|
|
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) {
|
|
addSpanEvent(span, 'submission_insert_error', {
|
|
type,
|
|
error: subError.message
|
|
});
|
|
throw subError;
|
|
}
|
|
|
|
// Insert into type-specific submission table
|
|
const typeTableMap: Record<string, string> = {
|
|
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) {
|
|
addSpanEvent(span, 'type_table_insert_error', {
|
|
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) {
|
|
addSpanEvent(span, 'submission_item_insert_error', {
|
|
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<string, string> = {
|
|
'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))
|
|
);
|
|
|
|
addSpanEvent(span, 'company_generation_started', {
|
|
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);
|
|
|
|
addSpanEvent(span, 'creating_companies', { 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')) {
|
|
addSpanEvent(span, 'creating_parks', { 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) {
|
|
addSpanEvent(span, 'creating_rides', { 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) {
|
|
addSpanEvent(span, 'creating_ride_models', { 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) {
|
|
addSpanEvent(span, 'no_manufacturers_available', {
|
|
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) {
|
|
addSpanEvent(span, 'creating_photos', { 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;
|
|
|
|
addSpanEvent(span, 'seed_generation_completed', {
|
|
duration: executionTime,
|
|
summary,
|
|
stage: stage || 'all'
|
|
});
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
success: true,
|
|
summary,
|
|
time: (executionTime / 1000).toFixed(2),
|
|
stage: stage || 'all',
|
|
requestId
|
|
}),
|
|
{ headers: { 'Content-Type': 'application/json' } }
|
|
);
|
|
}));
|