--liquibase formatted sql -- PANELS · core · RPC infrastructure (envelope helpers) --changeset panel:core-006a-rpc-generic-messages endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_generic_messages() RETURNS text[] LANGUAGE sql IMMUTABLE AS $$ SELECT ARRAY[ 'OK', 'Error', 'ERROR', 'Operación exitosa', 'Operacion exitosa', 'Algo salió mal', 'Algo salio mal', 'Error interno', 'Error interno del servidor' ]::text[]; $$; --changeset panel:core-006b-rpc-assert-message endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_assert_message(p_message text) RETURNS void LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN IF p_message IS NULL OR btrim(p_message) = '' THEN RAISE EXCEPTION 'RPC message must not be empty'; END IF; IF p_message = ANY (core.rpc_generic_messages()) THEN RAISE EXCEPTION 'RPC message is too generic: %', p_message; END IF; END; $$; --changeset panel:core-006c-rpc-ok endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_ok( p_data jsonb, p_message text, p_ctx jsonb DEFAULT '{}'::jsonb ) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN PERFORM core.rpc_assert_message(p_message); RETURN jsonb_build_object( 'ok', true, 'code', 'OK', 'layer', 'db', 'message', p_message, 'context', COALESCE(p_ctx, '{}'::jsonb), 'data', p_data, 'errors', NULL ); END; $$; --changeset panel:core-006d-rpc-created endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_created( p_data jsonb, p_message text, p_ctx jsonb DEFAULT '{}'::jsonb ) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN PERFORM core.rpc_assert_message(p_message); RETURN jsonb_build_object( 'ok', true, 'code', 'CREATED', 'layer', 'db', 'message', p_message, 'context', COALESCE(p_ctx, '{}'::jsonb), 'data', p_data, 'errors', NULL ); END; $$; --changeset panel:core-006e-rpc-err endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_err( p_code text, p_message text, p_ctx jsonb DEFAULT '{}'::jsonb, p_errors jsonb DEFAULT NULL ) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN PERFORM core.rpc_assert_message(p_message); RETURN jsonb_build_object( 'ok', false, 'code', p_code, 'layer', 'db', 'message', p_message, 'context', COALESCE(p_ctx, '{}'::jsonb), 'data', NULL, 'errors', p_errors ); END; $$; --changeset panel:core-006f-rpc-map-exception endDelimiter:; splitStatements:true CREATE OR REPLACE FUNCTION core.rpc_from_exception( p_fn text, p_sqlstate text, p_message text, p_detail text DEFAULT NULL ) RETURNS jsonb LANGUAGE plpgsql IMMUTABLE AS $$ DECLARE v_code text := 'INTERNAL'; v_msg text; v_ctx jsonb := jsonb_build_object('fn', p_fn, 'sqlstate', p_sqlstate); BEGIN IF p_sqlstate = '23505' THEN v_code := 'CONFLICT'; v_msg := format('CONFLICT en %s: %s', p_fn, COALESCE(p_detail, p_message)); ELSIF p_sqlstate = '23503' THEN v_code := 'VALIDATION'; v_msg := format('Referencia inválida en %s: %s', p_fn, COALESCE(p_detail, p_message)); ELSIF p_sqlstate = '23514' THEN v_code := 'VALIDATION'; v_msg := format('Restricción de valor en %s: %s', p_fn, COALESCE(p_detail, p_message)); ELSIF p_sqlstate = '40P01' THEN v_code := 'CONFLICT'; v_msg := format('Deadlock en %s: otra operación modificó los mismos registros, intente de nuevo', p_fn); ELSE v_code := 'INTERNAL'; v_msg := format('Error inesperado en %s (sqlstate=%s): %s', p_fn, p_sqlstate, p_message); END IF; RETURN core.rpc_err(v_code, v_msg, v_ctx); END; $$; --changeset panel:core-006g-rpc-grants endDelimiter:; splitStatements:true GRANT EXECUTE ON FUNCTION core.rpc_generic_messages() TO panels_core_app; GRANT EXECUTE ON FUNCTION core.rpc_assert_message(text) TO panels_core_app; GRANT EXECUTE ON FUNCTION core.rpc_ok(jsonb, text, jsonb) TO panels_core_app; GRANT EXECUTE ON FUNCTION core.rpc_created(jsonb, text, jsonb) TO panels_core_app; GRANT EXECUTE ON FUNCTION core.rpc_err(text, text, jsonb, jsonb) TO panels_core_app; GRANT EXECUTE ON FUNCTION core.rpc_from_exception(text, text, text, text) TO panels_core_app;