package handler import ( "html/template" "net/http" "time" "github.com/prabhupadasays/spsays-bff/internal/repository" ) // StatsHandler renders a single-page HTML snapshot of the system — // aggregate counts plus the most-recent feedback rows. Mounted at /stats // (outside /api/v1, since it returns HTML not JSON) and intentionally // public per current product call. If that ever needs to change, wrap // the route in middleware in server.go. // // It is READ-ONLY: feedback acknowledge/resolve actions live in the // authenticated admin panel, never on this public page. type StatsHandler struct { stats *repository.StatsRepo feedback *repository.FeedbackRepo } func NewStatsHandler(stats *repository.StatsRepo, feedback *repository.FeedbackRepo) *StatsHandler { return &StatsHandler{stats: stats, feedback: feedback} } func (h *StatsHandler) Render(w http.ResponseWriter, r *http.Request) { snap, err := h.stats.Snapshot(r.Context()) if err != nil { http.Error(w, "stats query failed: "+err.Error(), http.StatusInternalServerError) return } recent, err := h.feedback.Recent(r.Context(), 25) if err != nil { http.Error(w, "recent feedback query failed: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") // Caching off — this is an ops snapshot; staleness defeats the point. w.Header().Set("Cache-Control", "no-store") if err := statsTpl.Execute(w, map[string]any{ "Stats": snap, "Feedback": recent, "GeneratedAt": time.Now().Format("2006-01-02 15:04:05 MST"), }); err != nil { // Headers already flushed — best we can do is bail mid-stream. return } } var statsTpl = template.Must(template.New("stats").Funcs(template.FuncMap{ "fmtTime": func(t time.Time) string { return t.Format("2006-01-02 15:04") }, }).Parse(statsHTML)) const statsHTML = `
No feedback yet.
{{end}}No feedback yet.
{{end}} `