mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 17:51:14 -05:00
Refactor: Enforce submission queue for all edits
This commit is contained in:
@@ -251,6 +251,35 @@ function topologicalSort(items: SubmissionItemWithDeps[]): SubmissionItemWithDep
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract image URLs from ImageAssignments structure
|
||||
*/
|
||||
function extractImageAssignments(images: any) {
|
||||
if (!images || !images.uploaded || !Array.isArray(images.uploaded)) {
|
||||
return {
|
||||
banner_image_url: null,
|
||||
banner_image_id: null,
|
||||
card_image_url: null,
|
||||
card_image_id: null,
|
||||
};
|
||||
}
|
||||
|
||||
const bannerImage = images.banner_assignment !== null && images.banner_assignment !== undefined
|
||||
? images.uploaded[images.banner_assignment]
|
||||
: null;
|
||||
|
||||
const cardImage = images.card_assignment !== null && images.card_assignment !== undefined
|
||||
? images.uploaded[images.card_assignment]
|
||||
: null;
|
||||
|
||||
return {
|
||||
banner_image_url: bannerImage?.url || null,
|
||||
banner_image_id: bannerImage?.cloudflare_id || null,
|
||||
card_image_url: cardImage?.url || null,
|
||||
card_image_id: cardImage?.cloudflare_id || null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions to create entities with dependency resolution
|
||||
*/
|
||||
@@ -258,18 +287,61 @@ async function createPark(data: any, dependencyMap: Map<string, string>): Promis
|
||||
const { transformParkData, validateSubmissionData } = await import('./entityTransformers');
|
||||
const { ensureUniqueSlug } = await import('./slugUtils');
|
||||
|
||||
// Validate input data
|
||||
validateSubmissionData(data, 'Park');
|
||||
// Check if this is an edit (has park_id)
|
||||
const isEdit = !!data.park_id;
|
||||
|
||||
// Resolve dependencies
|
||||
if (isEdit) {
|
||||
// Handle park edit
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Extract image assignments from ImageAssignments structure
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Update the park
|
||||
const updateData: any = {
|
||||
name: resolvedData.name,
|
||||
slug: resolvedData.slug,
|
||||
description: resolvedData.description || null,
|
||||
park_type: resolvedData.park_type,
|
||||
status: resolvedData.status,
|
||||
opening_date: resolvedData.opening_date || null,
|
||||
closing_date: resolvedData.closing_date || null,
|
||||
website_url: resolvedData.website_url || null,
|
||||
phone: resolvedData.phone || null,
|
||||
email: resolvedData.email || null,
|
||||
operator_id: resolvedData.operator_id || null,
|
||||
property_owner_id: resolvedData.property_owner_id || null,
|
||||
location_id: resolvedData.location_id || null,
|
||||
...imageData,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from('parks')
|
||||
.update(updateData)
|
||||
.eq('id', data.park_id);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating park:', error);
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
return data.park_id;
|
||||
}
|
||||
|
||||
// Handle park creation
|
||||
validateSubmissionData(data, 'Park');
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Ensure unique slug
|
||||
const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'parks');
|
||||
resolvedData.slug = uniqueSlug;
|
||||
|
||||
// Extract image assignments
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Transform to database format
|
||||
const parkData = transformParkData(resolvedData);
|
||||
const parkData = { ...transformParkData(resolvedData), ...imageData };
|
||||
|
||||
// Insert into database
|
||||
const { data: park, error } = await supabase
|
||||
@@ -290,25 +362,75 @@ async function createRide(data: any, dependencyMap: Map<string, string>): Promis
|
||||
const { transformRideData, validateSubmissionData } = await import('./entityTransformers');
|
||||
const { ensureUniqueSlug } = await import('./slugUtils');
|
||||
|
||||
// Validate input data
|
||||
validateSubmissionData(data, 'Ride');
|
||||
// Check if this is an edit (has ride_id)
|
||||
const isEdit = !!data.ride_id;
|
||||
|
||||
// Resolve dependencies
|
||||
if (isEdit) {
|
||||
// Handle ride edit
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Extract image assignments from ImageAssignments structure
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Update the ride
|
||||
const updateData: any = {
|
||||
name: resolvedData.name,
|
||||
slug: resolvedData.slug,
|
||||
description: resolvedData.description,
|
||||
category: resolvedData.category,
|
||||
ride_sub_type: resolvedData.ride_sub_type,
|
||||
status: resolvedData.status,
|
||||
opening_date: resolvedData.opening_date,
|
||||
closing_date: resolvedData.closing_date,
|
||||
height_requirement: resolvedData.height_requirement,
|
||||
age_requirement: resolvedData.age_requirement,
|
||||
capacity_per_hour: resolvedData.capacity_per_hour,
|
||||
duration_seconds: resolvedData.duration_seconds,
|
||||
max_speed_kmh: resolvedData.max_speed_kmh,
|
||||
max_height_meters: resolvedData.max_height_meters,
|
||||
length_meters: resolvedData.length_meters,
|
||||
inversions: resolvedData.inversions,
|
||||
coaster_type: resolvedData.coaster_type,
|
||||
seating_type: resolvedData.seating_type,
|
||||
intensity_level: resolvedData.intensity_level,
|
||||
drop_height_meters: resolvedData.drop_height_meters,
|
||||
max_g_force: resolvedData.max_g_force,
|
||||
manufacturer_id: resolvedData.manufacturer_id,
|
||||
ride_model_id: resolvedData.ride_model_id,
|
||||
...imageData,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from('rides')
|
||||
.update(updateData)
|
||||
.eq('id', data.ride_id);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating ride:', error);
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
return data.ride_id;
|
||||
}
|
||||
|
||||
// Handle ride creation
|
||||
validateSubmissionData(data, 'Ride');
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Validate park_id is present (required for rides)
|
||||
if (!resolvedData.park_id) {
|
||||
throw new Error('Ride must be associated with a park');
|
||||
}
|
||||
|
||||
// Ensure unique slug
|
||||
const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'rides');
|
||||
resolvedData.slug = uniqueSlug;
|
||||
|
||||
// Transform to database format
|
||||
const rideData = transformRideData(resolvedData);
|
||||
// Extract image assignments
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Transform to database format
|
||||
const rideData = { ...transformRideData(resolvedData), ...imageData };
|
||||
|
||||
// Insert into database
|
||||
const { data: ride, error } = await supabase
|
||||
.from('rides')
|
||||
.insert(rideData)
|
||||
@@ -331,20 +453,54 @@ async function createCompany(
|
||||
const { transformCompanyData, validateSubmissionData } = await import('./entityTransformers');
|
||||
const { ensureUniqueSlug } = await import('./slugUtils');
|
||||
|
||||
// Validate input data
|
||||
validateSubmissionData(data, 'Company');
|
||||
// Check if this is an edit (has company_id)
|
||||
const isEdit = !!data.id;
|
||||
|
||||
// Resolve dependencies
|
||||
if (isEdit) {
|
||||
// Handle company edit
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Extract image assignments from ImageAssignments structure
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Update the company
|
||||
const updateData: any = {
|
||||
name: resolvedData.name,
|
||||
slug: resolvedData.slug,
|
||||
description: resolvedData.description || null,
|
||||
website_url: resolvedData.website_url || null,
|
||||
founded_year: resolvedData.founded_year || null,
|
||||
headquarters_location: resolvedData.headquarters_location || null,
|
||||
...imageData,
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
const { error } = await supabase
|
||||
.from('companies')
|
||||
.update(updateData)
|
||||
.eq('id', data.id);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating company:', error);
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
return data.id;
|
||||
}
|
||||
|
||||
// Handle company creation
|
||||
validateSubmissionData(data, 'Company');
|
||||
const resolvedData = resolveDependencies(data, dependencyMap);
|
||||
|
||||
// Ensure unique slug
|
||||
const uniqueSlug = await ensureUniqueSlug(resolvedData.slug, 'companies');
|
||||
resolvedData.slug = uniqueSlug;
|
||||
|
||||
// Transform to database format
|
||||
const companyData = transformCompanyData(resolvedData, companyType as any);
|
||||
// Extract image assignments
|
||||
const imageData = extractImageAssignments(resolvedData.images);
|
||||
|
||||
// Transform to database format
|
||||
const companyData = { ...transformCompanyData(resolvedData, companyType as any), ...imageData };
|
||||
|
||||
// Insert into database
|
||||
const { data: company, error } = await supabase
|
||||
.from('companies')
|
||||
.insert(companyData)
|
||||
|
||||
@@ -175,60 +175,22 @@ export default function ParkDetail() {
|
||||
if (!user || !park) return;
|
||||
|
||||
try {
|
||||
if (isModerator()) {
|
||||
// Moderators can update directly
|
||||
const updateData: any = {
|
||||
name: parkData.name,
|
||||
slug: parkData.slug,
|
||||
description: parkData.description || null,
|
||||
park_type: parkData.park_type,
|
||||
status: parkData.status,
|
||||
opening_date: parkData.opening_date || null,
|
||||
closing_date: parkData.closing_date || null,
|
||||
website_url: parkData.website_url || null,
|
||||
phone: parkData.phone || null,
|
||||
email: parkData.email || null,
|
||||
banner_image_url: parkData.banner_image_url || null,
|
||||
banner_image_id: parkData.banner_image_id || null,
|
||||
card_image_url: parkData.card_image_url || null,
|
||||
card_image_id: parkData.card_image_id || null,
|
||||
operator_id: parkData.operator_id || null,
|
||||
property_owner_id: parkData.property_owner_id || null
|
||||
};
|
||||
// Everyone goes through submission queue
|
||||
const { submitParkUpdate } = await import('@/lib/entitySubmissionHelpers');
|
||||
await submitParkUpdate(park.id, parkData, user.id);
|
||||
|
||||
const { error } = await supabase
|
||||
.from('parks')
|
||||
.update({
|
||||
...updateData,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', park.id);
|
||||
toast({
|
||||
title: "Edit Submitted",
|
||||
description: isModerator()
|
||||
? "Your edit has been submitted. You can approve it in the moderation queue."
|
||||
: "Your park edit has been submitted for review.",
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast({
|
||||
title: "Park Updated",
|
||||
description: "The park has been updated successfully.",
|
||||
});
|
||||
|
||||
setIsEditParkModalOpen(false);
|
||||
fetchParkData();
|
||||
} else {
|
||||
// Regular users submit for moderation
|
||||
const { submitParkUpdate } = await import('@/lib/entitySubmissionHelpers');
|
||||
await submitParkUpdate(park.id, parkData, user.id);
|
||||
|
||||
toast({
|
||||
title: "Edit Submitted",
|
||||
description: "Your park edit has been submitted for review.",
|
||||
});
|
||||
|
||||
setIsEditParkModalOpen(false);
|
||||
}
|
||||
setIsEditParkModalOpen(false);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: error.message || "Failed to update park.",
|
||||
description: error.message || "Failed to submit park edit.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,75 +125,21 @@ export default function RideDetail() {
|
||||
};
|
||||
|
||||
const handleEditSubmit = async (data: any) => {
|
||||
if (!user || !ride) return;
|
||||
|
||||
try {
|
||||
if (isModerator) {
|
||||
// Moderators can edit directly
|
||||
const { error } = await supabase
|
||||
.from('rides')
|
||||
.update({
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
category: data.category,
|
||||
ride_sub_type: data.ride_sub_type,
|
||||
status: data.status,
|
||||
opening_date: data.opening_date,
|
||||
closing_date: data.closing_date,
|
||||
height_requirement: data.height_requirement,
|
||||
age_requirement: data.age_requirement,
|
||||
capacity_per_hour: data.capacity_per_hour,
|
||||
duration_seconds: data.duration_seconds,
|
||||
max_speed_kmh: data.max_speed_kmh,
|
||||
max_height_meters: data.max_height_meters,
|
||||
length_meters: data.length_meters,
|
||||
inversions: data.inversions,
|
||||
coaster_type: data.coaster_type,
|
||||
seating_type: data.seating_type,
|
||||
intensity_level: data.intensity_level,
|
||||
drop_height_meters: data.drop_height_meters,
|
||||
max_g_force: data.max_g_force,
|
||||
banner_image_url: data.banner_image_url,
|
||||
banner_image_id: data.banner_image_id,
|
||||
card_image_url: data.card_image_url,
|
||||
card_image_id: data.card_image_id,
|
||||
manufacturer_id: data.manufacturer_id,
|
||||
ride_model_id: data.ride_model_id,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.eq('id', ride?.id);
|
||||
// Everyone goes through submission queue
|
||||
const { submitRideUpdate } = await import('@/lib/entitySubmissionHelpers');
|
||||
await submitRideUpdate(ride.id, data, user.id);
|
||||
|
||||
if (error) throw error;
|
||||
toast({
|
||||
title: "Edit Submitted",
|
||||
description: isModerator
|
||||
? "Your edit has been submitted. You can approve it in the moderation queue."
|
||||
: "Your ride edit has been submitted for review."
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "Ride Updated",
|
||||
description: "The ride has been updated successfully."
|
||||
});
|
||||
|
||||
setIsEditModalOpen(false);
|
||||
fetchRideData(); // Refresh the ride data
|
||||
} else {
|
||||
// Regular users submit for moderation
|
||||
const { error } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: user?.id,
|
||||
submission_type: 'ride_edit',
|
||||
content: {
|
||||
ride_id: ride?.id,
|
||||
...data
|
||||
},
|
||||
status: 'pending'
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast({
|
||||
title: "Edit Submitted",
|
||||
description: "Your edit has been submitted for review."
|
||||
});
|
||||
|
||||
setIsEditModalOpen(false);
|
||||
}
|
||||
setIsEditModalOpen(false);
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: "Error",
|
||||
|
||||
Reference in New Issue
Block a user