mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
Fix entity submission pipelines
Refactor park updates, ride updates, and timeline event submissions to use dedicated relational tables instead of JSON blobs in `submission_items.item_data`. This enforces the "NO JSON IN SQL" rule, improving queryability, data integrity, and consistency across the pipeline.
This commit is contained in:
@@ -91,7 +91,9 @@ export interface ParkFormData {
|
|||||||
park_type: string;
|
park_type: string;
|
||||||
status: string;
|
status: string;
|
||||||
opening_date?: string;
|
opening_date?: string;
|
||||||
|
opening_date_precision?: string;
|
||||||
closing_date?: string;
|
closing_date?: string;
|
||||||
|
closing_date_precision?: string;
|
||||||
website_url?: string;
|
website_url?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -131,7 +133,9 @@ export interface RideFormData {
|
|||||||
designer_id?: string;
|
designer_id?: string;
|
||||||
ride_model_id?: string;
|
ride_model_id?: string;
|
||||||
opening_date?: string;
|
opening_date?: string;
|
||||||
|
opening_date_precision?: string;
|
||||||
closing_date?: string;
|
closing_date?: string;
|
||||||
|
closing_date_precision?: string;
|
||||||
max_speed_kmh?: number;
|
max_speed_kmh?: number;
|
||||||
max_height_meters?: number;
|
max_height_meters?: number;
|
||||||
length_meters?: number;
|
length_meters?: number;
|
||||||
@@ -890,21 +894,72 @@ export async function submitParkUpdate(
|
|||||||
|
|
||||||
if (submissionError) throw submissionError;
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
// Create the submission item with actual park data AND original data
|
// Extract changed fields
|
||||||
|
const changedFields = extractChangedFields(data, existingPark as any);
|
||||||
|
|
||||||
|
// Handle location data properly
|
||||||
|
let tempLocationData: any = null;
|
||||||
|
if (data.location) {
|
||||||
|
tempLocationData = {
|
||||||
|
name: data.location.name,
|
||||||
|
street_address: data.location.street_address || null,
|
||||||
|
city: data.location.city || null,
|
||||||
|
state_province: data.location.state_province || null,
|
||||||
|
country: data.location.country,
|
||||||
|
latitude: data.location.latitude,
|
||||||
|
longitude: data.location.longitude,
|
||||||
|
timezone: data.location.timezone || null,
|
||||||
|
postal_code: data.location.postal_code || null,
|
||||||
|
display_name: data.location.display_name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ FIXED: Insert into park_submissions table (relational pattern)
|
||||||
|
const { data: parkSubmission, error: parkSubmissionError } = await supabase
|
||||||
|
.from('park_submissions')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
name: changedFields.name ?? existingPark.name,
|
||||||
|
slug: changedFields.slug ?? existingPark.slug,
|
||||||
|
description: changedFields.description !== undefined ? changedFields.description : existingPark.description,
|
||||||
|
park_type: changedFields.park_type ?? existingPark.park_type,
|
||||||
|
status: changedFields.status ?? existingPark.status,
|
||||||
|
opening_date: changedFields.opening_date !== undefined ? changedFields.opening_date : existingPark.opening_date,
|
||||||
|
opening_date_precision: changedFields.opening_date_precision !== undefined ? changedFields.opening_date_precision : existingPark.opening_date_precision,
|
||||||
|
closing_date: changedFields.closing_date !== undefined ? changedFields.closing_date : existingPark.closing_date,
|
||||||
|
closing_date_precision: changedFields.closing_date_precision !== undefined ? changedFields.closing_date_precision : existingPark.closing_date_precision,
|
||||||
|
website_url: changedFields.website_url !== undefined ? changedFields.website_url : existingPark.website_url,
|
||||||
|
phone: changedFields.phone !== undefined ? changedFields.phone : existingPark.phone,
|
||||||
|
email: changedFields.email !== undefined ? changedFields.email : existingPark.email,
|
||||||
|
operator_id: changedFields.operator_id !== undefined ? changedFields.operator_id : existingPark.operator_id,
|
||||||
|
property_owner_id: changedFields.property_owner_id !== undefined ? changedFields.property_owner_id : existingPark.property_owner_id,
|
||||||
|
location_id: changedFields.location_id !== undefined ? changedFields.location_id : existingPark.location_id,
|
||||||
|
temp_location_data: tempLocationData,
|
||||||
|
banner_image_url: changedFields.banner_image_url !== undefined ? changedFields.banner_image_url : existingPark.banner_image_url,
|
||||||
|
banner_image_id: changedFields.banner_image_id !== undefined ? changedFields.banner_image_id : existingPark.banner_image_id,
|
||||||
|
card_image_url: changedFields.card_image_url !== undefined ? changedFields.card_image_url : existingPark.card_image_url,
|
||||||
|
card_image_id: changedFields.card_image_id !== undefined ? changedFields.card_image_id : existingPark.card_image_id,
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (parkSubmissionError) throw parkSubmissionError;
|
||||||
|
|
||||||
|
// ✅ Create submission_items referencing park_submission (no JSON data)
|
||||||
const { error: itemError } = await supabase
|
const { error: itemError } = await supabase
|
||||||
.from('submission_items')
|
.from('submission_items')
|
||||||
.insert({
|
.insert({
|
||||||
submission_id: submissionData.id,
|
submission_id: submissionData.id,
|
||||||
item_type: 'park',
|
item_type: 'park',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: JSON.parse(JSON.stringify({
|
item_data: {
|
||||||
...extractChangedFields(data, existingPark as any),
|
park_id: parkId, // Only reference IDs
|
||||||
park_id: parkId, // Always include for relational integrity
|
images: processedImages as unknown as Json
|
||||||
images: processedImages
|
},
|
||||||
})) as Json,
|
|
||||||
original_data: JSON.parse(JSON.stringify(existingPark)),
|
original_data: JSON.parse(JSON.stringify(existingPark)),
|
||||||
status: 'pending' as const,
|
status: 'pending' as const,
|
||||||
order_index: 0
|
order_index: 0,
|
||||||
|
park_submission_id: parkSubmission.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (itemError) throw itemError;
|
if (itemError) throw itemError;
|
||||||
@@ -1440,7 +1495,52 @@ export async function submitRideUpdate(
|
|||||||
|
|
||||||
if (submissionError) throw submissionError;
|
if (submissionError) throw submissionError;
|
||||||
|
|
||||||
// Create the submission item with actual ride data AND original data
|
// Extract changed fields
|
||||||
|
const changedFields = extractChangedFields(data, existingRide as any);
|
||||||
|
|
||||||
|
// ✅ FIXED: Insert into ride_submissions table (relational pattern)
|
||||||
|
const { data: rideSubmission, error: rideSubmissionError } = await supabase
|
||||||
|
.from('ride_submissions')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
name: changedFields.name ?? existingRide.name,
|
||||||
|
slug: changedFields.slug ?? existingRide.slug,
|
||||||
|
description: changedFields.description !== undefined ? changedFields.description : existingRide.description,
|
||||||
|
category: changedFields.category ?? existingRide.category,
|
||||||
|
status: changedFields.status ?? existingRide.status,
|
||||||
|
park_id: changedFields.park_id !== undefined ? changedFields.park_id : existingRide.park_id,
|
||||||
|
manufacturer_id: changedFields.manufacturer_id !== undefined ? changedFields.manufacturer_id : existingRide.manufacturer_id,
|
||||||
|
designer_id: changedFields.designer_id !== undefined ? changedFields.designer_id : existingRide.designer_id,
|
||||||
|
ride_model_id: changedFields.ride_model_id !== undefined ? changedFields.ride_model_id : existingRide.ride_model_id,
|
||||||
|
opening_date: changedFields.opening_date !== undefined ? changedFields.opening_date : existingRide.opening_date,
|
||||||
|
opening_date_precision: changedFields.opening_date_precision !== undefined ? changedFields.opening_date_precision : existingRide.opening_date_precision,
|
||||||
|
closing_date: changedFields.closing_date !== undefined ? changedFields.closing_date : existingRide.closing_date,
|
||||||
|
closing_date_precision: changedFields.closing_date_precision !== undefined ? changedFields.closing_date_precision : existingRide.closing_date_precision,
|
||||||
|
max_speed_kmh: changedFields.max_speed_kmh !== undefined ? changedFields.max_speed_kmh : existingRide.max_speed_kmh,
|
||||||
|
max_height_meters: changedFields.max_height_meters !== undefined ? changedFields.max_height_meters : existingRide.max_height_meters,
|
||||||
|
length_meters: changedFields.length_meters !== undefined ? changedFields.length_meters : existingRide.length_meters,
|
||||||
|
duration_seconds: changedFields.duration_seconds !== undefined ? changedFields.duration_seconds : existingRide.duration_seconds,
|
||||||
|
capacity_per_hour: changedFields.capacity_per_hour !== undefined ? changedFields.capacity_per_hour : existingRide.capacity_per_hour,
|
||||||
|
height_requirement: changedFields.height_requirement !== undefined ? changedFields.height_requirement : existingRide.height_requirement,
|
||||||
|
age_requirement: changedFields.age_requirement !== undefined ? changedFields.age_requirement : existingRide.age_requirement,
|
||||||
|
inversions: changedFields.inversions !== undefined ? changedFields.inversions : existingRide.inversions,
|
||||||
|
drop_height_meters: changedFields.drop_height_meters !== undefined ? changedFields.drop_height_meters : existingRide.drop_height_meters,
|
||||||
|
max_g_force: changedFields.max_g_force !== undefined ? changedFields.max_g_force : existingRide.max_g_force,
|
||||||
|
intensity_level: changedFields.intensity_level !== undefined ? changedFields.intensity_level : existingRide.intensity_level,
|
||||||
|
coaster_type: changedFields.coaster_type !== undefined ? changedFields.coaster_type : existingRide.coaster_type,
|
||||||
|
seating_type: changedFields.seating_type !== undefined ? changedFields.seating_type : existingRide.seating_type,
|
||||||
|
ride_sub_type: changedFields.ride_sub_type !== undefined ? changedFields.ride_sub_type : existingRide.ride_sub_type,
|
||||||
|
banner_image_url: changedFields.banner_image_url !== undefined ? changedFields.banner_image_url : existingRide.banner_image_url,
|
||||||
|
banner_image_id: changedFields.banner_image_id !== undefined ? changedFields.banner_image_id : existingRide.banner_image_id,
|
||||||
|
card_image_url: changedFields.card_image_url !== undefined ? changedFields.card_image_url : existingRide.card_image_url,
|
||||||
|
card_image_id: changedFields.card_image_id !== undefined ? changedFields.card_image_id : existingRide.card_image_id,
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (rideSubmissionError) throw rideSubmissionError;
|
||||||
|
|
||||||
|
// ✅ Create submission_items referencing ride_submission (no JSON data)
|
||||||
const { error: itemError } = await supabase
|
const { error: itemError } = await supabase
|
||||||
.from('submission_items')
|
.from('submission_items')
|
||||||
.insert({
|
.insert({
|
||||||
@@ -1448,13 +1548,13 @@ export async function submitRideUpdate(
|
|||||||
item_type: 'ride',
|
item_type: 'ride',
|
||||||
action_type: 'edit',
|
action_type: 'edit',
|
||||||
item_data: {
|
item_data: {
|
||||||
...extractChangedFields(data, existingRide as any),
|
ride_id: rideId, // Only reference IDs
|
||||||
ride_id: rideId, // Always include for relational integrity
|
|
||||||
images: processedImages as unknown as Json
|
images: processedImages as unknown as Json
|
||||||
},
|
},
|
||||||
original_data: JSON.parse(JSON.stringify(existingRide)),
|
original_data: JSON.parse(JSON.stringify(existingRide)),
|
||||||
status: 'pending' as const,
|
status: 'pending' as const,
|
||||||
order_index: 0
|
order_index: 0,
|
||||||
|
ride_submission_id: rideSubmission.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (itemError) throw itemError;
|
if (itemError) throw itemError;
|
||||||
@@ -2248,16 +2348,30 @@ export async function submitTimelineEvent(
|
|||||||
throw new Error('User ID is required for timeline event submission');
|
throw new Error('User ID is required for timeline event submission');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create submission content (minimal reference data only)
|
|
||||||
const content: Json = {
|
|
||||||
action: 'create',
|
|
||||||
entity_type: entityType,
|
|
||||||
entity_id: entityId,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the main submission record
|
// Create the main submission record
|
||||||
// Use atomic RPC function to create submission + items in transaction
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
const itemData: Record<string, any> = {
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'timeline_event',
|
||||||
|
status: 'pending' as const
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) {
|
||||||
|
handleError(submissionError, {
|
||||||
|
action: 'Submit timeline event',
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
throw new Error('Failed to create timeline event submission');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ FIXED: Insert into timeline_event_submissions table (relational pattern)
|
||||||
|
const { data: timelineSubmission, error: timelineSubmissionError } = await supabase
|
||||||
|
.from('timeline_event_submissions')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
entity_type: entityType,
|
entity_type: entityType,
|
||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
event_type: data.event_type,
|
event_type: data.event_type,
|
||||||
@@ -2272,34 +2386,45 @@ export async function submitTimelineEvent(
|
|||||||
from_location_id: data.from_location_id,
|
from_location_id: data.from_location_id,
|
||||||
to_location_id: data.to_location_id,
|
to_location_id: data.to_location_id,
|
||||||
is_public: true,
|
is_public: true,
|
||||||
};
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
const items = [{
|
if (timelineSubmissionError) {
|
||||||
item_type: 'timeline_event',
|
handleError(timelineSubmissionError, {
|
||||||
action_type: 'create',
|
action: 'Submit timeline event data',
|
||||||
item_data: itemData,
|
|
||||||
order_index: 0,
|
|
||||||
}];
|
|
||||||
|
|
||||||
const { data: submissionId, error } = await supabase
|
|
||||||
.rpc('create_submission_with_items', {
|
|
||||||
p_user_id: userId,
|
|
||||||
p_submission_type: 'timeline_event',
|
|
||||||
p_content: content,
|
|
||||||
p_items: items as unknown as Json[],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error || !submissionId) {
|
|
||||||
handleError(error || new Error('No submission ID returned'), {
|
|
||||||
action: 'Submit timeline event',
|
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
throw new Error('Failed to submit timeline event for review');
|
throw new Error('Failed to submit timeline event for review');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Create submission_items referencing timeline_event_submission (no JSON data)
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'timeline_event',
|
||||||
|
action_type: 'create',
|
||||||
|
item_data: {
|
||||||
|
entity_type: entityType,
|
||||||
|
entity_id: entityId
|
||||||
|
} as Json,
|
||||||
|
status: 'pending' as const,
|
||||||
|
order_index: 0,
|
||||||
|
timeline_event_submission_id: timelineSubmission.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) {
|
||||||
|
handleError(itemError, {
|
||||||
|
action: 'Create timeline event submission item',
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
throw new Error('Failed to link timeline event submission');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
submitted: true,
|
submitted: true,
|
||||||
submissionId: submissionId,
|
submissionId: submissionData.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2332,49 +2457,85 @@ export async function submitTimelineEventUpdate(
|
|||||||
// Extract only changed fields from form data
|
// Extract only changed fields from form data
|
||||||
const changedFields = extractChangedFields(data, originalEvent as Partial<Record<string, unknown>>);
|
const changedFields = extractChangedFields(data, originalEvent as Partial<Record<string, unknown>>);
|
||||||
|
|
||||||
const itemData: Record<string, unknown> = {
|
// Create the main submission record
|
||||||
...changedFields,
|
const { data: submissionData, error: submissionError } = await supabase
|
||||||
// Always include entity reference (for FK integrity)
|
.from('content_submissions')
|
||||||
|
.insert({
|
||||||
|
user_id: userId,
|
||||||
|
submission_type: 'timeline_event',
|
||||||
|
status: 'pending' as const
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (submissionError) {
|
||||||
|
handleError(submissionError, {
|
||||||
|
action: 'Update timeline event',
|
||||||
|
metadata: { eventId },
|
||||||
|
});
|
||||||
|
throw new Error('Failed to create timeline event update submission');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ FIXED: Insert into timeline_event_submissions table (relational pattern)
|
||||||
|
const { data: timelineSubmission, error: timelineSubmissionError } = await supabase
|
||||||
|
.from('timeline_event_submissions')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
entity_type: originalEvent.entity_type,
|
entity_type: originalEvent.entity_type,
|
||||||
entity_id: originalEvent.entity_id,
|
entity_id: originalEvent.entity_id,
|
||||||
|
event_type: changedFields.event_type !== undefined ? changedFields.event_type : originalEvent.event_type,
|
||||||
|
event_date: changedFields.event_date !== undefined ? (typeof changedFields.event_date === 'string' ? changedFields.event_date : changedFields.event_date.toISOString().split('T')[0]) : originalEvent.event_date,
|
||||||
|
event_date_precision: (changedFields.event_date_precision !== undefined ? changedFields.event_date_precision : originalEvent.event_date_precision) || 'day',
|
||||||
|
title: changedFields.title !== undefined ? changedFields.title : originalEvent.title,
|
||||||
|
description: changedFields.description !== undefined ? changedFields.description : originalEvent.description,
|
||||||
|
from_value: changedFields.from_value !== undefined ? changedFields.from_value : originalEvent.from_value,
|
||||||
|
to_value: changedFields.to_value !== undefined ? changedFields.to_value : originalEvent.to_value,
|
||||||
|
from_entity_id: changedFields.from_entity_id !== undefined ? changedFields.from_entity_id : originalEvent.from_entity_id,
|
||||||
|
to_entity_id: changedFields.to_entity_id !== undefined ? changedFields.to_entity_id : originalEvent.to_entity_id,
|
||||||
|
from_location_id: changedFields.from_location_id !== undefined ? changedFields.from_location_id : originalEvent.from_location_id,
|
||||||
|
to_location_id: changedFields.to_location_id !== undefined ? changedFields.to_location_id : originalEvent.to_location_id,
|
||||||
is_public: true,
|
is_public: true,
|
||||||
};
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
// Use atomic RPC function to create submission and item together
|
if (timelineSubmissionError) {
|
||||||
const { data: result, error: rpcError } = await supabase.rpc(
|
handleError(timelineSubmissionError, {
|
||||||
'create_submission_with_items',
|
action: 'Update timeline event data',
|
||||||
{
|
|
||||||
p_user_id: userId,
|
|
||||||
p_submission_type: 'timeline_event',
|
|
||||||
p_content: {
|
|
||||||
action: 'edit',
|
|
||||||
event_id: eventId,
|
|
||||||
entity_type: originalEvent.entity_type,
|
|
||||||
} as unknown as Json,
|
|
||||||
p_items: [
|
|
||||||
{
|
|
||||||
item_type: 'timeline_event',
|
|
||||||
action_type: 'edit',
|
|
||||||
item_data: itemData,
|
|
||||||
original_data: originalEvent,
|
|
||||||
status: 'pending' as const,
|
|
||||||
order_index: 0,
|
|
||||||
}
|
|
||||||
] as unknown as Json[],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (rpcError || !result) {
|
|
||||||
handleError(rpcError || new Error('No result returned'), {
|
|
||||||
action: 'Update timeline event',
|
|
||||||
metadata: { eventId },
|
metadata: { eventId },
|
||||||
});
|
});
|
||||||
throw new Error('Failed to submit timeline event update');
|
throw new Error('Failed to submit timeline event update');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Create submission_items referencing timeline_event_submission (no JSON data)
|
||||||
|
const { error: itemError } = await supabase
|
||||||
|
.from('submission_items')
|
||||||
|
.insert({
|
||||||
|
submission_id: submissionData.id,
|
||||||
|
item_type: 'timeline_event',
|
||||||
|
action_type: 'edit',
|
||||||
|
item_data: {
|
||||||
|
event_id: eventId,
|
||||||
|
entity_type: originalEvent.entity_type,
|
||||||
|
entity_id: originalEvent.entity_id
|
||||||
|
} as Json,
|
||||||
|
original_data: JSON.parse(JSON.stringify(originalEvent)),
|
||||||
|
status: 'pending' as const,
|
||||||
|
order_index: 0,
|
||||||
|
timeline_event_submission_id: timelineSubmission.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemError) {
|
||||||
|
handleError(itemError, {
|
||||||
|
action: 'Create timeline event update submission item',
|
||||||
|
metadata: { eventId },
|
||||||
|
});
|
||||||
|
throw new Error('Failed to link timeline event update submission');
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
submitted: true,
|
submitted: true,
|
||||||
submissionId: result,
|
submissionId: submissionData.id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1653,6 +1653,37 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
parkId = data.park_id;
|
parkId = data.park_id;
|
||||||
delete data.park_id; // Remove ID from update data
|
delete data.park_id; // Remove ID from update data
|
||||||
|
|
||||||
|
// ✅ FIXED: Handle location updates from temp_location_data
|
||||||
|
if (data.temp_location_data && !data.location_id) {
|
||||||
|
edgeLogger.info('Creating location from temp data for update', {
|
||||||
|
action: 'approval_create_location_update',
|
||||||
|
locationName: data.temp_location_data.name
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: newLocation, error: locationError } = await supabase
|
||||||
|
.from('locations')
|
||||||
|
.insert({
|
||||||
|
name: data.temp_location_data.name,
|
||||||
|
street_address: data.temp_location_data.street_address || null,
|
||||||
|
city: data.temp_location_data.city,
|
||||||
|
state_province: data.temp_location_data.state_province,
|
||||||
|
country: data.temp_location_data.country,
|
||||||
|
latitude: data.temp_location_data.latitude,
|
||||||
|
longitude: data.temp_location_data.longitude,
|
||||||
|
timezone: data.temp_location_data.timezone,
|
||||||
|
postal_code: data.temp_location_data.postal_code
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (locationError) {
|
||||||
|
throw new Error(`Failed to create location: ${locationError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.location_id = newLocation.id;
|
||||||
|
}
|
||||||
|
delete data.temp_location_data;
|
||||||
|
|
||||||
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
const normalizedData = normalizeParkTypeValue(normalizeStatusValue(data));
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
||||||
@@ -1764,6 +1795,89 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
if (error) throw new Error(`Failed to update ride: ${error.message}`);
|
if (error) throw new Error(`Failed to update ride: ${error.message}`);
|
||||||
|
|
||||||
|
// ✅ FIXED: Handle nested data updates (technical specs, coaster stats, name history)
|
||||||
|
// For updates, we typically replace all related data rather than merge
|
||||||
|
// Delete existing and insert new
|
||||||
|
if (technicalSpecifications.length > 0) {
|
||||||
|
// Delete existing specs
|
||||||
|
await supabase
|
||||||
|
.from('ride_technical_specifications')
|
||||||
|
.delete()
|
||||||
|
.eq('ride_id', rideId);
|
||||||
|
|
||||||
|
// Insert new specs
|
||||||
|
const techSpecsToInsert = technicalSpecifications.map((spec: any) => ({
|
||||||
|
ride_id: rideId,
|
||||||
|
spec_name: spec.spec_name,
|
||||||
|
spec_value: spec.spec_value,
|
||||||
|
spec_unit: spec.spec_unit || null,
|
||||||
|
category: spec.category || null,
|
||||||
|
display_order: spec.display_order || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { error: techSpecError } = await supabase
|
||||||
|
.from('ride_technical_specifications')
|
||||||
|
.insert(techSpecsToInsert);
|
||||||
|
|
||||||
|
if (techSpecError) {
|
||||||
|
edgeLogger.error('Failed to update technical specifications', { action: 'approval_update_specs', error: techSpecError.message, rideId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coasterStatistics.length > 0) {
|
||||||
|
// Delete existing stats
|
||||||
|
await supabase
|
||||||
|
.from('ride_coaster_stats')
|
||||||
|
.delete()
|
||||||
|
.eq('ride_id', rideId);
|
||||||
|
|
||||||
|
// Insert new stats
|
||||||
|
const statsToInsert = coasterStatistics.map((stat: any) => ({
|
||||||
|
ride_id: rideId,
|
||||||
|
stat_name: stat.stat_name,
|
||||||
|
stat_value: stat.stat_value,
|
||||||
|
unit: stat.unit || null,
|
||||||
|
category: stat.category || null,
|
||||||
|
description: stat.description || null,
|
||||||
|
display_order: stat.display_order || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { error: statsError } = await supabase
|
||||||
|
.from('ride_coaster_stats')
|
||||||
|
.insert(statsToInsert);
|
||||||
|
|
||||||
|
if (statsError) {
|
||||||
|
edgeLogger.error('Failed to update coaster statistics', { action: 'approval_update_stats', error: statsError.message, rideId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameHistory.length > 0) {
|
||||||
|
// Delete existing name history
|
||||||
|
await supabase
|
||||||
|
.from('ride_name_history')
|
||||||
|
.delete()
|
||||||
|
.eq('ride_id', rideId);
|
||||||
|
|
||||||
|
// Insert new name history
|
||||||
|
const namesToInsert = nameHistory.map((name: any) => ({
|
||||||
|
ride_id: rideId,
|
||||||
|
former_name: name.former_name,
|
||||||
|
date_changed: name.date_changed || null,
|
||||||
|
reason: name.reason || null,
|
||||||
|
from_year: name.from_year || null,
|
||||||
|
to_year: name.to_year || null,
|
||||||
|
order_index: name.order_index || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { error: namesError } = await supabase
|
||||||
|
.from('ride_name_history')
|
||||||
|
.insert(namesToInsert);
|
||||||
|
|
||||||
|
if (namesError) {
|
||||||
|
edgeLogger.error('Failed to update name history', { action: 'approval_update_names', error: namesError.message, rideId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update park ride counts after successful ride update
|
// Update park ride counts after successful ride update
|
||||||
if (parkId) {
|
if (parkId) {
|
||||||
edgeLogger.info('Updating ride counts for park', { action: 'approval_update_counts', parkId });
|
edgeLogger.info('Updating ride counts for park', { action: 'approval_update_counts', parkId });
|
||||||
|
|||||||
Reference in New Issue
Block a user