Widget NPS embutível via 1 arquivo JS + API em Go.
DATABASE_URL (obrigatória)
postgres://postgres:postgres@localhost:5432/gonps?sslmode=disableADDR (opcional, default :8080)SENHA_PAINEL (opcional)
/painel.O servidor controla o cache de /static/e-li.nps.js via ETag.
Cache-Control: no-cache, must-revalidate), então:
304 (rápido)Isso evita problemas de clientes com JS antigo em cache após mudanças.
.envO 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:
DATABASE_URL='postgres://postgres:postgres@localhost:5432/gonps?sslmode=disable'
ADDR=':8080'
docker run --rm -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=gonps -p 5432:5432 postgres:16
go run ./cmd/server
Este repositório inclui:
Dockerfile (build multi-stage do binário Go)docker-compose.yml (apenas o app; Postgres é externo)Para subir tudo:
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):
docker compose up -d --build
Para forçar rebuild da imagem (mesmo sem mudanças detectadas):
docker compose build --no-cache && docker compose up
Para parar a aplicação:
docker compose down
Para apenas parar sem remover (mantém o container):
docker compose stop
Para iniciar novamente após stop:
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
godotenvao iniciar.Exemplo (compose):
./.env:/app/.env:ro
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:
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.
Este repositório inclui um exemplo de Caddyfile para publicar o serviço em:
https://nps.idz.one → {ip-app}:8080nps.idz.one deve apontar para o IP público do servidor onde o Caddy roda.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.
O painel tem um endpoint de debug que mostra o IP que a aplicação está enxergando e os headers recebidos:
GET /painel/debug/ipPasso a passo:
/painel./painel/debug/ip.O JSON retornado inclui:
remote_addr (já após o middleware.RealIP)x_forwarded_forx_real_ipInterpretação esperada:
remote_addr deve ser o IP do cliente (ou do seu balanceador).remote_addr tende a ser o IP do host/bridge; atrás de proxy (Caddy), o remote_addr deve refletir o IP real.x_forwarded_for deve conter o IP real do cliente e o remote_addr deve refletir esse IP após RealIP.Depois acesse:
http://localhost:8080/http://localhost:8080/teste.htmlhttp://localhost:8080/painel (senha em SENHA_PAINEL)Painel:
http://localhost:8080/painel/painel/loginHealthcheck:
curl -i http://localhost:8080/healthz
Se você quiser ter autocomplete e validação de tipos no seu projeto (TS), pode declarar a interface abaixo:
declare global {
interface Window {
ELiNPS: {
init: (opts: ELiNPSInitOptions) => Promise<void> | 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;
};
<!-- Carrega o widget (arquivo único) -->
<script src="http://localhost:8080/static/e-li.nps.js"></script>
<script>
window.ELiNPS.init({
// apiBase (opcional)
// Base da API do e-li.nps.
// - Se o widget estiver sendo servido pelo mesmo host, pode deixar vazio.
// - Se a API estiver em outro host, informe a URL completa.
// Ex.: "https://sua-api.exemplo.com".
apiBase: 'http://localhost:8080',
// cooldownHours (opcional)
// Tempo (em horas) de cooldown visual no navegador para evitar o modal
// reaparecer em sequência.
// Default: 24.
cooldownHours: 24,
// data_minima_abertura (opcional)
// Bloqueia a abertura do modal antes de uma data.
// Formato ISO (data): YYYY-MM-DD (ex.: "2026-01-01").
// Ex.: data_minima_abertura: '2026-01-01',
data_minima_abertura: '',
// produto_nome (obrigatório)
// Nome livre do produto (é exibido ao usuário exatamente como informado).
// Exemplos: "e-licencie.gov", "Cachaça & Churras".
// Importante: o backend normaliza apenas para montar nome de tabela/rotas.
produto_nome: 'e-licencie.gov',
// inquilino_codigo (obrigatório)
// Código do cliente/tenant (usado nas regras de exibição e no banco).
inquilino_codigo: 'acme',
// inquilino_nome (obrigatório)
// Nome do cliente/tenant (exibição / auditoria).
inquilino_nome: 'ACME LTDA',
// usuario_codigo (obrigatório)
// Identificador do usuário.
// Importante: é a chave principal para as regras de exibição.
usuario_codigo: 'u-123',
// usuario_nome (obrigatório)
// Nome do usuário (exibição / auditoria).
usuario_nome: 'Maria',
// usuario_telefone (opcional)
// Telefone do usuário (auditoria). Pode ser vazio.
usuario_telefone: '+55 11 99999-9999',
// usuario_email (opcional)
// Email do usuário. É opcional: o controle de exibição é por
// (produto + inquilino_codigo + usuario_codigo).
usuario_email: 'maria@acme.com',
});
</script>
POST /api/e-li.nps/pedidocurl -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}/formAbre o formulário (HTML) para responder/editar.
PATCH /api/e-li.nps/{produto}/{id}curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/<id> \
-H 'Content-Type: application/json' \
-d '{"nota":10}'
Finalizar:
curl -sS -X PATCH http://localhost:8080/api/e-li.nps/elicencie_gov/<id> \
-H 'Content-Type: application/json' \
-d '{"justificativa":"muito bom", "finalizar":true}'
Access-Control-Allow-Origin: *.ip_real no banco (IPv4/IPv6).
X-Forwarded-For / X-Real-IP.middleware.RealIP (chi) para resolver o IP antes de gravar.nps_{produto} é criada automaticamente ao ver um produto_nome novo.produto_nome apenas para uso técnico (nome da tabela e rota):
[a-z0-9_] para _^[a-z_][a-z0-9_]*$produto_nome na tabela do produto.Alguns cuidados:
README.mdDesenvolvido por Azteca Software (e-licencie) para pesquisa de NPS.
Suporte: ti@e-licencie.com.br ou WhatsApp (48) 9 9948 2983.
Página gerada automaticamente a partir de README.md