// Package handlers provides HTTP request handlers. package handlers import ( "context" "encoding/json" "log/slog" "net/http" "time" "go.uber.org/fx" "sneak.berlin/go/pixa/internal/allowlist" "sneak.berlin/go/pixa/internal/config" "sneak.berlin/go/pixa/internal/encurl" "sneak.berlin/go/pixa/internal/healthcheck" "sneak.berlin/go/pixa/internal/imgcache" "sneak.berlin/go/pixa/internal/logger" "sneak.berlin/go/pixa/internal/session" ) // Params defines dependencies for Handlers. type Params struct { fx.In Logger *logger.Logger Healthcheck *healthcheck.Healthcheck Config *config.Config ImageService *imgcache.Service SessionManager *session.Manager EncryptedURLGenerator *encurl.Generator } // Handlers provides HTTP request handlers. type Handlers struct { log *slog.Logger hc *healthcheck.Healthcheck config *config.Config imgSvc *imgcache.Service sessMgr *session.Manager encGen *encurl.Generator csrfProtect func(http.Handler) http.Handler // refererBlocklist matches the hosts of referer_blocklist; its IsAllowed // reports whether a URL's host is on that list. refererBlocklist *allowlist.HostAllowList } // New creates a new Handlers instance. func New(_ fx.Lifecycle, params Params) (*Handlers, error) { csrfProtect, err := newCSRFProtect(params.Config.SigningKey, params.Config.Debug) if err != nil { return nil, err } return &Handlers{ log: params.Logger.Get(), hc: params.Healthcheck, config: params.Config, imgSvc: params.ImageService, sessMgr: params.SessionManager, encGen: params.EncryptedURLGenerator, csrfProtect: csrfProtect, refererBlocklist: allowlist.New(params.Config.RefererBlocklist), }, nil } // WaitForProcessing waits until no image is being processed, or until ctx // ends, and returns how many images were still being processed then. func (s *Handlers) WaitForProcessing(ctx context.Context) int { return s.imgSvc.WaitForProcessing(ctx) } func (s *Handlers) respondJSON(w http.ResponseWriter, data any, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if data != nil { err := json.NewEncoder(w).Encode(data) if err != nil { s.log.Error("json encode error", "error", err) } } } func (s *Handlers) respondError(w http.ResponseWriter, message string, status int) { s.respondJSON(w, map[string]any{ "error": message, "status": status, "timestamp": time.Now().UTC().Format(time.RFC3339), }, status) }