fix: apply code-review and insight findings across nuntius

This commit is contained in:
2026-07-26 20:40:16 +02:00
parent 6339771a24
commit 83c95dff08
8 changed files with 94 additions and 18 deletions
+7 -2
View File
@@ -198,14 +198,19 @@ func compose(from, to string, req contactform.Request, formName, formType string
case "generic":
tmpl = emailTemplateGeneric
}
_ = tmpl.Execute(&htmlBuf, map[string]string{
if err := tmpl.Execute(&htmlBuf, map[string]string{
"FormName": formName,
"Name": req.Name,
"Email": req.Email,
"Service": req.Service,
"Message": req.Message,
"Received": received,
})
}); err != nil {
// template.Must guarantees valid templates; this path is
// unreachable in normal operation.
htmlBuf.Reset()
fmt.Fprintf(&htmlBuf, "<p>Email generation error: %v</p>", err)
}
// --- Subject + plain-text body per form type ---
var subject, text string
+42 -8
View File
@@ -114,10 +114,12 @@ func (h *ContactHandler) makeHandler(path string) http.HandlerFunc {
if errs := contactform.Validate(&req, form.Type); len(errs) > 0 {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(contactform.ErrorResponse{
if err := json.NewEncoder(w).Encode(contactform.ErrorResponse{
Error: "validation",
Details: errs,
})
}); err != nil {
slog.Error("failed to encode json response", "err", err)
}
return
}
@@ -155,10 +157,12 @@ func (h *ContactHandler) makeHandler(path string) http.HandlerFunc {
// Health handles GET /health.
func (h *ContactHandler) Health(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(map[string]any{
if err := json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
"forms": len(h.forms),
})
}); err != nil {
slog.Error("failed to encode health response", "err", err)
}
}
// --- internals ---
@@ -201,10 +205,36 @@ type bucket struct {
}
func newRateLimiter(perHour int) *rateLimiter {
return &rateLimiter{
r := &rateLimiter{
perHour: perHour,
buckets: make(map[string]*bucket),
}
// Clean up entries older than twice the refill window every hour.
r.startCleanup(1*time.Hour, 2*time.Hour)
return r
}
// startCleanup launches a background goroutine that periodically removes
// expired bucket entries to prevent unbounded memory growth.
func (r *rateLimiter) startCleanup(interval, maxAge time.Duration) {
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
r.cleanup(maxAge)
}
}()
}
func (r *rateLimiter) cleanup(maxAge time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
cutoff := time.Now().Add(-maxAge)
for ip, b := range r.buckets {
if b.last.Before(cutoff) {
delete(r.buckets, ip)
}
}
}
func (r *rateLimiter) allow(ip string) bool {
@@ -239,16 +269,20 @@ func minF(a, b float64) float64 {
func respondOK(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(contactform.Response{OK: true})
if err := json.NewEncoder(w).Encode(contactform.Response{OK: true}); err != nil {
slog.Error("failed to encode ok response", "err", err)
}
}
func respondError(w http.ResponseWriter, code int, err, msg string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(contactform.ErrorResponse{
if encErr := json.NewEncoder(w).Encode(contactform.ErrorResponse{
Error: err,
Message: msg,
})
}); encErr != nil {
slog.Error("failed to encode error response", "err", encErr)
}
}
func clientIP(r *http.Request) string {