Files
RDO/supabase_migration_convites.sql
2026-02-20 07:25:32 -03:00

265 lines
7.3 KiB
PL/PgSQL

-- =================================================================
-- MIGRAÇÃO: Sistema de Convites + Super Admin
-- Execute este SQL no Supabase Dashboard > SQL Editor
-- =================================================================
-- 1. Remover tabela de convites se existir (para recriar limpa)
DROP TABLE IF EXISTS public.convites CASCADE;
-- 2. Criar tabela de convites
CREATE TABLE public.convites (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
organizacao_id UUID NOT NULL REFERENCES public.organizacoes(id) ON DELETE CASCADE,
codigo TEXT NOT NULL UNIQUE,
criado_por UUID REFERENCES auth.users(id),
email_convidado TEXT,
role TEXT DEFAULT 'usuario',
max_usos INTEGER DEFAULT 1,
usos_atuais INTEGER DEFAULT 0,
ativo BOOLEAN DEFAULT true,
expira_em TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- 3. Índices para performance
CREATE INDEX idx_convites_codigo ON public.convites(codigo);
CREATE INDEX idx_convites_organizacao ON public.convites(organizacao_id);
CREATE INDEX idx_convites_ativo ON public.convites(ativo);
-- 4. Habilitar RLS
ALTER TABLE public.convites ENABLE ROW LEVEL SECURITY;
-- 5. Políticas RLS (com exceção para super admin)
CREATE POLICY "convites_select" ON public.convites
FOR SELECT USING (
auth.uid() IS NOT NULL
AND (
ativo = true
OR auth.jwt()->>'email' = 'admtracksteel@gmail.com'
)
);
CREATE POLICY "convites_insert" ON public.convites
FOR INSERT WITH CHECK (
auth.uid() IS NOT NULL
AND (
organizacao_id IN (
SELECT u.organizacao_id FROM public.usuarios u WHERE u.id = auth.uid()
)
OR auth.jwt()->>'email' = 'admtracksteel@gmail.com'
)
);
CREATE POLICY "convites_update" ON public.convites
FOR UPDATE USING (
auth.uid() IS NOT NULL
AND (
organizacao_id IN (
SELECT u.organizacao_id FROM public.usuarios u WHERE u.id = auth.uid()
)
OR auth.jwt()->>'email' = 'admtracksteel@gmail.com'
)
);
CREATE POLICY "convites_delete" ON public.convites
FOR DELETE USING (
auth.uid() IS NOT NULL
AND (
organizacao_id IN (
SELECT u.organizacao_id FROM public.usuarios u WHERE u.id = auth.uid()
)
OR auth.jwt()->>'email' = 'admtracksteel@gmail.com'
)
);
-- 6. Política para usuários atualizarem seu próprio organizacao_id
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_policies
WHERE tablename = 'usuarios'
AND policyname = 'usuarios_update_own_org'
) THEN
CREATE POLICY "usuarios_update_own_org" ON public.usuarios
FOR UPDATE USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
END IF;
END $$;
-- 7. Função para usar um convite
CREATE OR REPLACE FUNCTION public.usar_convite(
p_codigo TEXT,
p_usuario_id UUID
)
RETURNS JSON
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
v_convite RECORD;
v_usuario RECORD;
BEGIN
-- Buscar convite válido
SELECT * INTO v_convite
FROM public.convites
WHERE codigo = UPPER(TRIM(p_codigo))
AND ativo = true
AND (expira_em IS NULL OR expira_em > NOW())
AND (max_usos = 0 OR usos_atuais < max_usos);
IF NOT FOUND THEN
RETURN json_build_object(
'success', false,
'error', 'Código de convite inválido, expirado ou já utilizado.'
);
END IF;
-- Verificar se convite é para email específico
IF v_convite.email_convidado IS NOT NULL THEN
SELECT * INTO v_usuario FROM public.usuarios WHERE id = p_usuario_id;
IF v_usuario.email != v_convite.email_convidado THEN
RETURN json_build_object(
'success', false,
'error', 'Este convite é destinado a outro email.'
);
END IF;
END IF;
-- Verificar se usuário já tem organização
SELECT * INTO v_usuario FROM public.usuarios WHERE id = p_usuario_id;
IF v_usuario.organizacao_id IS NOT NULL THEN
RETURN json_build_object(
'success', false,
'error', 'Você já pertence a uma organização.'
);
END IF;
-- Associar usuário à organização
UPDATE public.usuarios
SET organizacao_id = v_convite.organizacao_id,
role = v_convite.role,
updated_at = NOW()
WHERE id = p_usuario_id;
-- Incrementar usos do convite
UPDATE public.convites
SET usos_atuais = usos_atuais + 1,
ativo = CASE
WHEN max_usos > 0 AND usos_atuais + 1 >= max_usos THEN false
ELSE ativo
END,
updated_at = NOW()
WHERE id = v_convite.id;
RETURN json_build_object(
'success', true,
'organizacao_id', v_convite.organizacao_id,
'organizacao_nome', (SELECT nome FROM public.organizacoes WHERE id = v_convite.organizacao_id),
'role', v_convite.role
);
END;
$$;
-- 8. Função para gerar código aleatório
CREATE OR REPLACE FUNCTION public.gerar_codigo_convite()
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
v_codigo TEXT;
v_exists BOOLEAN;
BEGIN
LOOP
v_codigo := UPPER(SUBSTRING(MD5(RANDOM()::TEXT || CLOCK_TIMESTAMP()::TEXT) FROM 1 FOR 8));
SELECT EXISTS(SELECT 1 FROM public.convites WHERE codigo = v_codigo) INTO v_exists;
EXIT WHEN NOT v_exists;
END LOOP;
RETURN v_codigo;
END;
$$;
-- =================================================================
-- 9. CRIAR ORGANIZAÇÃO ESPECIAL: TrackSteel Admin
-- =================================================================
INSERT INTO public.organizacoes (
nome,
slug,
razao_social,
status,
max_usuarios,
max_obras,
max_rdos_mes,
max_storage_mb
)
VALUES (
'TrackSteel Admin',
'tracksteel-admin',
'TrackSteel Desenvolvimento Ltda',
'ativa',
999,
999,
999999,
999999
)
ON CONFLICT DO NOTHING;
-- =================================================================
-- 10. CRIAR PRIMEIRA ORGANIZAÇÃO: Baldon Engemetal
-- =================================================================
INSERT INTO public.organizacoes (
nome,
slug,
razao_social,
status,
max_usuarios,
max_obras,
max_rdos_mes,
max_storage_mb
)
VALUES (
'Baldon Engemetal',
'baldon-engemetal',
'Baldon Engemetal Ltda',
'ativa',
50,
100,
1000,
5120
)
ON CONFLICT DO NOTHING;
-- 11. Gerar convite inicial para Baldon (admin, sem limite, sem expiração)
INSERT INTO public.convites (
organizacao_id,
codigo,
role,
max_usos,
ativo
)
SELECT
id,
'BALDON01',
'admin',
0,
true
FROM public.organizacoes
WHERE nome = 'Baldon Engemetal'
ON CONFLICT (codigo) DO NOTHING;
-- =================================================================
-- RESUMO:
--
-- SUPER ADMIN:
-- Email: admtracksteel@gmail.com
-- - Acesso automático à organização "TrackSteel Admin"
-- - Bypass de verificação de código de convite
-- - Pode criar convites para qualquer organização
-- - Acesso total ao sistema
--
-- PRIMEIRA ORGANIZAÇÃO:
-- Nome: Baldon Engemetal
-- Código de convite: BALDON01
-- Tipo: Admin, sem limite de usos, sem expiração
-- =================================================================