Files
braidsbymira/supabase/migrations/20260808000003_notify_triggers.sql
belviskhoremk 7e51c2df37 feat: add on-booking-created and on-inquiry-created Edge Functions with DB-trigger wiring
Wires pg_net-based database triggers on bookings/inquiries to invoke the two
Edge Functions on insert. Two empirically-discovered fixes over the brief's
literal SQL were required to make this work locally:

- ALTER DATABASE ... SET for a custom GUC needs superuser, but the local
  migration role (postgres) isn't superuser in this Supabase CLI stack (only
  supabase_admin is) - inlined the base URL as a literal constant in each
  trigger function instead.
- pg_net's http_post lives in schema `net`, not `extensions.net` - fixed the
  function calls accordingly.

Also disabled verify_jwt for both functions in config.toml since they're
invoked by pg_net (no Authorization header), not by end-user clients.

Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
2026-08-08 13:25:04 +00:00

92 lines
3.9 KiB
PL/PgSQL

create extension if not exists pg_net with schema extensions;
-- The Edge Functions base URL is environment-specific: for local development
-- it points at the local functions runtime; Task 9 updates it to the real
-- deployed URL in production.
--
-- NOTE ON IMPLEMENTATION: the brief originally called for storing this as a
-- database-level GUC via `alter database postgres set
-- app.settings.edge_function_base_url = '...'`, read back in each trigger
-- function via current_setting(). That statement fails locally with
-- `permission denied to set parameter "app.settings.edge_function_base_url"`
-- (SQLSTATE 42501): in this Supabase CLI version's local Postgres stack, the
-- `postgres` role that runs migrations is deliberately NOT a superuser
-- (Create role/Create DB/Replication/Bypass RLS only) — only `supabase_admin`
-- is superuser, mirroring the hosted permission model. Setting an arbitrary
-- custom GUC at the database level requires either superuser or a `GRANT SET
-- ON PARAMETER` from a superuser, and the migration has no way to grant
-- itself that privilege. Empirically verified via `docker exec
-- supabase_db_<project> psql -U postgres -d postgres -c "alter database
-- postgres set app.settings.edge_function_base_url = '...'"` (denied) vs the
-- same command as `-U supabase_admin` (succeeds).
--
-- Rather than depend on a migration-time GRANT (fragile, and would still
-- need to run as supabase_admin some other way), the base URL is inlined as
-- a literal constant in each trigger function below. Task 9 (or any future
-- environment change) updates it by editing the constant here and
-- re-migrating — the same operational shape the GUC approach would have had.
--
-- Default here is the local Supabase CLI functions gateway address,
-- confirmed to resolve from inside the Postgres container in Step 5 below
-- (curl from supabase_db_<project> to http://host.docker.internal:54321
-- reached the edge runtime and got a real HTTP response, not a connection
-- failure).
--
-- SECOND CORRECTION: the brief's SQL called `extensions.net.http_post(...)`
-- (treating `net` as a schema nested under `extensions`). Empirically, even
-- though `pg_extension.extnamespace` for pg_net reports `extensions` (from
-- `with schema extensions` above), pg_net's install script hardcodes its own
-- `net` schema for the actual functions (confirmed via `select n.nspname,
-- p.proname from pg_proc p join pg_namespace n on p.pronamespace = n.oid
-- where p.proname = 'http_post'` -> `net.http_post`). Calling
-- `extensions.net.http_post` therefore fails with "cross-database
-- references are not implemented: extensions.net.http_post" (Postgres reads
-- `extensions.net` as database.schema). Fixed to call `net.http_post`
-- directly below.
create or replace function public.notify_booking_created()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
declare
base_url text := 'http://host.docker.internal:54321/functions/v1';
begin
perform net.http_post(
url := base_url || '/on-booking-created',
headers := jsonb_build_object('Content-Type', 'application/json'),
body := jsonb_build_object('record', to_jsonb(new))
);
return new;
end;
$$;
create trigger booking_created_notify
after insert on public.bookings
for each row
execute function public.notify_booking_created();
create or replace function public.notify_inquiry_created()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
declare
base_url text := 'http://host.docker.internal:54321/functions/v1';
begin
perform net.http_post(
url := base_url || '/on-inquiry-created',
headers := jsonb_build_object('Content-Type', 'application/json'),
body := jsonb_build_object('record', to_jsonb(new))
);
return new;
end;
$$;
create trigger inquiry_created_notify
after insert on public.inquiries
for each row
execute function public.notify_inquiry_created();