Aplicação Next.js 16 (App Router) sobre Supabase, com a regra de negócio concentrada no Postgres: 25 migrações SQL versionadas definem tabelas, cerca de 100 policies de RLS, funções security definer, triggers e jobs do pg_cron. O front lê e escreve com @supabase/ssr usando a sessão do próprio usuário, então o que cada pessoa vê ou altera é decidido pelo banco, inclusive pela API pública. Os tipos do banco são gerados a partir do schema e tipam os clientes do Supabase. Busca, recomendações, notificações, limites de envio e classificação indicativa rodam no Postgres, sem serviços externos; a documentação de uso é feita com Fumadocs em MDX.
- Front-end
- Next.js 16.3 (App Router, Server Actions, imagens Open Graph geradas por livro e capítulo)React 19.2Tailwind CSS 4
- Back-end e dados
- Supabase (Postgres, Auth, Storage) via @supabase/ssr e supabase-js 2com tipos gerados do schema
- Banco
- 25 migrações SQL com RLSfunções PL/pgSQLfull-text search com unaccent e pg_trgme pg_cron para tarefas agendadas
- Conteúdo
- Markdown com react-markdown e remark-gfm; exportação para EPUBHTML e Markdown com unified/rehype; Fumadocs (MDX) para o guia de uso
- Infra
- Vercel com AnalyticsSpeed Insights e BotID; PWA com service worker próprio para leitura offline
- Qualidade
- TypeScript strictESLint e relatório de erros estruturado nos logs (servidor e navegador)
Segurança
Segurança decidida no banco
Cada autor só altera os próprios livros, rascunhos e capítulos agendados ficam invisíveis ao público e cada usuário só comenta e avalia em nome próprio: tudo isso é RLS, não checagem na tela. Além das policies, privilégios por coluna (revoke update na tabela e grant update só nas colunas editáveis) impedem que alguém se promova a admin; a coluna is_admin só muda pela função set_admin, chamada por outro admin.
Segurança
Proteção de menores no próprio Postgres
A função account_status() classifica a conta (ok, sem data, menor de 13, aguardando responsável etc.) e policies restritivas impedem que conta não liberada comente, avalie, reaja, siga, grife ou publique. A classificação indicativa entra numa policy restritiva de livros e nas funções de visibilidade, então um livro +18 some de listas, busca, capítulos, comentários e wiki para visitantes e menores, também pela API. O link enviado ao responsável leva um token aleatório; o banco guarda só o hash SHA-256.
Dados
Busca em português sem serviço externo
Uma configuração de text search própria (português com stemming + unaccent) alimenta colunas tsvector geradas com pesos para título, gênero, sinopse e texto, indexadas com GIN, então o índice se mantém sozinho. Índices de trigramas (pg_trgm) sobre uma versão IMMUTABLE de unaccent toleram erros de digitação nos títulos. A função de busca é security invoker, para o RLS continuar valendo.
Arquitetura
Limites contra spam e agendamento com pg_cron
Um trigger genérico de rate limit, com teto e janela por ação, barra excesso de comentários, notas, reações, grifos e seguidas direto no banco; a tabela de marcações tem RLS sem policies e as funções têm EXECUTE revogado para anon e authenticated. Como um capítulo agendado entra no ar sem nenhum UPDATE (só pela data), um trigger não perceberia: o pg_cron roda a cada 5 minutos a varredura que gera os avisos de capítulo novo, e diariamente uma rotina de retenção limpa dados antigos.
Dados
Recomendações que não expõem quem leu o quê
O "quem leu isto também leu" é calculado por coocorrência no histórico de leitura numa função security definer, já que o RLS só deixa cada pessoa ver as próprias linhas. A função devolve apenas o agregado e só quando há pelo menos 5 leitores em comum, para que não dê para deduzir a leitura de alguém específico.
Acessibilidade
Leitura offline e acessível
Um service worker próprio guarda capítulos e páginas de livro abertos (rede primeiro, cópia em cache sem rede) e pré-carrega os próximos capítulos dos livros da biblioteca; ao sair da conta, as cópias são apagadas. O leitor oferece ajuste de fonte, incluindo Atkinson Hyperlegible para baixa visão, espaçamento, cores e leitura em voz alta pela Web Speech API.