-- ============================================================================ -- CRITICAL FIX: Add missing `category` field to ride and ride_model creation -- ============================================================================ -- Without this field, ALL ride and ride_model approvals fail with constraint violation -- Bug discovered during pipeline audit DO $$ DECLARE func_rec RECORD; BEGIN -- Drop all versions of create_entity_from_submission FOR func_rec IN SELECT oid::regprocedure::text as func_signature FROM pg_proc WHERE proname = 'create_entity_from_submission' AND pg_function_is_visible(oid) LOOP EXECUTE format('DROP FUNCTION IF EXISTS %s CASCADE', func_rec.func_signature); END LOOP; END $$; -- Recreate with category fields added CREATE FUNCTION create_entity_from_submission( p_entity_type TEXT, p_data JSONB, p_created_by UUID ) RETURNS UUID LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_entity_id UUID; v_fk_id UUID; v_location_id UUID; BEGIN CASE p_entity_type WHEN 'park' THEN -- Auto-create location if location data provided but no location_id IF p_data->>'location_id' IS NULL AND p_data->>'location_name' IS NOT NULL THEN INSERT INTO locations ( name, street_address, city, state_province, country, postal_code, latitude, longitude, timezone, display_name ) VALUES ( p_data->>'location_name', p_data->>'location_street_address', p_data->>'location_city', p_data->>'location_state_province', p_data->>'location_country', p_data->>'location_postal_code', (p_data->>'location_latitude')::NUMERIC, (p_data->>'location_longitude')::NUMERIC, p_data->>'location_timezone', p_data->>'location_display_name' ) RETURNING id INTO v_location_id; p_data := p_data || jsonb_build_object('location_id', v_location_id); RAISE NOTICE 'Created new location % for park', v_location_id; END IF; -- Validate foreign keys IF p_data->>'location_id' IS NOT NULL THEN v_fk_id := (p_data->>'location_id')::UUID; IF NOT EXISTS (SELECT 1 FROM locations WHERE id = v_fk_id) THEN RAISE EXCEPTION 'Invalid location_id: Location does not exist' USING ERRCODE = '23503', HINT = 'location_id'; END IF; END IF; IF p_data->>'operator_id' IS NOT NULL THEN v_fk_id := (p_data->>'operator_id')::UUID; IF NOT EXISTS (SELECT 1 FROM companies WHERE id = v_fk_id AND company_type = 'operator') THEN RAISE EXCEPTION 'Invalid operator_id: Company does not exist or is not an operator' USING ERRCODE = '23503', HINT = 'operator_id'; END IF; END IF; IF p_data->>'property_owner_id' IS NOT NULL THEN v_fk_id := (p_data->>'property_owner_id')::UUID; IF NOT EXISTS (SELECT 1 FROM companies WHERE id = v_fk_id AND company_type = 'property_owner') THEN RAISE EXCEPTION 'Invalid property_owner_id: Company does not exist or is not a property owner' USING ERRCODE = '23503', HINT = 'property_owner_id'; END IF; END IF; INSERT INTO parks ( name, slug, description, park_type, status, location_id, operator_id, property_owner_id, opening_date, closing_date, opening_date_precision, closing_date_precision, website_url, phone, email, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( p_data->>'name', p_data->>'slug', p_data->>'description', p_data->>'park_type', p_data->>'status', (p_data->>'location_id')::UUID, (p_data->>'operator_id')::UUID, (p_data->>'property_owner_id')::UUID, (p_data->>'opening_date')::DATE, (p_data->>'closing_date')::DATE, p_data->>'opening_date_precision', p_data->>'closing_date_precision', p_data->>'website_url', p_data->>'phone', p_data->>'email', p_data->>'banner_image_url', p_data->>'banner_image_id', p_data->>'card_image_url', p_data->>'card_image_id' ) RETURNING id INTO v_entity_id; WHEN 'ride' THEN -- Validate park_id (required) v_fk_id := (p_data->>'park_id')::UUID; IF v_fk_id IS NULL THEN RAISE EXCEPTION 'park_id is required for ride creation' USING ERRCODE = '23502', HINT = 'park_id'; END IF; IF NOT EXISTS (SELECT 1 FROM parks WHERE id = v_fk_id) THEN RAISE EXCEPTION 'Invalid park_id: Park does not exist' USING ERRCODE = '23503', HINT = 'park_id'; END IF; IF p_data->>'manufacturer_id' IS NOT NULL THEN v_fk_id := (p_data->>'manufacturer_id')::UUID; IF NOT EXISTS (SELECT 1 FROM companies WHERE id = v_fk_id AND company_type = 'manufacturer') THEN RAISE EXCEPTION 'Invalid manufacturer_id: Company does not exist or is not a manufacturer' USING ERRCODE = '23503', HINT = 'manufacturer_id'; END IF; END IF; IF p_data->>'ride_model_id' IS NOT NULL THEN v_fk_id := (p_data->>'ride_model_id')::UUID; IF NOT EXISTS (SELECT 1 FROM ride_models WHERE id = v_fk_id) THEN RAISE EXCEPTION 'Invalid ride_model_id: Ride model does not exist' USING ERRCODE = '23503', HINT = 'ride_model_id'; END IF; END IF; -- ✅ FIX #1: Add category to ride creation INSERT INTO rides ( name, slug, park_id, category, ride_type, status, manufacturer_id, ride_model_id, opening_date, closing_date, opening_date_precision, closing_date_precision, description, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( p_data->>'name', p_data->>'slug', (p_data->>'park_id')::UUID, p_data->>'category', p_data->>'ride_type', p_data->>'status', (p_data->>'manufacturer_id')::UUID, (p_data->>'ride_model_id')::UUID, (p_data->>'opening_date')::DATE, (p_data->>'closing_date')::DATE, p_data->>'opening_date_precision', p_data->>'closing_date_precision', p_data->>'description', p_data->>'banner_image_url', p_data->>'banner_image_id', p_data->>'card_image_url', p_data->>'card_image_id' ) RETURNING id INTO v_entity_id; WHEN 'manufacturer', 'operator', 'property_owner', 'designer' THEN INSERT INTO companies ( name, slug, company_type, description, website_url, founded_year, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( p_data->>'name', p_data->>'slug', p_entity_type, p_data->>'description', p_data->>'website_url', (p_data->>'founded_year')::INTEGER, p_data->>'banner_image_url', p_data->>'banner_image_id', p_data->>'card_image_url', p_data->>'card_image_id' ) RETURNING id INTO v_entity_id; WHEN 'ride_model' THEN -- Validate manufacturer_id (required) v_fk_id := (p_data->>'manufacturer_id')::UUID; IF v_fk_id IS NULL THEN RAISE EXCEPTION 'manufacturer_id is required for ride model creation' USING ERRCODE = '23502', HINT = 'manufacturer_id'; END IF; IF NOT EXISTS (SELECT 1 FROM companies WHERE id = v_fk_id AND company_type = 'manufacturer') THEN RAISE EXCEPTION 'Invalid manufacturer_id: Company does not exist or is not a manufacturer' USING ERRCODE = '23503', HINT = 'manufacturer_id'; END IF; -- ✅ FIX #2: Add category to ride_model creation INSERT INTO ride_models ( name, slug, manufacturer_id, category, ride_type, description, banner_image_url, banner_image_id, card_image_url, card_image_id ) VALUES ( p_data->>'name', p_data->>'slug', (p_data->>'manufacturer_id')::UUID, p_data->>'category', p_data->>'ride_type', p_data->>'description', p_data->>'banner_image_url', p_data->>'banner_image_id', p_data->>'card_image_url', p_data->>'card_image_id' ) RETURNING id INTO v_entity_id; WHEN 'timeline_event', 'milestone' THEN v_fk_id := (p_data->>'entity_id')::UUID; IF v_fk_id IS NULL THEN RAISE EXCEPTION 'entity_id is required for timeline event creation' USING ERRCODE = '23502', HINT = 'entity_id'; END IF; INSERT INTO entity_timeline_events ( entity_id, entity_type, event_type, event_date, event_date_precision, title, description, from_value, to_value, from_entity_id, to_entity_id, from_location_id, to_location_id, created_by, approved_by ) VALUES ( (p_data->>'entity_id')::UUID, p_data->>'entity_type', p_data->>'event_type', (p_data->>'event_date')::DATE, p_data->>'event_date_precision', p_data->>'title', p_data->>'description', p_data->>'from_value', p_data->>'to_value', (p_data->>'from_entity_id')::UUID, (p_data->>'to_entity_id')::UUID, (p_data->>'from_location_id')::UUID, (p_data->>'to_location_id')::UUID, p_created_by, current_setting('app.moderator_id', true)::UUID ) RETURNING id INTO v_entity_id; ELSE RAISE EXCEPTION 'Unsupported entity type for creation: %', p_entity_type USING ERRCODE = '22023'; END CASE; RETURN v_entity_id; END; $$; -- Grant execute permissions GRANT EXECUTE ON FUNCTION create_entity_from_submission TO authenticated; COMMENT ON FUNCTION create_entity_from_submission IS 'Creates entities with category field support for rides and ride_models, plus automatic location creation and timeline event support';