primeira versão do e-li-nps construido com IA

This commit is contained in:
Luiz Silva 2025-12-31 11:18:20 -03:00
commit 06950d6e2c
34 changed files with 2524 additions and 0 deletions

View file

@ -0,0 +1,134 @@
package elinps
import (
"bytes"
"fmt"
"html/template"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/yuin/goldmark"
)
// ReadmePage serve o README.md renderizado como HTML.
//
// Motivação: dar uma "home" simples para o serviço (documentação em tempo real).
// Sem autenticação, conforme solicitado.
//
// Implementação: cache em memória por mtime para evitar renderização em toda request.
type ReadmePage struct {
caminho string
mu sync.Mutex
ultimoMTime time.Time
html []byte
errMsg string
}
func NewReadmePage(caminho string) *ReadmePage {
return &ReadmePage{caminho: caminho}
}
func (p *ReadmePage) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Só respondemos GET/HEAD.
if r.Method != http.MethodGet && r.Method != http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
html, errMsg := p.renderIfNeeded()
if errMsg != "" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(errMsg))
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
if r.Method == http.MethodHead {
return
}
w.Write(html)
}
func (p *ReadmePage) renderIfNeeded() ([]byte, string) {
p.mu.Lock()
defer p.mu.Unlock()
st, err := os.Stat(p.caminho)
if err != nil {
p.errMsg = fmt.Sprintf("README não encontrado: %s", p.caminho)
p.html = nil
p.ultimoMTime = time.Time{}
return nil, p.errMsg
}
// Cache: se o arquivo não mudou, devolve o HTML já renderizado.
if p.html != nil && st.ModTime().Equal(p.ultimoMTime) {
return p.html, ""
}
md, err := os.ReadFile(p.caminho)
if err != nil {
p.errMsg = "erro ao ler README"
p.html = nil
p.ultimoMTime = time.Time{}
return nil, p.errMsg
}
var buf bytes.Buffer
if err := goldmark.Convert(md, &buf); err != nil {
p.errMsg = "erro ao renderizar README"
p.html = nil
p.ultimoMTime = time.Time{}
return nil, p.errMsg
}
// Envelopa em uma página com estilo básico.
// Importante: NÃO usamos fmt.Sprintf com o HTML/CSS diretamente,
// porque o CSS pode conter "%" (ex.: width:100%) e o fmt interpreta
// como placeholders.
page := `<!doctype html>
<html lang="pt-br">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>e-li.nps README</title>
<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:0;background:#fafafa;color:#111;}
.wrap{max-width:980px;margin:0 auto;padding:24px;}
.card{background:#fff;border:1px solid #e5e5e5;border-radius:12px;padding:22px;}
h1,h2,h3{margin-top:1.2em;}
pre{background:#0b1020;color:#e6e6e6;padding:14px;border-radius:12px;overflow:auto;}
code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;}
table{border-collapse:collapse;width:100%;}
th,td{border:1px solid #e5e5e5;padding:8px;text-align:left;}
a{color:#111;}
.muted{color:#666;font-size:12px;}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<!--CONTEUDO_README-->
<p class="muted" style="margin-top:16px;">Página gerada automaticamente a partir de README.md</p>
</div>
</div>
</body>
</html>`
html := []byte(strings.Replace(page, "<!--CONTEUDO_README-->", buf.String(), 1))
// Sanitização mínima: como o README é do próprio projeto, aceitamos o HTML gerado.
// Se quiser endurecer segurança, podemos usar um sanitizer (bluemonday).
_ = template.HTMLEscapeString
p.html = html
p.errMsg = ""
p.ultimoMTime = st.ModTime()
return p.html, ""
}