mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 09:11:12 -05:00
215 lines
7.3 KiB
TypeScript
215 lines
7.3 KiB
TypeScript
import { Database } from '@/integrations/supabase/types';
|
|
|
|
type ParkInsert = Database['public']['Tables']['parks']['Insert'];
|
|
type RideInsert = Database['public']['Tables']['rides']['Insert'];
|
|
type CompanyInsert = Database['public']['Tables']['companies']['Insert'];
|
|
type RideModelInsert = Database['public']['Tables']['ride_models']['Insert'];
|
|
|
|
/**
|
|
* Transform park submission data to database insert format
|
|
*/
|
|
export function transformParkData(submissionData: any): ParkInsert {
|
|
return {
|
|
name: submissionData.name,
|
|
slug: submissionData.slug,
|
|
description: submissionData.description || null,
|
|
park_type: submissionData.park_type,
|
|
status: normalizeStatus(submissionData.status),
|
|
opening_date: submissionData.opening_date || null,
|
|
closing_date: submissionData.closing_date || null,
|
|
website_url: submissionData.website_url || null,
|
|
phone: submissionData.phone || null,
|
|
email: submissionData.email || null,
|
|
operator_id: submissionData.operator_id || null,
|
|
property_owner_id: submissionData.property_owner_id || null,
|
|
location_id: submissionData.location_id || null,
|
|
banner_image_url: submissionData.banner_image_url || null,
|
|
banner_image_id: submissionData.banner_image_id || null,
|
|
card_image_url: submissionData.card_image_url || null,
|
|
card_image_id: submissionData.card_image_id || null,
|
|
average_rating: 0,
|
|
review_count: 0,
|
|
ride_count: 0,
|
|
coaster_count: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Transform ride submission data to database insert format
|
|
*/
|
|
export function transformRideData(submissionData: any): RideInsert {
|
|
// Parse JSON fields if they're strings
|
|
let coasterStats = null;
|
|
let technicalSpecs = null;
|
|
let formerNames = null;
|
|
|
|
try {
|
|
if (submissionData.coaster_stats) {
|
|
coasterStats = typeof submissionData.coaster_stats === 'string'
|
|
? JSON.parse(submissionData.coaster_stats)
|
|
: submissionData.coaster_stats;
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse coaster_stats:', e);
|
|
}
|
|
|
|
try {
|
|
if (submissionData.technical_specs) {
|
|
technicalSpecs = typeof submissionData.technical_specs === 'string'
|
|
? JSON.parse(submissionData.technical_specs)
|
|
: submissionData.technical_specs;
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse technical_specs:', e);
|
|
}
|
|
|
|
try {
|
|
if (submissionData.former_names) {
|
|
formerNames = typeof submissionData.former_names === 'string'
|
|
? JSON.parse(submissionData.former_names)
|
|
: submissionData.former_names;
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse former_names:', e);
|
|
}
|
|
|
|
return {
|
|
name: submissionData.name,
|
|
slug: submissionData.slug,
|
|
description: submissionData.description || null,
|
|
category: submissionData.category,
|
|
ride_sub_type: submissionData.ride_sub_type || null,
|
|
status: normalizeStatus(submissionData.status),
|
|
park_id: submissionData.park_id,
|
|
ride_model_id: submissionData.ride_model_id || null,
|
|
manufacturer_id: submissionData.manufacturer_id || null,
|
|
designer_id: submissionData.designer_id || null,
|
|
opening_date: submissionData.opening_date || null,
|
|
closing_date: submissionData.closing_date || null,
|
|
height_requirement: submissionData.height_requirement || null,
|
|
age_requirement: submissionData.age_requirement || null,
|
|
capacity_per_hour: submissionData.capacity_per_hour || null,
|
|
duration_seconds: submissionData.duration_seconds || null,
|
|
max_speed_kmh: submissionData.max_speed_kmh || null,
|
|
max_height_meters: submissionData.max_height_meters || null,
|
|
length_meters: submissionData.length_meters || null,
|
|
drop_height_meters: submissionData.drop_height_meters || null,
|
|
inversions: submissionData.inversions || null,
|
|
max_g_force: submissionData.max_g_force || null,
|
|
coaster_type: submissionData.coaster_type || null,
|
|
seating_type: submissionData.seating_type || null,
|
|
intensity_level: submissionData.intensity_level || null,
|
|
coaster_stats: coasterStats,
|
|
technical_specs: technicalSpecs,
|
|
former_names: formerNames || [],
|
|
banner_image_url: submissionData.banner_image_url || null,
|
|
banner_image_id: submissionData.banner_image_id || null,
|
|
card_image_url: submissionData.card_image_url || null,
|
|
card_image_id: submissionData.card_image_id || null,
|
|
image_url: submissionData.image_url || null,
|
|
average_rating: 0,
|
|
review_count: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Transform company submission data to database insert format
|
|
*/
|
|
export function transformCompanyData(
|
|
submissionData: any,
|
|
companyType: 'manufacturer' | 'operator' | 'property_owner' | 'designer'
|
|
): CompanyInsert {
|
|
return {
|
|
name: submissionData.name,
|
|
slug: submissionData.slug,
|
|
description: submissionData.description || null,
|
|
company_type: companyType,
|
|
person_type: submissionData.person_type || 'company',
|
|
founded_year: submissionData.founded_year || null,
|
|
headquarters_location: submissionData.headquarters_location || null,
|
|
website_url: submissionData.website_url || null,
|
|
logo_url: submissionData.logo_url || null,
|
|
average_rating: 0,
|
|
review_count: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Transform ride model submission data to database insert format
|
|
*/
|
|
export function transformRideModelData(submissionData: any): RideModelInsert {
|
|
let technicalSpecs = null;
|
|
|
|
try {
|
|
if (submissionData.technical_specs) {
|
|
technicalSpecs = typeof submissionData.technical_specs === 'string'
|
|
? JSON.parse(submissionData.technical_specs)
|
|
: submissionData.technical_specs;
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse technical_specs:', e);
|
|
}
|
|
|
|
return {
|
|
name: submissionData.name,
|
|
slug: submissionData.slug,
|
|
manufacturer_id: submissionData.manufacturer_id,
|
|
category: submissionData.category,
|
|
ride_type: submissionData.ride_type || null,
|
|
description: submissionData.description || null,
|
|
technical_specs: technicalSpecs,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Normalize status values to match database enums
|
|
*/
|
|
function normalizeStatus(status: string): string {
|
|
if (!status) return 'operating';
|
|
|
|
const statusMap: Record<string, string> = {
|
|
'Operating': 'operating',
|
|
'operating': 'operating',
|
|
'Seasonal': 'seasonal',
|
|
'seasonal': 'seasonal',
|
|
'Closed Temporarily': 'closed_temporarily',
|
|
'closed_temporarily': 'closed_temporarily',
|
|
'Closed Permanently': 'closed_permanently',
|
|
'closed_permanently': 'closed_permanently',
|
|
'Under Construction': 'under_construction',
|
|
'under_construction': 'under_construction',
|
|
'Planned': 'planned',
|
|
'planned': 'planned',
|
|
'SBNO': 'sbno',
|
|
'sbno': 'sbno',
|
|
};
|
|
|
|
return statusMap[status] || 'operating';
|
|
}
|
|
|
|
/**
|
|
* Extract Cloudflare image ID from URL
|
|
*/
|
|
export function extractImageId(url: string): string {
|
|
const match = url.match(/\/([a-f0-9-]{36})\//);
|
|
return match ? match[1] : '';
|
|
}
|
|
|
|
/**
|
|
* Validate and sanitize submission data before transformation
|
|
*/
|
|
export function validateSubmissionData(data: any, itemType: string): void {
|
|
if (!data.name || typeof data.name !== 'string' || data.name.trim() === '') {
|
|
throw new Error(`${itemType} name is required`);
|
|
}
|
|
|
|
if (!data.slug || typeof data.slug !== 'string' || data.slug.trim() === '') {
|
|
throw new Error(`${itemType} slug is required`);
|
|
}
|
|
|
|
// Validate slug format
|
|
if (!/^[a-z0-9-]+$/.test(data.slug)) {
|
|
throw new Error(`${itemType} slug must contain only lowercase letters, numbers, and hyphens`);
|
|
}
|
|
}
|