All checks were successful
check / check (push) Successful in 1m48s
Per review on PR #48: pre-1.0 there is no installed base to keep compiling against, so remove the dead parameter in one pass instead of deferring. NewManager now takes only the signing key. Update the handlers.go call site and its stale comment, mechanically update the NewManager call shapes in the session tests (assertions unchanged), collapse the now-meaningless constructor-argument loop in the cookie attributes test, and drop the moot P1 Future Step from TODO.md. Reviewer-directed test call-site updates; cookie behavior is unchanged from the previous commit (always Secure/HttpOnly/SameSite=Strict).
135 lines
3.2 KiB
Go
135 lines
3.2 KiB
Go
// 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/config"
|
|
"sneak.berlin/go/pixa/internal/database"
|
|
"sneak.berlin/go/pixa/internal/encurl"
|
|
"sneak.berlin/go/pixa/internal/healthcheck"
|
|
"sneak.berlin/go/pixa/internal/httpfetcher"
|
|
"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
|
|
Database *database.Database
|
|
Config *config.Config
|
|
}
|
|
|
|
// Handlers provides HTTP request handlers.
|
|
type Handlers struct {
|
|
log *slog.Logger
|
|
hc *healthcheck.Healthcheck
|
|
db *database.Database
|
|
config *config.Config
|
|
imgSvc *imgcache.Service
|
|
imgCache *imgcache.Cache
|
|
sessMgr *session.Manager
|
|
encGen *encurl.Generator
|
|
}
|
|
|
|
// New creates a new Handlers instance.
|
|
func New(lc fx.Lifecycle, params Params) (*Handlers, error) {
|
|
s := &Handlers{
|
|
log: params.Logger.Get(),
|
|
hc: params.Healthcheck,
|
|
db: params.Database,
|
|
config: params.Config,
|
|
}
|
|
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(_ context.Context) error {
|
|
return s.initImageService()
|
|
},
|
|
})
|
|
|
|
return s, nil
|
|
}
|
|
|
|
// initImageService initializes the image cache and service.
|
|
func (s *Handlers) initImageService() error {
|
|
// Create the cache
|
|
cache, err := imgcache.NewCache(s.db.DB(), imgcache.CacheConfig{
|
|
StateDir: s.config.StateDir,
|
|
CacheTTL: imgcache.DefaultCacheTTL,
|
|
NegativeTTL: imgcache.DefaultNegativeTTL,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.imgCache = cache
|
|
|
|
// Create the fetcher config
|
|
fetcherCfg := httpfetcher.DefaultConfig()
|
|
fetcherCfg.AllowHTTP = s.config.AllowHTTP
|
|
if s.config.UpstreamConnectionsPerHost > 0 {
|
|
fetcherCfg.MaxConnectionsPerHost = s.config.UpstreamConnectionsPerHost
|
|
}
|
|
|
|
// Create the service
|
|
svc, err := imgcache.NewService(&imgcache.ServiceConfig{
|
|
Cache: cache,
|
|
FetcherConfig: fetcherCfg,
|
|
SigningKey: s.config.SigningKey,
|
|
Whitelist: s.config.WhitelistHosts,
|
|
Logger: s.log,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.imgSvc = svc
|
|
s.log.Info("image service initialized")
|
|
|
|
// Initialize session manager (signing key is validated at config load
|
|
// time). Session cookies are always Secure/HttpOnly/SameSite=Strict.
|
|
sessMgr, err := session.NewManager(s.config.SigningKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.sessMgr = sessMgr
|
|
|
|
// Initialize encrypted URL generator
|
|
encGen, err := encurl.NewGenerator(s.config.SigningKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.encGen = encGen
|
|
|
|
s.log.Info("session manager and URL generator initialized")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Handlers) respondJSON(w http.ResponseWriter, data interface{}, 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]interface{}{
|
|
"error": message,
|
|
"status": status,
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, status)
|
|
}
|