Files
nutriscan/README.md
T

7.2 KiB
Raw Blame History

O cache JSON do servidor vem no repositório (data/products-cache.json) com produtos reais do Open Food Facts. Contado por Nutri-Score, num clone novo e sem instalar nada:

$ node -e "const p = Object.values(require('./data/products-cache.json').products); const g = {}; for (const x of p) g[x.nutrition_grades] = (g[x.nutrition_grades] || 0) + 1; console.log(p.length, g)"
232 { e: 26, unknown: 167, d: 24, c: 9, a: 2, b: 4 }

O que é

NutriScan é um app web em Next.js para buscar produtos alimentícios no Open Food Facts e montar listas de compras. A entrada é detectada sozinha: 8 a 13 dígitos viram busca por código de barras; o resto, busca por nome ou marca.

Páginas: / (landing), /resultados, /produto/[barcode], /lista/[id] e /sobre.

Recurso O que faz
Filtros Nutri-Score A–E, grupos NOVA 1–4, categorias, marcas, alérgenos e aditivos (incluir/excluir, ou "sem aditivos"), faixas por 100 g de energia, gordura, carboidratos, proteínas, açúcares, fibras e sódio, e países
12 critérios de ordenação Relevância, Nutri-Score, nome, marca, energia, gordura, açúcares, completude, com imagem, com dados nutricionais, Eco-Score e grupo NOVA, em ordem crescente ou decrescente
País por IP A rota /api/location descobre o país do visitante e a busca prioriza produtos dele; o resultado fica 7 dias no localStorage, com Brasil como fallback
Listas de compras Várias listas acessíveis por ID em /lista/[id]; cada item tem nome, quantidade, unidade e status de comprado, persistidos com Prisma + SQLite
Interface Tema claro/escuro, componentes shadcn/ui sobre Radix UI, animações com Framer Motion, skeletons, paginação e manifest de PWA

Por que usar

  • Menos chamadas ao Open Food Facts. A busca passa por dois caches antes da API: localStorage no navegador (TTL padrão de 1 hora) e o cache JSON no servidor. Tudo que vem da API é gravado no cache JSON.
  • Funciona sem a API. No modo cache-only o servidor responde só com o cache JSON, sem consultar o Open Food Facts.
  • Filtros combinados: Nutri-Score, NOVA, alérgenos, aditivos e faixas de nutrientes ao mesmo tempo, com 12 ordenações.

Quando não usar: se você precisa publicar o app ou usá-lo comercialmente (a licença só permite uso pessoal em localhost), ler código de barras pela câmera (a entrada é digitada ou colada) ou esperar Nutri-Score em cada produto (no cache acima, 167 de 232 produtos vieram sem nota).

Como funciona

  1. O navegador procura a busca no localStorage (lib/cacheManager.ts, hooks/useSearchCache.ts).
  2. Se não achar, chama as rotas Next.js /api/search e /api/products/[barcode].
  3. As rotas consultam o cache JSON do servidor (lib/jsonCacheManager.ts, data/products-cache.json).
  4. Fora do modo cache-only, o que falta vem do Open Food Facts (lib/openFoodFactsApi.ts: timeout de 8 s, 200 ms entre páginas, até 1000 produtos por página) e é gravado no cache JSON. POST /api/cache/sync também grava produtos enviados pelo cliente.
  5. As listas de compras usam Server Actions (app/actions.ts) sobre Prisma + SQLite: modelos Lista e Produtos em prisma/schema.prisma.

Início rápido

Requisitos: Node.js 18+ (o deploy usa nodejs_18) e pnpm (o deploy fixa [email protected]).

git clone https://git.evandro.dev.br/evandro/nutriscan.git
cd nutriscan
pnpm install                      # o postinstall já roda `prisma generate`

# .env na raiz (o Prisma lê .env, não .env.local)
echo 'DATABASE_URL="file:./db/database.sqlite"' > .env

pnpm db:push                      # cria o banco SQLite a partir do schema
pnpm dev                          # http://localhost:3000

Warning

Não existe .env.example e pnpm db:migrate não funciona num clone novo: ele roda prisma migrate deploy, mas /prisma/migrations está no .gitignore. Crie o banco com pnpm db:push. O DATABASE_URL é resolvido a partir da pasta prisma/, então file:./db/database.sqlite vira prisma/db/database.sqlite (pasta também ignorada pelo git).

Outros scripts: pnpm build (prisma generate && next build), pnpm start, pnpm lint (next lint), pnpm db:generate.

Configuração

Variável Efeito
DATABASE_URL Banco SQLite das listas de compras (obrigatória)
NEXT_PUBLIC_CACHE_ONLY_MODE=true As rotas de busca e de produto respondem só com o cache JSON
NEXT_PUBLIC_RYBBIT_HOST Destino do proxy /analytics/* (padrão https://analytics.evandro.dev.br/api)

O switch de cache-only na interface só funciona em produção (NODE_ENV=production); as rotas /api/search e /api/products/[barcode] aceitam ?cache_only=true em qualquer ambiente.

O que sai do seu servidor:

  • Open Food Facts: dados e imagens de produtos (next.config.mjs libera static.openfoodfacts.org e images.openfoodfacts.org).
  • ip-api.com: o IP do visitante, enviado pelo servidor por HTTP, para descobrir o país.
  • Rybbit Analytics (self-hosted, via proxy /analytics/): com session replay e rastreamento de erros.
  • O app também exibe um card de doação via Buy Me a Coffee.

Estado do projeto

Projeto pessoal, sem suíte de testes; o único check é pnpm lint. O último trabalho de código foi o redesign completo e os ajustes no deploy com Prisma/SQLite, em 2026-07-16. O repositório traz dokploy.yaml e nixpacks.toml para o deploy do autor, sem workflow de CI. A instância pública do autor em nutriscan.evandro.dev.br não respondia com o app em 2026-10-24.

Limites conhecidos:

  • Toda busca nova depende do Open Food Facts; além do cache, não há fila nem fallback.
  • A localização é por IP, não pela API de geolocalização do navegador.
  • O sitemap.xml lista só a página inicial.

Licença

Licença de Uso Pessoal, em LICENSE (Copyright (c) 2024 Evandro): uso pessoal, não comercial e só em localhost. É proibido fazer deploy público, redistribuir, usar comercialmente ou criar obras derivadas para distribuição. Dúvidas sobre a licença: [email protected].

Créditos: Open Food Facts, Next.js, Prisma, shadcn/ui e Radix UI.