mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 14:51:13 -05:00
Implement Test Data Generator
This commit is contained in:
283
supabase/functions/seed-test-data/index.ts
Normal file
283
supabase/functions/seed-test-data/index.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
||||
|
||||
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[];
|
||||
includeDependencies: boolean;
|
||||
includeConflicts: boolean;
|
||||
includeVersionChains: boolean;
|
||||
includeEscalated: boolean;
|
||||
includeExpiredLocks: boolean;
|
||||
}
|
||||
|
||||
interface SeedPlan {
|
||||
parks: number;
|
||||
rides: number;
|
||||
companies: number;
|
||||
rideModels: number;
|
||||
}
|
||||
|
||||
const PRESETS: Record<string, SeedPlan> = {
|
||||
small: { parks: 5, rides: 10, companies: 3, rideModels: 2 },
|
||||
medium: { parks: 20, rides: 50, companies: 20, rideModels: 10 },
|
||||
large: { parks: 100, rides: 250, companies: 100, rideModels: 50 },
|
||||
stress: { parks: 400, rides: 1000, companies: 400, rideModels: 200 }
|
||||
};
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
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);
|
||||
|
||||
// Get auth header
|
||||
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' }
|
||||
});
|
||||
}
|
||||
|
||||
// Verify user is moderator
|
||||
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 } = await supabase.rpc('is_moderator', { _user_id: user.id });
|
||||
if (!isMod) {
|
||||
return new Response(JSON.stringify({ error: 'Must be moderator' }), {
|
||||
status: 403,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// Parse request
|
||||
const { preset = 'small', entityTypes = [], includeDependencies = true, includeEscalated = false, includeExpiredLocks = false }: SeedOptions = await req.json();
|
||||
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 summary = { parks: 0, rides: 0, companies: 0, rideModels: 0 };
|
||||
const createdParks: string[] = [];
|
||||
const createdCompanies: Record<string, string[]> = { manufacturer: [], operator: [], designer: [], property_owner: [] };
|
||||
|
||||
// Helper to create submission
|
||||
async function createSubmission(userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean } = {}) {
|
||||
const submissionId = crypto.randomUUID();
|
||||
const itemId = crypto.randomUUID();
|
||||
|
||||
const contentData = {
|
||||
action: 'create',
|
||||
metadata: {
|
||||
is_test_data: true,
|
||||
generated_at: new Date().toISOString(),
|
||||
generator_version: '1.0.0',
|
||||
preset
|
||||
}
|
||||
};
|
||||
|
||||
// Create content_submission
|
||||
const submissionData: any = {
|
||||
id: submissionId,
|
||||
user_id: userId,
|
||||
submission_type: type,
|
||||
status: 'pending',
|
||||
content: contentData,
|
||||
submitted_at: new Date().toISOString(),
|
||||
priority: options.escalated ? 10 : Math.floor(Math.random() * 5) + 1
|
||||
};
|
||||
|
||||
if (options.escalated) {
|
||||
submissionData.escalated = true;
|
||||
submissionData.escalation_reason = 'Test escalation';
|
||||
}
|
||||
|
||||
if (options.expiredLock) {
|
||||
submissionData.assigned_to = user.id;
|
||||
submissionData.locked_until = new Date(Date.now() - 1000 * 60 * 30).toISOString(); // 30 min ago
|
||||
}
|
||||
|
||||
const { error: subError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert(submissionData);
|
||||
|
||||
if (subError) throw subError;
|
||||
|
||||
// Create submission_item
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
id: itemId,
|
||||
submission_id: submissionId,
|
||||
item_type: type,
|
||||
item_data: itemData,
|
||||
status: 'pending',
|
||||
order_index: 0
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
// Create type-specific submission record
|
||||
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];
|
||||
if (table) {
|
||||
const typeData = { ...itemData, submission_id: submissionId };
|
||||
if (table === 'company_submissions') {
|
||||
typeData.company_type = type;
|
||||
}
|
||||
|
||||
const { error: typeError } = await supabase
|
||||
.from(table)
|
||||
.insert(typeData);
|
||||
|
||||
if (typeError) throw typeError;
|
||||
}
|
||||
|
||||
return submissionId;
|
||||
}
|
||||
|
||||
// Create parks
|
||||
if (entityTypes.includes('parks')) {
|
||||
for (let i = 0; i < plan.parks; i++) {
|
||||
const parkData = {
|
||||
name: `Test Park ${i + 1}`,
|
||||
slug: `test-park-${i + 1}`,
|
||||
description: 'Test park description',
|
||||
park_type: ['theme_park', 'amusement_park', 'water_park'][Math.floor(Math.random() * 3)],
|
||||
status: 'operating',
|
||||
opening_date: '2000-01-01'
|
||||
};
|
||||
|
||||
const options = {
|
||||
escalated: includeEscalated && Math.random() < 0.1,
|
||||
expiredLock: includeExpiredLocks && Math.random() < 0.1
|
||||
};
|
||||
|
||||
await createSubmission(user.id, 'park', parkData, options);
|
||||
createdParks.push(`test-park-${i + 1}`);
|
||||
summary.parks++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create companies
|
||||
const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner'];
|
||||
for (const compType of companyTypes) {
|
||||
if (entityTypes.includes(compType)) {
|
||||
const count = Math.floor(plan.companies / 4);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const companyData = {
|
||||
name: `Test ${compType} ${i + 1}`,
|
||||
slug: `test-${compType}-${i + 1}`,
|
||||
description: `Test ${compType} description`,
|
||||
company_type: compType,
|
||||
person_type: 'company',
|
||||
founded_year: 2000
|
||||
};
|
||||
|
||||
await createSubmission(user.id, compType, companyData);
|
||||
createdCompanies[compType].push(`test-${compType}-${i + 1}`);
|
||||
summary.companies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create rides (with dependencies if enabled)
|
||||
if (entityTypes.includes('rides') && includeDependencies && createdParks.length > 0) {
|
||||
for (let i = 0; i < plan.rides; i++) {
|
||||
// Get random park ID from database
|
||||
const parkSlug = createdParks[Math.floor(Math.random() * createdParks.length)];
|
||||
const { data: parkData } = await supabase
|
||||
.from('parks')
|
||||
.select('id')
|
||||
.eq('slug', parkSlug)
|
||||
.maybeSingle();
|
||||
|
||||
const rideData = {
|
||||
name: `Test Ride ${i + 1}`,
|
||||
slug: `test-ride-${i + 1}`,
|
||||
description: 'Test ride description',
|
||||
category: ['roller_coaster', 'flat_ride', 'water_ride'][Math.floor(Math.random() * 3)],
|
||||
status: 'operating',
|
||||
park_id: parkData?.id || null,
|
||||
opening_date: '2010-01-01'
|
||||
};
|
||||
|
||||
await createSubmission(user.id, 'ride', rideData);
|
||||
summary.rides++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create ride models
|
||||
if (entityTypes.includes('ride_models') && includeDependencies && createdCompanies.manufacturer.length > 0) {
|
||||
for (let i = 0; i < plan.rideModels; i++) {
|
||||
const mfgSlug = createdCompanies.manufacturer[Math.floor(Math.random() * createdCompanies.manufacturer.length)];
|
||||
const { data: mfgData } = await supabase
|
||||
.from('companies')
|
||||
.select('id')
|
||||
.eq('slug', mfgSlug)
|
||||
.maybeSingle();
|
||||
|
||||
const modelData = {
|
||||
name: `Test Model ${i + 1}`,
|
||||
slug: `test-model-${i + 1}`,
|
||||
manufacturer_id: mfgData?.id || null,
|
||||
category: 'roller_coaster',
|
||||
ride_type: 'steel',
|
||||
description: 'Test ride model'
|
||||
};
|
||||
|
||||
await createSubmission(user.id, 'ride_model', modelData);
|
||||
summary.rideModels++;
|
||||
}
|
||||
}
|
||||
|
||||
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
summary,
|
||||
time: elapsedTime
|
||||
}),
|
||||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Seed error:', error);
|
||||
return new Response(
|
||||
JSON.stringify({ error: error.message }),
|
||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user