# e-li.nps (Go + HTMX) Widget NPS embutível via **1 arquivo JS** + API em Go. ## Requisitos - Go 1.22+ - PostgreSQL 14+ ## Variáveis de ambiente - `DATABASE_URL` (obrigatória) - Ex: `postgres://postgres:postgres@localhost:5432/gonps?sslmode=disable` - `ADDR` (opcional, default `:8080`) - `SENHA_PAINEL` (opcional) - Se definida, habilita o painel em `/painel`. - Se vazia, o painel fica desabilitado. ### Cache do widget (e-li.nps.js) O servidor controla o cache de `/static/e-li.nps.js` via **ETag**. - A versão (ETag) é **gerada automaticamente a cada inicialização do servidor**. - O browser é instruído a **revalidar** (`Cache-Control: no-cache, must-revalidate`), então: - se o ETag não mudou: o servidor responde `304` (rápido) - se o ETag mudou: o browser baixa o JS novo automaticamente Isso evita problemas de clientes com JS antigo em cache após mudanças. ### Arquivo `.env` O servidor carrega automaticamente um arquivo `.env` na raiz do projeto (se existir) usando `godotenv`. Isso facilita rodar localmente sem exportar variáveis manualmente. Exemplo de `.env`: ```env DATABASE_URL='postgres://postgres:postgres@localhost:5432/gonps?sslmode=disable' ADDR=':8080' ``` ## Como rodar 1. Suba um Postgres (exemplo via Docker): ```bash docker run --rm -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=gonps -p 5432:5432 postgres:16 ``` 2. Rode o server: ```bash go run ./cmd/server ``` ## Rodar com Docker Este repositório inclui: - `Dockerfile` (build multi-stage do binário Go) - `docker-compose.yml` (apenas o app; Postgres é externo) Para subir tudo: ```bash docker compose up --build ``` > Dica: esse comando roda em **foreground**. Ao pressionar `Ctrl+C`, o Docker > encerra os containers. > > Para manter rodando em background (recomendado em servidor): > > ```bash > docker compose up -d --build > ``` Para forçar rebuild da imagem (mesmo sem mudanças detectadas): ```bash docker compose build --no-cache && docker compose up ``` Para parar a aplicação: ```bash docker compose down ``` Para apenas parar sem remover (mantém o container): ```bash docker compose stop ``` Para iniciar novamente após stop: ```bash docker compose start ``` > Importante: > - O Postgres é **externo**. > - O arquivo `.env` é **obrigatório** e deve ser passado como **volume** para `/app/.env`. > - O servidor carrega esse arquivo automaticamente via `godotenv` ao iniciar. > > Exemplo (compose): `./.env:/app/.env:ro` ### Postgres no host (host.docker.internal) Se o seu Postgres estiver rodando **no host** (fora do container) e você quiser que o container acesse via `host.docker.internal`, use no `.env`: ```env DATABASE_URL='postgres://usuario:senha@host.docker.internal:5432/seu_banco?sslmode=disable' ``` No Linux, o `docker-compose.yml` já inclui `extra_hosts` com `host-gateway` para esse hostname funcionar. ## Publicar com Caddy (reverse proxy) Este repositório inclui um exemplo de `Caddyfile` para publicar o serviço em: - `https://nps.idz.one` → `{ip-app}:8080` ### Pré-requisitos - O DNS de `nps.idz.one` deve apontar para o **IP público** do servidor onde o Caddy roda. - Portas **80/443** liberadas para o Caddy (TLS automático). ### IP real do usuário O Caddy repassa o IP do cliente via `X-Forwarded-For` e `X-Real-IP`. O servidor Go já usa `middleware.RealIP` (chi), então o IP real chega corretamente e é gravado em `ip_real`. ### Check do IP real (direto / Docker / Caddy) O painel tem um endpoint de debug que mostra o IP que a aplicação está enxergando e os headers recebidos: - `GET /painel/debug/ip` Passo a passo: 1) Faça login no painel em `/painel`. 2) Acesse `/painel/debug/ip`. O JSON retornado inclui: - `remote_addr` (já após o `middleware.RealIP`) - `x_forwarded_for` - `x_real_ip` Interpretação esperada: - Rodando **direto** (sem proxy): `remote_addr` deve ser o IP do cliente (ou do seu balanceador). - Rodando via **Docker**: se você acessar diretamente a porta publicada, o `remote_addr` tende a ser o IP do host/bridge; atrás de proxy (Caddy), o `remote_addr` deve refletir o IP real. - Rodando via **Caddy**: `x_forwarded_for` deve conter o IP real do cliente e o `remote_addr` deve refletir esse IP após `RealIP`. Depois acesse: - Home/README: `http://localhost:8080/` - Teste do widget: `http://localhost:8080/teste.html` - Painel: `http://localhost:8080/painel` (senha em `SENHA_PAINEL`) Painel: - Acesse `http://localhost:8080/painel` - Você será redirecionado para `/painel/login` Healthcheck: ```bash curl -i http://localhost:8080/healthz ``` ## Incluir o widget em outra aplicação ### Tipagem TypeScript (opcional) Se você quiser ter autocomplete e validação de tipos no seu projeto (TS), pode declarar a interface abaixo: ```ts declare global { interface Window { ELiNPS: { init: (opts: ELiNPSInitOptions) => Promise | void; }; } } export type ELiNPSInitOptions = { // apiBase (opcional) // Base da API do e-li.nps. // Se o widget estiver sendo servido pelo mesmo host, pode deixar vazio. apiBase?: string; // cooldownHours (opcional) // Tempo (em horas) de cooldown visual no navegador. cooldownHours?: number; // data_minima_abertura (opcional) // Bloqueia a abertura do modal antes de uma data. // Formato ISO (data): YYYY-MM-DD (ex.: "2026-01-01"). data_minima_abertura?: string; // produto_nome (obrigatório) produto_nome: string; // inquilino_codigo (obrigatório) inquilino_codigo: string; // inquilino_nome (obrigatório) inquilino_nome: string; // usuario_codigo (obrigatório) usuario_codigo: string; // usuario_nome (obrigatório) usuario_nome: string; // usuario_telefone (opcional) usuario_telefone?: string; // usuario_email (opcional) usuario_email?: string; }; ``` ```html ``` ## Endpoints ### `POST /api/e-li.nps/pedido` ```bash curl -sS -X POST http://localhost:8080/api/e-li.nps/pedido \ -H 'Content-Type: application/json' \ -d '{ "produto_nome":"e-licencie.gov", "inquilino_codigo":"acme", "inquilino_nome":"ACME", "usuario_codigo":"u-123", "usuario_nome":"Maria", "usuario_telefone":"+55...", "usuario_email":"maria@acme.com" }' ``` ### `GET /e-li.nps/{produto}/{id}/form` Abre o formulário (HTML) para responder/editar. ### `PATCH /api/e-li.nps/{produto}/{id}` ```bash curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/ \ -H 'Content-Type: application/json' \ -d '{"nota":10}' ``` Finalizar: ```bash curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/ \ -H 'Content-Type: application/json' \ -d '{"justificativa":"muito bom", "finalizar":true}' ``` ## Observações importantes - **Fail-closed**: se a API falhar, o widget não abre o modal. - **CORS**: liberado com `Access-Control-Allow-Origin: *`. - **IP real do usuário**: o sistema grava `ip_real` no banco (IPv4/IPv6). - Para funcionar corretamente atrás de proxy/Docker, garanta que o proxy repasse `X-Forwarded-For` / `X-Real-IP`. - O servidor usa `middleware.RealIP` (chi) para resolver o IP antes de gravar. - **Tabelas por produto**: `nps_{produto}` é criada automaticamente ao ver um `produto_nome` novo. - O backend **normaliza** `produto_nome` apenas para uso técnico (nome da tabela e rota): - minúsculo + trim - remove diacríticos - converte caracteres fora de `[a-z0-9_]` para `_` - valida por regex: `^[a-z_][a-z0-9_]*$` - O nome **exibido ao usuário** é o original informado e fica salvo em `produto_nome` na tabela do produto. - O controle de exibição (regras 45 dias / 10 dias) é baseado em: **produto + inquilino_codigo + usuario_codigo**. ### Observabilidade (logs) O servidor registra **uma linha por requisição** com: - `metodo`, `path`, `status` - `dur_ms` (tempo de execução) - `request_id` (quando disponível) - `ip_real` (após `middleware.RealIP`) Regra importante (segurança): o projeto **não** deve logar segredos (senha, tokens, cookies, Authorization, DSN). --- ## Créditos e suporte Desenvolvido por **Azteca Software (e-licencie)** para pesquisa de NPS. Suporte: **ti@e-licencie.com.br** ou WhatsApp **(48) 9 9948 2983**.