-- Add comprehensive validation and error handling to create_submission_with_items -- This prevents silent failures and ensures proper transaction rollback CREATE OR REPLACE FUNCTION public.create_submission_with_items( p_user_id uuid, p_submission_type text, p_content jsonb, -- Keep for backward compatibility p_items jsonb[] ) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public' AS $$ DECLARE v_submission_id UUID; v_item JSONB; v_item_data JSONB; v_item_type TEXT; v_action_type TEXT; v_park_submission_id UUID; v_company_submission_id UUID; v_ride_submission_id UUID; v_ride_model_submission_id UUID; v_photo_submission_id UUID; BEGIN -- Create main submission INSERT INTO content_submissions (user_id, submission_type, status, approval_mode) VALUES (p_user_id, p_submission_type, 'pending', 'full') RETURNING id INTO v_submission_id; -- Validate items array IF array_length(p_items, 1) IS NULL OR array_length(p_items, 1) = 0 THEN RAISE EXCEPTION 'Cannot create submission without items'; END IF; -- Process each item FOREACH v_item IN ARRAY p_items LOOP v_item_type := (v_item->>'item_type')::TEXT; v_action_type := (v_item->>'action_type')::TEXT; v_item_data := v_item->'item_data'; -- Reset IDs for this iteration v_park_submission_id := (v_item->>'park_submission_id')::UUID; v_company_submission_id := (v_item->>'company_submission_id')::UUID; v_ride_submission_id := (v_item->>'ride_submission_id')::UUID; v_ride_model_submission_id := (v_item->>'ride_model_submission_id')::UUID; v_photo_submission_id := (v_item->>'photo_submission_id')::UUID; -- Create specialized submission if item_data exists (for new entities) IF v_item_data IS NOT NULL AND jsonb_typeof(v_item_data) = 'object' THEN IF v_item_type = 'park' THEN -- ✅ VALIDATE REQUIRED FIELDS BEFORE INSERT IF v_item_data->>'name' IS NULL THEN RAISE EXCEPTION 'Park submission missing required field: name'; END IF; IF v_item_data->>'slug' IS NULL THEN RAISE EXCEPTION 'Park submission missing required field: slug (name="%")', v_item_data->>'name'; END IF; IF v_item_data->>'park_type' IS NULL THEN RAISE EXCEPTION 'Park submission missing required field: park_type (name="%")', v_item_data->>'name'; END IF; IF v_item_data->>'status' IS NULL THEN RAISE EXCEPTION 'Park submission missing required field: status (name="%")', v_item_data->>'name'; END IF; INSERT INTO park_submissions ( submission_id, name, slug, description, park_type, status, opening_date, closing_date, opening_date_precision, closing_date_precision, website_url, phone, email, location_id, operator_id, property_owner_id, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( v_submission_id, v_item_data->>'name', v_item_data->>'slug', v_item_data->>'description', v_item_data->>'park_type', v_item_data->>'status', (v_item_data->>'opening_date')::DATE, (v_item_data->>'closing_date')::DATE, v_item_data->>'opening_date_precision', v_item_data->>'closing_date_precision', v_item_data->>'website_url', v_item_data->>'phone', v_item_data->>'email', (v_item_data->>'location_id')::UUID, (v_item_data->>'operator_id')::UUID, (v_item_data->>'property_owner_id')::UUID, v_item_data->>'banner_image_url', v_item_data->>'banner_image_id', v_item_data->>'card_image_url', v_item_data->>'card_image_id' ) RETURNING id INTO v_park_submission_id; ELSIF v_item_type IN ('operator', 'property_owner', 'manufacturer', 'designer') THEN -- ✅ VALIDATE REQUIRED FIELDS BEFORE INSERT IF v_item_data->>'name' IS NULL THEN RAISE EXCEPTION 'Company submission missing required field: name (type="%")', v_item_type; END IF; IF v_item_data->>'slug' IS NULL THEN RAISE EXCEPTION 'Company submission missing required field: slug (type="%", name="%")', v_item_type, v_item_data->>'name'; END IF; IF v_item_data->>'company_type' IS NULL THEN RAISE EXCEPTION 'Company submission missing required field: company_type (name="%")', v_item_data->>'name'; END IF; INSERT INTO company_submissions ( submission_id, name, slug, description, company_type, person_type, founded_year, founded_date, founded_date_precision, headquarters_location, website_url, logo_url, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( v_submission_id, v_item_data->>'name', v_item_data->>'slug', v_item_data->>'description', v_item_data->>'company_type', v_item_data->>'person_type', (v_item_data->>'founded_year')::INTEGER, (v_item_data->>'founded_date')::DATE, v_item_data->>'founded_date_precision', v_item_data->>'headquarters_location', v_item_data->>'website_url', v_item_data->>'logo_url', v_item_data->>'banner_image_url', v_item_data->>'banner_image_id', v_item_data->>'card_image_url', v_item_data->>'card_image_id' ) RETURNING id INTO v_company_submission_id; ELSIF v_item_type = 'ride' THEN -- ✅ VALIDATE REQUIRED FIELDS BEFORE INSERT IF v_item_data->>'name' IS NULL THEN RAISE EXCEPTION 'Ride submission missing required field: name'; END IF; IF v_item_data->>'slug' IS NULL THEN RAISE EXCEPTION 'Ride submission missing required field: slug (name="%")', v_item_data->>'name'; END IF; IF v_item_data->>'category' IS NULL THEN RAISE EXCEPTION 'Ride submission missing required field: category (name="%")', v_item_data->>'name'; END IF; IF v_item_data->>'status' IS NULL THEN RAISE EXCEPTION 'Ride submission missing required field: status (name="%")', v_item_data->>'name'; END IF; INSERT INTO ride_submissions ( submission_id, name, slug, description, category, status, park_id, manufacturer_id, designer_id, ride_model_id, opening_date, closing_date, opening_date_precision, closing_date_precision, height_requirement_cm, age_requirement, max_speed_kmh, duration_seconds, capacity_per_hour, gforce_max, inversions_count, length_meters, height_meters, drop_meters, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( v_submission_id, v_item_data->>'name', v_item_data->>'slug', v_item_data->>'description', v_item_data->>'category', v_item_data->>'status', (v_item_data->>'park_id')::UUID, (v_item_data->>'manufacturer_id')::UUID, (v_item_data->>'designer_id')::UUID, (v_item_data->>'ride_model_id')::UUID, (v_item_data->>'opening_date')::DATE, (v_item_data->>'closing_date')::DATE, v_item_data->>'opening_date_precision', v_item_data->>'closing_date_precision', (v_item_data->>'height_requirement_cm')::INTEGER, (v_item_data->>'age_requirement')::INTEGER, (v_item_data->>'max_speed_kmh')::NUMERIC, (v_item_data->>'duration_seconds')::INTEGER, (v_item_data->>'capacity_per_hour')::INTEGER, (v_item_data->>'gforce_max')::NUMERIC, (v_item_data->>'inversions_count')::INTEGER, (v_item_data->>'length_meters')::NUMERIC, (v_item_data->>'height_meters')::NUMERIC, (v_item_data->>'drop_meters')::NUMERIC, v_item_data->>'banner_image_url', v_item_data->>'banner_image_id', v_item_data->>'card_image_url', v_item_data->>'card_image_id' ) RETURNING id INTO v_ride_submission_id; END IF; END IF; -- Insert submission_item with proper foreign key linkage INSERT INTO submission_items ( submission_id, item_type, action_type, park_submission_id, company_submission_id, ride_submission_id, ride_model_submission_id, photo_submission_id, status, order_index, depends_on ) VALUES ( v_submission_id, v_item_type, v_action_type, v_park_submission_id, v_company_submission_id, v_ride_submission_id, v_ride_model_submission_id, v_photo_submission_id, 'pending', COALESCE((v_item->>'order_index')::INTEGER, 0), (v_item->>'depends_on')::UUID ); END LOOP; RETURN v_submission_id; EXCEPTION -- ✅ PROPER ERROR HANDLING: Log and re-raise to ensure full rollback WHEN OTHERS THEN RAISE NOTICE 'Submission creation failed for user % (type=%): %', p_user_id, p_submission_type, SQLERRM; -- Re-raise the exception to rollback the entire transaction RAISE; END; $$;