Files
agenciapsilmno/database-novo/manual/freemium_f3b_saas_owners_notify.supabase_admin.sql
T
Leonardo cb153165c3 freemium F3b: /saas/usuarios (donos) + notify_all_devs
- saas_list_account_owners(): 1 linha/tenant com dono (master), nome/slug/email/
  plano + selo "novo" (24h). Dev-only (is_saas_admin), cast text p/ varchar.
- notify_all_devs() insere em notifications_sistema p/ cada saas_admin
- trigger AFTER INSERT/UPDATE OF status em subscriptions avisa os devs com
  deeplink /saas/usuarios
- testado em ROLLBACK: lista + notify ao inserir subscription

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:01:27 -03:00

117 lines
4.5 KiB
PL/PgSQL

-- =============================================================================
-- Freemium F3b — /saas/usuarios (donos por tenant) + notificação aos devs
--
-- ⚠️ APLICAR COMO supabase_admin (lê auth.users.email + cria trigger em
-- public.subscriptions; notify_user_sistema é chamada por SECURITY DEFINER).
--
-- • saas_list_account_owners(): 1 linha por tenant com o DONO (master),
-- nome/slug/e-mail/plano + selo "novo" (24h). Dev-only (is_saas_admin).
-- • notify_all_devs(): insere em notifications_sistema p/ cada saas_admin.
-- • trigger em subscriptions: avisa os devs quando nasce/muda uma assinatura,
-- com deeplink pra /saas/usuarios.
-- =============================================================================
BEGIN;
-- 1) Donos por tenant (dev-only) ---------------------------------------------
CREATE OR REPLACE FUNCTION public.saas_list_account_owners()
RETURNS TABLE (
tenant_id uuid,
slug text,
tenant_name text,
kind text,
owner_id uuid,
owner_name text,
owner_email text,
plan_key text,
created_at timestamptz,
is_new boolean
)
LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
BEGIN
IF NOT public.is_saas_admin() THEN
RAISE EXCEPTION 'forbidden' USING ERRCODE = '42501';
END IF;
RETURN QUERY
SELECT t.id, t.slug::text, t.name::text, t.kind::text,
owner.user_id, pr.full_name::text, au.email::text,
COALESCE(vas.plan_key, ps.plan_key)::text,
t.created_at,
(t.created_at > now() - interval '24 hours')
FROM public.tenants t
LEFT JOIN LATERAL (
SELECT tm.user_id
FROM public.tenant_members tm
WHERE tm.tenant_id = t.id AND tm.role = 'tenant_admin' AND tm.status = 'active'
ORDER BY tm.created_at ASC
LIMIT 1
) owner ON true
LEFT JOIN public.profiles pr ON pr.id = owner.user_id
LEFT JOIN auth.users au ON au.id = owner.user_id
LEFT JOIN public.v_tenant_active_subscription vas ON vas.tenant_id = t.id
LEFT JOIN LATERAL (
SELECT s.plan_key FROM public.subscriptions s
WHERE s.user_id = owner.user_id AND s.status = 'active' AND s.tenant_id IS NULL
ORDER BY s.created_at DESC LIMIT 1
) ps ON true
ORDER BY t.created_at DESC;
END $$;
ALTER FUNCTION public.saas_list_account_owners() OWNER TO supabase_admin;
REVOKE ALL ON FUNCTION public.saas_list_account_owners() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.saas_list_account_owners() TO authenticated, service_role;
-- 2) notify_all_devs ----------------------------------------------------------
CREATE OR REPLACE FUNCTION public.notify_all_devs(
p_type text, p_payload jsonb, p_ref_id uuid DEFAULT NULL, p_ref_table text DEFAULT NULL
)
RETURNS int LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
DECLARE r record; n int := 0;
BEGIN
FOR r IN SELECT user_id FROM public.saas_admins LOOP
PERFORM public.notify_user_sistema(r.user_id, p_type, p_payload, NULL, p_ref_id, p_ref_table);
n := n + 1;
END LOOP;
RETURN n;
END $$;
ALTER FUNCTION public.notify_all_devs(text, jsonb, uuid, text) OWNER TO supabase_admin;
-- 3) trigger em subscriptions -------------------------------------------------
CREATE OR REPLACE FUNCTION public.trg_notify_devs_subscription()
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp'
AS $$
DECLARE v_slug text; v_title text;
BEGIN
-- só em INSERT ou quando o status muda
IF TG_OP = 'UPDATE' AND NEW.status IS NOT DISTINCT FROM OLD.status THEN
RETURN NEW;
END IF;
SELECT t.slug INTO v_slug FROM public.tenants t WHERE t.id = NEW.tenant_id;
v_title := CASE WHEN TG_OP = 'INSERT' THEN 'Nova assinatura' ELSE 'Assinatura atualizada' END;
PERFORM public.notify_all_devs(
'subscription_' || lower(TG_OP),
jsonb_build_object(
'title', v_title,
'detail', NEW.plan_key || ' · ' || NEW.status || COALESCE(' · ' || v_slug, ''),
'deeplink', '/saas/usuarios',
'plan_key', NEW.plan_key,
'status', NEW.status
),
NEW.id, 'subscriptions'
);
RETURN NEW;
END $$;
ALTER FUNCTION public.trg_notify_devs_subscription() OWNER TO supabase_admin;
DROP TRIGGER IF EXISTS trg_subscriptions_notify_devs ON public.subscriptions;
CREATE TRIGGER trg_subscriptions_notify_devs
AFTER INSERT OR UPDATE OF status ON public.subscriptions
FOR EACH ROW EXECUTE FUNCTION public.trg_notify_devs_subscription();
COMMIT;