-- ============================================================================ -- 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;