Grupos hierárquicos de 6 níveis: a funcionalidade mais complexa
Pontos-chave: Construir um sistema de grupos hierárquicos com 6 níveis de profundidade, 4 papéis, consultas recursivas PostgreSQL (CTE) e 31 políticas RLS é a funcionalidade mais complexa do TAMSIV. Este artigo aborda o modelo de dados, a herança de permissões, o componente frontend HierarchicalGroupPicker e as lições aprendidas.
Existem funcionalidades que parecem simples no papel. "Adicionar grupos" — parece inocente. Como adicionar um carrinho em um aplicativo de e-commerce. Então você começa a investigar e percebe que acabou de abrir a caixa de Pandora.
TAMSIV suporta grupos hierárquicos com até 6 níveis de profundidade. Meu clube de mergulho foi o caso de uso perfeito: Clube → Comissão Técnica → Nível 1 → Grupo de terça-feira. Uma família usa: Família → Casa → Cozinha / Jardim / Garagem. Uma PME: Empresa → Departamento → Equipe → Projeto.
Esta é a funcionalidade que me levou mais tempo. É também a que torna o TAMSIV utilizável para organizações reais, não apenas para uso individual.
Como modelar grupos hierárquicos no PostgreSQL?
O modelo de dados baseia-se em um conceito simples: cada grupo tem um parent_id que aponta para seu grupo pai. Um grupo raiz tem parent_id = NULL.
-- Tabela simplificada
CREATE TABLE collaborative.groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
parent_id UUID REFERENCES collaborative.groups(id),
created_by UUID REFERENCES auth.users(id),
depth INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now()
);
Para exibir a árvore completa de um grupo, uso consultas recursivas (Common Table Expressions, ou CTE) no PostgreSQL:
WITH RECURSIVE group_tree AS (
-- Base: o grupo raiz
SELECT id, name, parent_id, 0 AS depth
FROM collaborative.groups
WHERE id = p_group_id
UNION ALL
-- Recursão: os subgrupos
SELECT g.id, g.name, g.parent_id, gt.depth + 1
FROM collaborative.groups g
JOIN group_tree gt ON g.parent_id = gt.id
WHERE gt.depth < 6 -- Salvaguarda
)
SELECT * FROM group_tree;
A cláusula WHERE depth < 6 é uma salvaguarda crítica. Sem ela, um erro de dados (um ciclo de referências, por exemplo) faria a consulta entrar em loop infinito. Seis níveis cobrem todos os casos de uso reais que encontrei.
Este modelo de dados reside no esquema collaborative, separado do restante — explico o porquê no artigo sobre a estruturação do banco de dados.
Quais são os quatro papéis e como eles funcionam?
Cada membro de um grupo tem um papel que determina suas permissões:
- Admin — Fazer tudo: criar, modificar, excluir, gerenciar membros, convidar, modificar as configurações do grupo. É o criador do grupo por padrão.
- Manager — Gerenciar conteúdo: criar/modificar/excluir tarefas e memorandos, atribuir membros, validar checklists. Mas não gerenciar membros ou configurações.
- Member — Contribuir: criar conteúdo, ver todo o conteúdo do grupo, validar seus próprios itens de checklist.
- Viewer — Somente leitura: ver o conteúdo, nada mais. Útil para observadores ou partes interessadas que desejam acompanhar o progresso sem intervir.
A armadilha da herança de permissões
Se você é Admin do grupo "Clube de mergulho", você é automaticamente Admin de "Comissão Técnica" (um subgrupo)? No TAMSIV, a resposta é sim — mas com nuances.
A herança funciona para baixo: um Admin de um grupo pai tem os mesmos direitos em todos os subgrupos. Mas um Admin de um subgrupo não tem direitos no grupo pai. Essa assimetria é natural (o diretor vê tudo, o chefe de equipe vê sua equipe), mas adiciona complexidade às consultas SQL.
Para verificar as permissões, cada consulta deve subir a árvore hierárquica até encontrar um papel ou atingir a raiz:
-- O usuário tem um papel neste grupo ou em um pai?
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups WHERE id = p_group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT role FROM collaborative.group_members
WHERE group_id IN (SELECT id FROM parent_chain)
AND user_id = auth.uid()
ORDER BY role ASC -- Admin > Manager > Member > Viewer
LIMIT 1;
Como escrever e testar 31 políticas RLS?
Este é o número que dói: 31 políticas de Row Level Security para o esquema collaborative. Supabase usa RLS para proteger o acesso aos dados — cada tabela tem regras que determinam quem pode ler, escrever, modificar, excluir.
Aqui estão exemplos concretos:
Política de leitura simples
-- Um membro pode ver as tarefas de seus grupos
CREATE POLICY "members_read_tasks" ON collaborative.group_tasks
FOR SELECT USING (
EXISTS (
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id = group_tasks.group_id
AND gm.user_id = auth.uid()
)
);
Política com herança hierárquica
-- Um admin pode excluir as tarefas de seus grupos E subgrupos
CREATE POLICY "admin_delete_tasks" ON collaborative.group_tasks
FOR DELETE USING (
EXISTS (
WITH RECURSIVE parent_chain AS (
SELECT id, parent_id FROM collaborative.groups
WHERE id = group_tasks.group_id
UNION ALL
SELECT g.id, g.parent_id
FROM collaborative.groups g
JOIN parent_chain pc ON g.id = pc.parent_id
)
SELECT 1 FROM collaborative.group_members gm
WHERE gm.group_id IN (SELECT id FROM parent_chain)
AND gm.user_id = auth.uid()
AND gm.role = 'admin'
)
);
Testar 31 políticas significa no mínimo 31 cenários de teste. Na realidade, é muito mais, porque cada política deve ser testada positivamente (o acesso é concedido) E negativamente (o acesso é negado). Metódico, chato, indispensável. Os detalhes sobre a abordagem de teste estão no artigo sobre auditoria de segurança.
Como construir o componente HierarchicalGroupPicker?
No frontend, exibir uma árvore de grupos com indentação, ícones de papel e interações é um desafio de UI por si só.
O HierarchicalGroupPicker é um componente React Native que:
- Exibe a árvore com indentação proporcional à profundidade (usando
spacing()do sistema de escalonamento). - Indica o papel do usuário em cada grupo através de um ícone colorido.
- Permite a seleção com um toggle "incluir subgrupos" — quando você seleciona um grupo pai, os subgrupos podem ser incluídos automaticamente.
- Suporta dobrar/desdobrar: os subgrupos podem ser recolhidos para não sobrecarregar a tela em hierarquias profundas.
Este componente é reutilizado em todos os lugares: no filtro da agenda, na criação de tarefas, na tela de gerenciamento de grupos. É um investimento de tempo enorme no início, mas uma economia de tempo colossal depois.
O padrão FilterBar
O HierarchicalGroupPicker funciona com a FilterBar, um componente de filtragem com 3 contextos (Privado / Compartilhado / Tudo) e modos (todas as tarefas / criadas por mim / atribuídas a mim). A combinação dos dois permite consultas muito precisas: "mostre-me as tarefas atribuídas a mim no grupo Família e seus subgrupos".
Quais são os casos de uso reais dos grupos hierárquicos?
Aqui estão os casos de uso que meus testadores usam ativamente:
- Família: Família → Casa (tarefas domésticas) / Férias (planejamento) / Escola (deveres dos filhos). Falo sobre isso no artigo sobre organização familiar.
- Clube esportivo: Clube → Comissões → Níveis → Grupos por horário.
- Pequena equipe: Empresa → Departamento → Projeto → Sprint.
- Associação: Associação → Polo → Evento.
O limite de 6 níveis raramente é atingido. Na prática, 3-4 níveis cobrem 95% das necessidades. Mas essa profundidade máxima é uma rede de segurança — é melhor tê-la e não precisar dela.
Que lições tirar desta implementação?
- As consultas recursivas PostgreSQL são poderosas, mas custosas: Cada consulta que sobe a hierarquia faz N junções (onde N = a profundidade). Um índice em
parent_idé indispensável. - As políticas RLS se acumulam rapidamente: Cada tabela × cada operação (SELECT, INSERT, UPDATE, DELETE) × cada papel = muitas políticas. Documente-as em um arquivo dedicado.
- A herança de permissões é o ponto mais delicado: Decida cedo se as permissões se propagam para baixo, para cima ou para ambos. Mudar essa decisão mais tarde é extremamente custoso.
- O componente frontend é um investimento: Um bom HierarchicalGroupPicker leva uma semana para ser construído corretamente, mas é reutilizável em todos os lugares.
- Teste com dados reais: Uma árvore de 3 grupos em desenvolvimento não revela os mesmos bugs que uma árvore de 15 grupos em 4 níveis em produção.
Se você está construindo um sistema colaborativo, a hierarquia é o que transforma uma simples "lista de grupos" em uma ferramenta que as organizações realmente adotam. É difícil, é demorado, mas é o que faz a diferença em relação aos concorrentes que se limitam a grupos planos.
FAQ
Por que limitar a 6 níveis de profundidade?
Seis níveis cobrem todos os casos de uso reais que encontrei. Além disso, a árvore se torna incontrolável para o usuário. O limite também protege contra consultas recursivas muito profundas que poderiam afetar o desempenho. Na prática, 3-4 níveis são suficientes para 95% das organizações.
As consultas recursivas causam problemas de desempenho?
Com um índice em parent_id e um limite de profundidade, o desempenho é excelente mesmo com centenas de grupos. Uma consulta recursiva em 6 níveis leva menos de 10ms no Supabase PostgreSQL. O verdadeiro risco é um ciclo de referências (grupo A pai de B, B pai de A) — a cláusula WHERE depth < 6 protege contra isso.
Como gerenciar o convite de novos membros?
O administrador de um grupo pode convidar por e-mail ou por link de compartilhamento. O convidado se junta ao grupo com o papel escolhido pelo administrador. Se o grupo tiver subgrupos, o convidado só terá acesso ao grupo em que foi convidado — a menos que um administrador do pai lhe dê acesso explicitamente.
As checklists de grupo funcionam de forma diferente das checklists pessoais?
Sim. As checklists de grupo têm dois modos de validação: "single" (uma única pessoa valida para todos) e "everyone" (cada membro deve validar individualmente). O modo "everyone" é perfeito para listas de compras familiares onde ninguém compra a mesma coisa.
É possível mover um grupo para outro grupo pai?
Sim, um administrador pode reorganizar a hierarquia alterando o parent_id de um grupo. Mas é uma operação sensível: as permissões herdadas mudam imediatamente, e os membros do novo pai veem o conteúdo do grupo movido. Uma mensagem de confirmação explícita avisa o administrador das consequências.