0956e4facc
Modulo 5 da Fase 1 + quick wins fechados. features/tenantship/ com 2 services + 2 composables (members + invites). MembersPage.vue nova em views/pages/admin/ + rota /admin/members em routes.clinic. Migration 20260520000005 cria RPC accept_tenant_invite (SECURITY DEFINER + lock FOR UPDATE) — tenantInvitesRepository.acceptInvite agora chama RPC real (nao mais stub). SaasTenantFeaturesPage refatorada pra usar novo tenantFeatureAdminService. SetupWizardPage 2648 linhas deferido pra sessao dedicada. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
3.4 KiB
PL/PgSQL
96 lines
3.4 KiB
PL/PgSQL
-- ============================================================================
|
|
-- RPC accept_tenant_invite — destrava o fluxo de aceitar convite
|
|
-- ----------------------------------------------------------------------------
|
|
-- Recebe o token UUID do invite. Em uma transação (SECURITY DEFINER):
|
|
-- 1. Lê invite ATIVO (não accepted, não revoked, não expired)
|
|
-- 2. INSERT em tenant_members com role do invite + user_id = auth.uid()
|
|
-- 3. UPDATE invite com accepted_at + accepted_by
|
|
--
|
|
-- Retorna jsonb { ok, tenant_id, role } em sucesso ou throw com mensagem PT-BR.
|
|
--
|
|
-- Chamada pelo features/tenantship/services/tenantInvitesRepository.acceptInvite().
|
|
-- Stub anterior tava jogando erro PT-BR explicando isso. Agora funciona.
|
|
-- ============================================================================
|
|
|
|
BEGIN;
|
|
|
|
CREATE OR REPLACE FUNCTION public.accept_tenant_invite(p_token uuid)
|
|
RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_uid uuid;
|
|
v_invite record;
|
|
v_existing_member record;
|
|
BEGIN
|
|
-- Quem está aceitando — auth.uid() pega do JWT
|
|
v_uid := auth.uid();
|
|
IF v_uid IS NULL THEN
|
|
RAISE EXCEPTION 'Sessão inválida (sem user autenticado).';
|
|
END IF;
|
|
|
|
-- 1. Lê invite ativo. Lock via FOR UPDATE pra evitar race.
|
|
SELECT id, tenant_id, email, role, accepted_at, revoked_at, expires_at
|
|
INTO v_invite
|
|
FROM public.tenant_invites
|
|
WHERE token = p_token
|
|
FOR UPDATE;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION 'Convite não encontrado. Verifique o link.';
|
|
END IF;
|
|
|
|
IF v_invite.revoked_at IS NOT NULL THEN
|
|
RAISE EXCEPTION 'Convite revogado pelo administrador.';
|
|
END IF;
|
|
|
|
IF v_invite.accepted_at IS NOT NULL THEN
|
|
RAISE EXCEPTION 'Convite já foi aceito anteriormente.';
|
|
END IF;
|
|
|
|
IF v_invite.expires_at IS NOT NULL AND v_invite.expires_at < now() THEN
|
|
RAISE EXCEPTION 'Convite expirado. Peça um novo ao administrador.';
|
|
END IF;
|
|
|
|
-- 2. Idempotência: se já é membro do tenant, só marca invite aceito.
|
|
SELECT id, role, status
|
|
INTO v_existing_member
|
|
FROM public.tenant_members
|
|
WHERE tenant_id = v_invite.tenant_id
|
|
AND user_id = v_uid
|
|
LIMIT 1;
|
|
|
|
IF v_existing_member.id IS NULL THEN
|
|
INSERT INTO public.tenant_members (tenant_id, user_id, role, status)
|
|
VALUES (v_invite.tenant_id, v_uid, v_invite.role, 'active');
|
|
ELSIF v_existing_member.status <> 'active' THEN
|
|
UPDATE public.tenant_members
|
|
SET status = 'active', role = v_invite.role
|
|
WHERE id = v_existing_member.id;
|
|
END IF;
|
|
-- (se já está ativo, deixa como tá — convite aceito não rebaixa)
|
|
|
|
-- 3. Marca invite como aceito
|
|
UPDATE public.tenant_invites
|
|
SET accepted_at = now(), accepted_by = v_uid
|
|
WHERE id = v_invite.id;
|
|
|
|
RETURN jsonb_build_object(
|
|
'ok', true,
|
|
'tenant_id', v_invite.tenant_id,
|
|
'role', v_invite.role
|
|
);
|
|
END;
|
|
$$;
|
|
|
|
COMMENT ON FUNCTION public.accept_tenant_invite(uuid) IS
|
|
'Aceita convite de membership. SECURITY DEFINER pra criar tenant_members em nome do user logado. Lock FOR UPDATE no invite previne race condition.';
|
|
|
|
-- Permite que qualquer authenticated chame (precisa do token UUID válido pra entrar).
|
|
REVOKE ALL ON FUNCTION public.accept_tenant_invite(uuid) FROM PUBLIC;
|
|
GRANT EXECUTE ON FUNCTION public.accept_tenant_invite(uuid) TO authenticated;
|
|
|
|
COMMIT;
|