193 lines
5.7 KiB
Go
193 lines
5.7 KiB
Go
package elinps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"e-li.nps/internal/contratos"
|
|
"e-li.nps/internal/db"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
type Handlers struct {
|
|
store *Store
|
|
tpl *TemplateRenderer
|
|
}
|
|
|
|
func NewHandlers(pool *pgxpool.Pool) *Handlers {
|
|
return &Handlers{
|
|
store: NewStore(pool),
|
|
tpl: NewTemplateRenderer(mustParseTemplates()),
|
|
}
|
|
}
|
|
|
|
func (h *Handlers) PostPedido(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var in contratos.PedidoInput
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "json_invalido"})
|
|
return
|
|
}
|
|
if err := ValidatePedidoInput(&in); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Garante a tabela do produto (e normaliza o identificador técnico).
|
|
table, err := h.store.EnsureTableForProduto(ctx, in.ProdutoNome)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "produto_invalido"})
|
|
return
|
|
}
|
|
|
|
// Mantemos a forma normalizada para o widget montar URLs com segurança.
|
|
// table = "nps_" + produto_normalizado
|
|
produtoNormalizado := strings.TrimPrefix(table, "nps_")
|
|
|
|
// Regras.
|
|
respRecente, err := h.store.HasRespostaValidaRecente(ctx, table, in.InquilinoCodigo, in.UsuarioCodigo)
|
|
if err != nil {
|
|
slog.Error("erro ao checar resposta recente", "err", err)
|
|
// Fail-closed.
|
|
writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
|
|
return
|
|
}
|
|
if respRecente {
|
|
writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "resposta_recente"})
|
|
return
|
|
}
|
|
|
|
pedidoAberto, err := h.store.HasPedidoEmAbertoRecente(ctx, table, in.InquilinoCodigo, in.UsuarioCodigo)
|
|
if err != nil {
|
|
slog.Error("erro ao checar pedido em aberto", "err", err)
|
|
writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
|
|
return
|
|
}
|
|
if pedidoAberto {
|
|
writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "pedido_em_aberto"})
|
|
return
|
|
}
|
|
|
|
id, err := h.store.CreatePedido(ctx, table, in, r)
|
|
if err != nil {
|
|
slog.Error("erro ao criar pedido", "err", err)
|
|
writeJSON(w, http.StatusOK, contratos.PedidoResponse{PodeAbrir: false, Motivo: "erro"})
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"pode_abrir": true, "id": id, "produto": produtoNormalizado})
|
|
}
|
|
|
|
func (h *Handlers) PatchResposta(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
produtoParam := chi.URLParam(r, "produto")
|
|
id := chi.URLParam(r, "id")
|
|
|
|
// produtoParam já está no path; sanitizamos novamente por segurança.
|
|
prod, err := db.NormalizeProduto(produtoParam)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "produto_invalido"})
|
|
return
|
|
}
|
|
table := db.TableNameForProduto(prod)
|
|
if err := db.EnsureNPSTable(ctx, h.store.pool, table); err != nil {
|
|
slog.Error("erro ao garantir tabela", "err", err)
|
|
writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "db"})
|
|
return
|
|
}
|
|
|
|
var in contratos.PatchInput
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "json_invalido"})
|
|
return
|
|
}
|
|
if err := ValidatePatchInput(&in); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if in.Nota == nil && in.Justificativa == nil && !in.Finalizar {
|
|
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "nada_para_atualizar"})
|
|
return
|
|
}
|
|
|
|
if err := h.store.PatchRegistro(ctx, table, id, in); err != nil {
|
|
slog.Error("erro ao atualizar registro", "err", err)
|
|
writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "db"})
|
|
return
|
|
}
|
|
|
|
// Se chamado via HTMX, respondemos com fragmento HTML atualizado.
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
reg, err := h.store.GetRegistro(ctx, table, id)
|
|
if err != nil {
|
|
slog.Error("erro ao buscar registro", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("db"))
|
|
return
|
|
}
|
|
data := contratos.FormPageData{Produto: prod, ID: id, Reg: reg}
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
h.tpl.Render(w, "form_inner.html", data)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func (h *Handlers) GetForm(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
produtoParam := chi.URLParam(r, "produto")
|
|
id := chi.URLParam(r, "id")
|
|
|
|
prod, err := db.NormalizeProduto(produtoParam)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte("produto invalido"))
|
|
return
|
|
}
|
|
table := db.TableNameForProduto(prod)
|
|
if err := db.EnsureNPSTable(ctx, h.store.pool, table); err != nil {
|
|
slog.Error("erro ao garantir tabela", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("db"))
|
|
return
|
|
}
|
|
|
|
reg, err := h.store.GetRegistro(ctx, table, id)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte("nao encontrado"))
|
|
return
|
|
}
|
|
slog.Error("erro ao buscar registro", "err", err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("db"))
|
|
return
|
|
}
|
|
|
|
data := contratos.FormPageData{
|
|
Produto: prod,
|
|
ID: id,
|
|
Reg: reg,
|
|
}
|
|
|
|
// Sempre retornamos uma página HTML completa para o widget usar iframe.
|
|
// Porém o container interno também é compatível com HTMX (swap de si mesmo).
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
h.tpl.Render(w, "form_page.html", data)
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(v)
|
|
}
|