feat(web): add logging middleware and refactor handlers

Introduced LoggerMiddleware for HTTP request logging. Refactored handler
methods to return http.HandlerFunc for improved composability. Updated
route registration and tests to use new handler signatures.
This commit is contained in:
Ruidy 2025-09-28 19:43:30 +02:00
parent d112a4f6e8
commit bf721dc130
No known key found for this signature in database
GPG key ID: 705C24D202990805
6 changed files with 77 additions and 47 deletions

View file

@ -9,7 +9,8 @@ import (
"github.com/rjNemo/payit/internal/payments" "github.com/rjNemo/payit/internal/payments"
) )
func (h *Handler) createCheckoutSession(w http.ResponseWriter, r *http.Request) { func (h *Handler) createCheckoutSession() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req payments.CheckoutSessionRequest var req payments.CheckoutSessionRequest
if r.Body != nil { if r.Body != nil {
@ -44,3 +45,4 @@ func (h *Handler) createCheckoutSession(w http.ResponseWriter, r *http.Request)
return return
} }
} }
}

View file

@ -37,7 +37,7 @@ func TestCreateCheckoutSessionSuccess(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/checkout", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/checkout", bytes.NewReader(body))
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
handler.createCheckoutSession(rec, req) handler.createCheckoutSession()(rec, req)
if rec.Code != http.StatusOK { if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code) t.Fatalf("expected status 200, got %d", rec.Code)
@ -70,7 +70,7 @@ func TestCreateCheckoutSessionDefaultsQuantity(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/checkout", http.NoBody) req := httptest.NewRequest(http.MethodPost, "/api/checkout", http.NoBody)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
handler.createCheckoutSession(rec, req) handler.createCheckoutSession()(rec, req)
if rec.Code != http.StatusOK { if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code) t.Fatalf("expected status 200, got %d", rec.Code)
@ -86,7 +86,7 @@ func TestCreateCheckoutSessionRejectsInvalidJSON(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/checkout", bytes.NewBufferString("{")) req := httptest.NewRequest(http.MethodPost, "/api/checkout", bytes.NewBufferString("{"))
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
handler.createCheckoutSession(rec, req) handler.createCheckoutSession()(rec, req)
if rec.Code != http.StatusBadRequest { if rec.Code != http.StatusBadRequest {
t.Fatalf("expected status 400, got %d", rec.Code) t.Fatalf("expected status 400, got %d", rec.Code)
@ -101,7 +101,7 @@ func TestCreateCheckoutSessionStripeFailure(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/checkout", http.NoBody) req := httptest.NewRequest(http.MethodPost, "/api/checkout", http.NoBody)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
handler.createCheckoutSession(rec, req) handler.createCheckoutSession()(rec, req)
if rec.Code != http.StatusInternalServerError { if rec.Code != http.StatusInternalServerError {
t.Fatalf("expected status 500, got %d", rec.Code) t.Fatalf("expected status 500, got %d", rec.Code)
@ -111,7 +111,7 @@ func TestCreateCheckoutSessionStripeFailure(t *testing.T) {
func TestCreateCheckoutSessionMethodNotAllowed(t *testing.T) { func TestCreateCheckoutSessionMethodNotAllowed(t *testing.T) {
handler := &Handler{checkout: &fakeCheckoutService{}} handler := &Handler{checkout: &fakeCheckoutService{}}
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("POST /api/checkout", handler.createCheckoutSession) mux.HandleFunc("POST /api/checkout", handler.createCheckoutSession())
req := httptest.NewRequest(http.MethodGet, "/api/checkout", http.NoBody) req := httptest.NewRequest(http.MethodGet, "/api/checkout", http.NoBody)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()

View file

@ -0,0 +1,26 @@
package web
import (
"log"
"net/http"
"time"
)
type WrappedWriter struct {
http.ResponseWriter
StatusCode int
}
func (w *WrappedWriter) WriteHeader(statusCode int) {
w.StatusCode = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &WrappedWriter{ResponseWriter: w, StatusCode: http.StatusOK}
next.ServeHTTP(wrapped, r)
log.Printf("%s %s %d %v", r.Method, r.URL.Path, wrapped.StatusCode, time.Since(start))
})
}

View file

@ -13,7 +13,8 @@ type checkoutPageData struct {
Currency string Currency string
} }
func (h *Handler) renderCheckoutPage(w http.ResponseWriter, r *http.Request) { func (h *Handler) renderCheckoutPage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
price := float64(h.cfg.Product.PriceCents) / 100 price := float64(h.cfg.Product.PriceCents) / 100
data := checkoutPageData{ data := checkoutPageData{
ProductName: h.cfg.Product.Name, ProductName: h.cfg.Product.Name,
@ -28,3 +29,4 @@ func (h *Handler) renderCheckoutPage(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
}

View file

@ -5,7 +5,7 @@ import (
) )
func (h *Handler) registerRoutes(mux *http.ServeMux) { func (h *Handler) registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("POST /api/checkout", h.createCheckoutSession) mux.Handle("POST /api/checkout", h.createCheckoutSession())
mux.Handle("GET /", http.HandlerFunc(h.renderCheckoutPage)) mux.Handle("GET /", h.renderCheckoutPage())
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(h.fs)))) mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(h.fs))))
} }

View file

@ -41,5 +41,5 @@ func NewServer(cfg config.Config) http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
h.registerRoutes(mux) h.registerRoutes(mux)
return mux return LoggerMiddleware(mux)
} }