Arquitetura de dados na Nexus G: como modelei tabelas e segurança no Supabase/Postgres

Tecnologia··4 min de leitura
Arquitetura de dados na Nexus G: como modelei tabelas e segurança no Supabase/Postgres
Wendell Nunes Lima
Wendell Nunes Lima

@wendellnuneslima

Fechando o tripé técnico do pilar de Engenharia depois da stack geral e do SEO técnico com Next.js falta a camada que sustenta tudo por trás: como os dados de perfil, conteúdo e depoimento se relacionam no Postgres, e como o Supabase resolve, com Row Level Security, o problema de cada usuário só poder mexer no que é dele.

O primeiro passo foi desenhar o modelo relacional pensando na pergunta certa: o que pertence a quem, e o que depende do quê. Na Nexus G, um perfil é a entidade central, e conteúdo, depoimento e visualização existem apenas em função de um perfil nenhuma dessas tabelas faz sentido isolada. O esqueleto simplificado fica assim:

create table profiles (
  id uuid primary key references auth.users(id),
  username text unique not null,
  name text not null,
  role text,
  bio text,
  avatar_url text,
  created_at timestamptz default now()
);

create table contents (
  id uuid primary key default gen_random_uuid(),
  profile_id uuid references profiles(id) on delete cascade,
  title text not null,
  slug text not null,
  body text not null,
  category text,
  published boolean default false,
  created_at timestamptz default now(),
  updated_at timestamptz default now(),
  unique (profile_id, slug)
);

create table testimonials (
  id uuid primary key default gen_random_uuid(),
  profile_id uuid references profiles(id) on delete cascade,
  author_name text not null,
  message text not null,
  created_at timestamptz default now()
);

create table content_views (
  id uuid primary key default gen_random_uuid(),
  content_id uuid references contents(id) on delete cascade,
  viewed_at timestamptz default now(),
  visitor_hash text
);

Um detalhe que fez diferença real: profiles.id referencia diretamente auth.users(id). Em vez de manter uma tabela de usuários separada da tabela de autenticação, o perfil é uma extensão direta do usuário autenticado do Supabase. Isso elimina uma camada inteira de sincronização manual entre "quem fez login" e "quem é dono de qual perfil" a própria chave estrangeira já resolve essa ligação.

A tabela content_views merece atenção à parte porque registrar visualização é diferente de registrar conteúdo. Em vez de incrementar um contador direto na tabela contents a cada view o que geraria concorrência de escrita toda vez que dois visitantes acessam o mesmo perfil ao mesmo tempo cada visualização vira uma linha própria, e a contagem exibida no painel é uma agregação:

select content_id, count(*) as total_views
from content_views
group by content_id;

Isso separa claramente o dado bruto (cada evento de visualização, que pode alimentar análises futuras como visualização por período) da métrica exibida (o total simples que aparece no perfil), sem travar a tabela principal de conteúdo com escrita constante.

O segundo pilar é a segurança, resolvida com Row Level Security ativado em cada tabela. O RLS do Postgres nega acesso por padrão assim que é habilitado, e cada operação leitura, escrita, atualização precisa de uma política explícita. Para perfis, qualquer visitante pode ler qualquer perfil público, mas só o próprio dono pode editar o seu:

alter table profiles enable row level security;

create policy "Perfis são públicos para leitura"
  on profiles for select
  using (true);

create policy "Usuário só edita o próprio perfil"
  on profiles for update
  using (auth.uid() = id);

Para conteúdo, a regra muda um pouco: leitura pública só vale para o que está marcado como publicado, e escrita só vale para o dono:

alter table contents enable row level security;

create policy "Conteúdo publicado é público"
  on contents for select
  using (published = true);

create policy "Dono pode gerenciar seus próprios conteúdos"
  on contents for all
  using (auth.uid() = profile_id);

Essa segunda política junta select, insert, update e delete numa única regra porque o critério de dono é o mesmo pras quatro operações e é justamente essa política que garante que, mesmo se algum bug de front-end tentasse expor um formulário de edição pro usuário errado, o banco recusaria a operação antes de qualquer dado sensível trafegar. A segurança não depende só do que a interface esconde, depende do que o banco realmente permite.

Um ponto de atenção prático: RLS mal configurado é silencioso ele não gera erro visível, simplesmente retorna zero linhas quando a política nega acesso. Isso torna testar cada política manualmente, simulando o auth.uid() de diferentes usuários, uma etapa que não dá pra pular antes de subir qualquer tabela nova pra produção.

Com esse modelo perfil como entidade central ligada direto à autenticação, conteúdo e depoimento dependentes por chave estrangeira, visualização tratada como evento agregável, e RLS garantindo que cada política de acesso viva dentro do próprio banco a Nexus G sustenta múltiplos perfis crescendo em paralelo sem que a arquitetura vire gargalo. É a parte que ninguém vê no perfil público, mas que decide se a plataforma aguenta escalar ou não.

Comentários

Entre com Google para comentar.

Mais de Wendell Nunes Lima

Wendell Nunes Lima

Escrito por

Wendell Nunes Lima

Fundador e Desenvolvedor da Nexus G

Especialista em desenvolvimento de plataformas digitais e fundador da Nexus G, um ecossistema criado para fortalecer a autoridade profissional com conteúdo otimizado para SEO e páginas preparadas para ranquear no Google.