import { supabase } from '@/integrations/supabase/client'; import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader'; import { uploadPendingImages } from './imageUploadHelper'; /** * ═══════════════════════════════════════════════════════════════════ * SUBMISSION PATTERN STANDARD - CRITICAL PROJECT RULE * ═══════════════════════════════════════════════════════════════════ * * ⚠️ NEVER STORE JSON IN SQL COLUMNS ⚠️ * * content_submissions.content should ONLY contain: * ✅ action: 'create' | 'edit' | 'delete' * ✅ Minimal reference IDs (entity_id, parent_id, etc.) - MAX 3 fields * ❌ NO actual form data * ❌ NO submission content * ❌ NO large objects * * ALL actual data MUST go in: * ✅ submission_items.item_data (new data) * ✅ submission_items.original_data (for edits) * ✅ Specialized relational tables: * - photo_submissions + photo_submission_items * - park_submissions * - ride_submissions * - company_submissions * - ride_model_submissions * * If your data is relational, model it relationally. * JSON blobs destroy: * - Queryability (can't filter/join) * - Performance (slower, larger) * - Data integrity (no constraints) * - Maintainability (impossible to refactor) * * EXAMPLES: * * ✅ CORRECT: * content: { action: 'create' } * content: { action: 'edit', park_id: uuid } * content: { action: 'delete', photo_id: uuid } * * ❌ WRONG: * content: { name: '...', description: '...', ...formData } * content: { photos: [...], metadata: {...} } * content: data // entire object dump * * ═══════════════════════════════════════════════════════════════════ */ export interface ParkFormData { name: string; slug: string; description?: string; park_type: string; status: string; opening_date?: string; closing_date?: string; website_url?: string; phone?: string; email?: string; operator_id?: string; property_owner_id?: string; // Location can be stored as object for new submissions or ID for editing location?: { name: string; city?: string; state_province?: string; country: string; postal_code?: string; latitude: number; longitude: number; timezone?: string; display_name: string; }; location_id?: string; images?: ImageAssignments; banner_image_url?: string; banner_image_id?: string; card_image_url?: string; card_image_id?: string; } export interface RideFormData { name: string; slug: string; description?: string; category: string; status: string; park_id: string; manufacturer_id?: string; designer_id?: string; ride_model_id?: string; opening_date?: string; closing_date?: string; max_speed_kmh?: number; max_height_meters?: number; length_meters?: number; duration_seconds?: number; capacity_per_hour?: number; height_requirement?: number; age_requirement?: number; inversions?: number; drop_height_meters?: number; max_g_force?: number; intensity_level?: string; coaster_type?: string; seating_type?: string; ride_sub_type?: string; images?: ImageAssignments; banner_image_url?: string; banner_image_id?: string; card_image_url?: string; card_image_id?: string; } export async function submitParkCreation( data: ParkFormData, userId: string ) { // Upload any pending local images first let processedImages = data.images; if (data.images?.uploaded && data.images.uploaded.length > 0) { const uploadedImages = await uploadPendingImages(data.images.uploaded); processedImages = { ...data.images, uploaded: uploadedImages }; } // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ user_id: userId, submission_type: 'park', content: { action: 'create' }, status: 'pending' }) .select() .single(); if (submissionError) throw submissionError; // Create the submission item with actual park data const { error: itemError } = await supabase .from('submission_items') .insert({ submission_id: submissionData.id, item_type: 'park', item_data: { ...data, images: processedImages as any }, status: 'pending', order_index: 0 }); if (itemError) throw itemError; return { submitted: true, submissionId: submissionData.id }; } export async function submitParkUpdate( parkId: string, data: ParkFormData, userId: string ) { // Fetch existing park data first const { data: existingPark, error: fetchError } = await supabase .from('parks') .select('*') .eq('id', parkId) .single(); if (fetchError) throw new Error(`Failed to fetch park: ${fetchError.message}`); if (!existingPark) throw new Error('Park not found'); // Upload any pending local images first let processedImages = data.images; if (data.images?.uploaded && data.images.uploaded.length > 0) { const uploadedImages = await uploadPendingImages(data.images.uploaded); processedImages = { ...data.images, uploaded: uploadedImages }; } // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ user_id: userId, submission_type: 'park', content: { action: 'edit', park_id: parkId }, status: 'pending' }) .select() .single(); if (submissionError) throw submissionError; // Create the submission item with actual park data AND original data const { error: itemError } = await supabase .from('submission_items') .insert({ submission_id: submissionData.id, item_type: 'park', item_data: { ...data, park_id: parkId, images: processedImages as any }, original_data: JSON.parse(JSON.stringify(existingPark)), status: 'pending', order_index: 0 }); if (itemError) throw itemError; return { submitted: true, submissionId: submissionData.id }; } export async function submitRideCreation( data: RideFormData, userId: string ) { // Upload any pending local images first let processedImages = data.images; if (data.images?.uploaded && data.images.uploaded.length > 0) { const uploadedImages = await uploadPendingImages(data.images.uploaded); processedImages = { ...data.images, uploaded: uploadedImages }; } // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ user_id: userId, submission_type: 'ride', content: { action: 'create' }, status: 'pending' }) .select() .single(); if (submissionError) throw submissionError; // Create the submission item with actual ride data const { error: itemError } = await supabase .from('submission_items') .insert({ submission_id: submissionData.id, item_type: 'ride', item_data: { ...data, images: processedImages as any }, status: 'pending', order_index: 0 }); if (itemError) throw itemError; return { submitted: true, submissionId: submissionData.id }; } export async function submitRideUpdate( rideId: string, data: RideFormData, userId: string ) { // Fetch existing ride data first const { data: existingRide, error: fetchError } = await supabase .from('rides') .select('*') .eq('id', rideId) .single(); if (fetchError) throw new Error(`Failed to fetch ride: ${fetchError.message}`); if (!existingRide) throw new Error('Ride not found'); // Upload any pending local images first let processedImages = data.images; if (data.images?.uploaded && data.images.uploaded.length > 0) { const uploadedImages = await uploadPendingImages(data.images.uploaded); processedImages = { ...data.images, uploaded: uploadedImages }; } // Create the main submission record const { data: submissionData, error: submissionError } = await supabase .from('content_submissions') .insert({ user_id: userId, submission_type: 'ride', content: { action: 'edit', ride_id: rideId }, status: 'pending' }) .select() .single(); if (submissionError) throw submissionError; // Create the submission item with actual ride data AND original data const { error: itemError } = await supabase .from('submission_items') .insert({ submission_id: submissionData.id, item_type: 'ride', item_data: { ...data, ride_id: rideId, images: processedImages as any }, original_data: JSON.parse(JSON.stringify(existingRide)), status: 'pending', order_index: 0 }); if (itemError) throw itemError; return { submitted: true, submissionId: submissionData.id }; }