Pinterest-style visual bookmarking app with: - URL metadata extraction (OG/Twitter meta, oEmbed fallback) - Image caching in Postgres with 480px thumbnails - Multi-tag filtering with Ctrl/Cmd for OR mode - Fuzzy tag suggestions and inline tag editing - Browser console auth() with first-use password setup - Brutalist UI with Commit Mono font and Pico CSS - Light/dark mode via browser preference
49 lines
1.2 KiB
Go
49 lines
1.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
)
|
|
|
|
// Handler is a function that handles HTTP requests and can return an error.
|
|
// It receives a RequestContext with injected dependencies (DB, Logger).
|
|
type Handler func(rc *RequestContext, w http.ResponseWriter, r *http.Request) error
|
|
|
|
// WithErrorHandling wraps a Handler to automatically handle errors.
|
|
func WithErrorHandling(rc *RequestContext, h Handler) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
wrapper := &responseWrapper{
|
|
ResponseWriter: w,
|
|
written: false,
|
|
}
|
|
|
|
err := h(rc, wrapper, r)
|
|
if err != nil {
|
|
rc.Logger.Error("handler error",
|
|
slog.String("method", r.Method),
|
|
slog.String("path", r.URL.Path),
|
|
slog.Any("error", err),
|
|
)
|
|
|
|
if !wrapper.written {
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// responseWrapper tracks if response was written
|
|
type responseWrapper struct {
|
|
http.ResponseWriter
|
|
written bool
|
|
}
|
|
|
|
func (w *responseWrapper) Write(b []byte) (int, error) {
|
|
w.written = true
|
|
return w.ResponseWriter.Write(b)
|
|
}
|
|
|
|
func (w *responseWrapper) WriteHeader(statusCode int) {
|
|
w.written = true
|
|
w.ResponseWriter.WriteHeader(statusCode)
|
|
}
|