DocumentaçãoPara quem contribui
Arquitetura
Como o repositório do OpENEM é organizado: a API NestJS, o app React, o site de documentação e onde fica cada tipo de código.
O repositório
| Pasta | O que tem |
|---|---|
openem_backend/ | API NestJS com Prisma e PostgreSQL. |
openem_frontend/ | O app: React, Vite e Tailwind v4, como PWA. |
openem_site/ | A landing page e esta documentação. |
tools/enem-fix/ | Scripts que consertam o dataset das provas e geram o catálogo de redações. |
docs/ | O PRD, com as decisões de produto e arquitetura. |
.github/ | CI, modelos de issue e PR e os espelhos em inglês da documentação. |
Cada projeto tem o próprio package.json: os comandos npm rodam de dentro da pasta dele. O docker-compose.yml da raiz junta banco, Redis, backend e frontend. Numa instalação, o site fica na raiz (/ e /docs/) e o app em /app/, servidos pelo mesmo nginx.
Backend
Entrada
src/main.tspõe as rotas de domínio sob o prefixoapi/v1(a raiz/e/healthficam fora), liga oValidationPipeglobal comwhitelist,forbidNonWhitelistedetransform(toda entrada passa por DTOs comclass-validator), aplica o helmet, serve as imagens das questões em/media/enem/e monta o Swagger em/apiquandoNODE_ENVnão éproduction.src/app.module.tsimporta os módulos e registra o rate limiting global. Módulo novo entra no arrayimports; prefira gerar comnpx nest g module <nome>.- O
PrismaModuleé global: injete oPrismaServiceem qualquer serviço sem importá-lo de novo.
Módulos
- Conta:
auth(cadastro com aceite dos termos e login com JWT) eusers(perfil de estudo, perfil público e os direitos do titular da LGPD:GET /users/me/exporteDELETE /users/me). - Banco de questões:
subjects(áreas, matérias e assuntos),questions(inclui o comentário sob demanda por IA),question-notesefavorites. - Estudo:
answers,simulations(simulados, por prova ou por tema, e o caderno em PDF),review(revisão espaçada SM-2),recommendation,performance,study-plan,weekly-goalsepomodoro. - Materiais:
flashcards,formulas(com o caderno de fórmulas em PDF),videosetools(a lista da tela Ferramentas, com as estrelas do GitHub). - Progresso e comunidade:
gamification(XP, nível, liga, sequência, conquistas, desafio diário),community(fórum) ethemes(temas de cor dos alunos). - Infraestrutura:
prisma,health,ai-usage(cotas das chamadas pagas de IA),cache(oCacheService, sobre o Redis ou a memória) erate-limit(contadores do rate limiting no Redis e o limite de senhas erradas por conta). essays: envio de redação e correção por rubrica. Continua na API, mas o app não tem tela para ele.
Lógica pura em src/common
Regra de negócio que não depende de banco nem de rede fica em src/common/, com um .spec.ts ao lado. Alguns exemplos: spaced-repetition.ts (SM-2), gamification.ts, irt.ts (nota TRI), simulation-filters.ts, simulation-answers.ts, sampling.ts, ai-quota.ts, trust-proxy.ts, enem-import/mapping.ts e classification/heuristic.ts. Os serviços dos módulos chamam essas funções e cuidam do Prisma.
O rootDir do Jest é src/. Por isso lógica testável não mora em prisma/: os scripts de lá (seed, import, classificação) importam de src/common/. Duas subpastas merecem nota:
src/common/ai/é a camada de provedores de IA (escolha do provedor e uma chamada que devolve JSON). O comentário de questão e a classificação de assunto passam por ela.src/common/pdf/desenha os PDFs com PDFKit. Todo PDF passa pela fila doPdfRenderService, que desenha cada um numa worker thread, porque o desenho é CPU síncrona e travaria a API. Não chame as funções de desenho direto de um serviço.
Banco
- Prisma 6, fixado. O Prisma 7 muda o modelo de configuração; não atualize sem migrar para ele.
- PostgreSQL em todo lugar: desenvolvimento, CI, e2e e produção. Schema em
prisma/schema.prisma, migrations emprisma/migrations/. - Evite SQL cru. Busca de texto usa
mode: 'insensitive', porque ocontainsdo Postgres diferencia maiúsculas. - Selecione alternativas sempre por
src/questions/question.select.ts, que garante a ordem porposition. - Questão oficial pode ter
topicIdeexplanationnulos: todo código que lê questão precisa aceitar os dois. - Toda relação com
UserusaonDelete: Cascade, e é isso que faz a exclusão da conta apagar tudo. Tabela nova ligada ao aluno também entra na exportação, emsrc/users/data-export.ts: o teste dele compara a lista com o schema e quebra até ela entrar.
Frontend
| Caminho | O que tem |
|---|---|
src/App.tsx | As rotas (React Router). |
src/pages/ | Uma tela por arquivo: Home, Questoes, Simulados, Revisao, Redacoes, Temas e as demais. |
src/components/ | Peças reutilizáveis. |
src/lib/ | Lógica pura, com *.test.ts ao lado. |
src/api.ts | Cliente da API, com base em /api/v1. |
src/offline/ | Fila de ações feitas sem rede. |
src/data/redacoes.json | Catálogo estático das propostas de redação. |
src/themes.css, src/theme.tsx | Temas de cor; veja Temas de cor. |
nginx.conf | Servidor de produção, com a CSP. |
O PWA usa vite-plugin-pwa. O service worker guarda as leituras da API (rede primeiro, cache como reserva) e as imagens das questões (cache primeiro, porque o nome do arquivo nunca muda), para o app funcionar com internet limitada.
O site (openem_site)
O openem_site/ é um projeto React + Vite separado do app, com a landing em / e a documentação em /docs/.
- Pré-renderizado.
npm run buildgera o bundle do navegador, um bundle de servidor (src/entry-server.tsx) e rodascripts/prerender.mjs, que grava um HTML estático por página. O texto aparece antes de o JavaScript carregar, e buscadores leem a página inteira. - Mesma aparência do app. O CSS do site importa o
index.cssdo app (tokens, temas e componentes), e o build reaproveitatheme-boot.jseicon.svgdeopenem_frontend/public/. Um tema novo no app chega ao site sem cópia de arquivo. - Dados do app. A landing lê, no build, o catálogo de redações do app para listar os temas por ano.
- Páginas da documentação. São arquivos Markdown em
src/docs/conteudo/<grupo>/NN-slug.md, com cabeçalhotituloedescricao; o número só ordena, e a URL é/docs/<grupo>/<slug>/. O testesrc/docs/conteudo.test.tsexige título e descrição, proíbe título de nível 1 e travessão no corpo e confere se os links internos apontam para páginas que existem. - Inglês. A tradução de uma página fica em
src/docs/conteudo/en/<grupo>/, com o mesmo nome de arquivo, e sai em/docs/en/<grupo>/<slug>/. Página sem tradução aparece em português no endereço inglês, com um aviso. Os grupos de hospedagem e contribuição estão traduzidos; os guias de estudante ficam em português, a língua do ENEM. - Páginas legais. A Política de Privacidade e os Termos de Uso são Markdown em
src/legal/conteudo/(tradução emen/) e saem em/privacidade/e/termos/(e/en/privacidade/,/en/termos/). O cabeçalhoatualizadotem de ser igual aoTERMS_VERSIONdo backend, a versão que o cadastro grava;src/legal/documentos.test.tsconfere isso e as seções da política. - Referência da API. A página Referência da API é montada de
src/docs/api/openapi.json, que o backend gera dos próprios controllers comnpm run openapi(sem subir servidor nem banco). O CI quebra se o arquivo ficar desatualizado.
Para rodar o site e o resto do projeto na sua máquina, veja Desenvolvimento.