Substitutes the real admin UID everywhere the placeholder appeared, points the Edge Functions base URL at the deployed project, and adds production JWT verification: trigger functions now send the anon key as a bearer token so verify_jwt can stay enabled instead of leaving the functions open to unauthenticated calls from the public internet. Also adds scripts/create-admin.ts (one-off helper used to create Mira's admin auth user), documents the seed script's non-VITE env vars in .env.example, and closes a gitignore gap where plain .env wasn't excluded alongside .env.local. Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
128 lines
5.6 KiB
PL/PgSQL
128 lines
5.6 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.
|
|
|
|
-- Single source of truth for the Edge Functions base URL, used by both
|
|
-- notify_booking_created() and notify_inquiry_created() below. Task 9
|
|
-- (production deploy) MUST update this function's return value to the real
|
|
-- deployed Edge Functions URL (e.g. https://<project-ref>.supabase.co/functions/v1)
|
|
-- when promoting to production. Because both trigger functions call through
|
|
-- this single function rather than each holding their own copy of the
|
|
-- literal, updating this one definition (and re-running the migration or an
|
|
-- equivalent `create or replace function` in production) is sufficient to
|
|
-- repoint both notification paths — there is no second copy to remember to
|
|
-- edit.
|
|
create or replace function public.edge_functions_base_url()
|
|
returns text
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select 'https://ylxppnclfpnnolnjkbwv.supabase.co/functions/v1'
|
|
$$;
|
|
|
|
-- The Edge Functions gateway enforces JWT verification (verify_jwt = true,
|
|
-- see supabase/config.toml) in production, since these functions are
|
|
-- reachable over the public internet and would otherwise let anyone trigger
|
|
-- an outbound email send by POSTing directly to the URL. pg_net's request
|
|
-- carries no Authorization header by default, so the trigger must supply
|
|
-- one. The anon key is not a secret (it already ships in the frontend
|
|
-- bundle), so embedding it here as a literal, single-source-of-truth
|
|
-- constant is safe — the same pattern as edge_functions_base_url() above.
|
|
create or replace function public.edge_functions_anon_key()
|
|
returns text
|
|
language sql
|
|
immutable
|
|
as $$
|
|
select 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlseHBwbmNsZnBubm9sbmprYnd2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODY0NTkyNzYsImV4cCI6MjEwMjAzNTI3Nn0.j0UaxlBvDctsDOXxPuWj43XvbObtX68-mjNf3AAefX8'
|
|
$$;
|
|
|
|
create or replace function public.notify_booking_created()
|
|
returns trigger
|
|
language plpgsql
|
|
security definer
|
|
set search_path = ''
|
|
as $$
|
|
begin
|
|
perform net.http_post(
|
|
url := public.edge_functions_base_url() || '/on-booking-created',
|
|
headers := jsonb_build_object(
|
|
'Content-Type', 'application/json',
|
|
'Authorization', 'Bearer ' || public.edge_functions_anon_key()
|
|
),
|
|
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 $$
|
|
begin
|
|
perform net.http_post(
|
|
url := public.edge_functions_base_url() || '/on-inquiry-created',
|
|
headers := jsonb_build_object(
|
|
'Content-Type', 'application/json',
|
|
'Authorization', 'Bearer ' || public.edge_functions_anon_key()
|
|
),
|
|
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();
|