Server changes/Sonarr
This commit is contained in:
@@ -18,12 +18,14 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/cache"
|
||||
"github.com/ponzischeme89/memby/server/internal/config"
|
||||
"github.com/ponzischeme89/memby/server/internal/emby"
|
||||
"github.com/ponzischeme89/memby/server/internal/recommend"
|
||||
"github.com/ponzischeme89/memby/server/internal/sonarr"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -33,8 +35,10 @@ type Server struct {
|
||||
store *store.Store
|
||||
cache *cache.Cache
|
||||
recommender *recommend.Engine
|
||||
sonarr *sonarr.Client
|
||||
syncer syncerHandle
|
||||
log *slog.Logger
|
||||
sonarrMu sync.Mutex
|
||||
|
||||
recommendationBuilds recommendationBuilds
|
||||
maintenance maintenanceState
|
||||
@@ -48,17 +52,22 @@ type Deps struct {
|
||||
Store *store.Store
|
||||
Cache *cache.Cache
|
||||
Recommender *recommend.Engine
|
||||
Sonarr *sonarr.Client
|
||||
Syncer syncerHandle
|
||||
Log *slog.Logger
|
||||
}
|
||||
|
||||
func New(cfg config.Config, deps Deps) *Server {
|
||||
if cfg.MaxClientsPerUser < 1 {
|
||||
cfg.MaxClientsPerUser = 1
|
||||
}
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
emby: deps.Emby,
|
||||
store: deps.Store,
|
||||
cache: deps.Cache,
|
||||
recommender: deps.Recommender,
|
||||
sonarr: deps.Sonarr,
|
||||
syncer: deps.Syncer,
|
||||
log: deps.Log,
|
||||
}
|
||||
@@ -70,6 +79,7 @@ func (s *Server) Routes() http.Handler {
|
||||
v1 := http.NewServeMux()
|
||||
|
||||
v1.HandleFunc("POST /v1/auth/login", s.handleLogin)
|
||||
v1.HandleFunc("GET /v1/auth/policy", s.handleAuthPolicy)
|
||||
v1.Handle("POST /v1/auth/logout", s.authed(s.handleLogout))
|
||||
v1.Handle("GET /v1/auth/session", s.authed(s.handleSession))
|
||||
|
||||
@@ -93,8 +103,12 @@ func (s *Server) Routes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", s.handleHealth)
|
||||
mux.HandleFunc("GET /readyz", s.handleReady)
|
||||
// Exact route outside the maintenance gate: signed-in clients poll this lightweight
|
||||
// status even while every normal /v1 operation is deliberately unavailable.
|
||||
mux.Handle("GET /v1/status", s.authed(s.handleServiceStatus))
|
||||
mux.Handle("/v1/", s.maintenanceGate(v1))
|
||||
mux.Handle("/admin/", s.adminRoutes())
|
||||
mux.HandleFunc("GET /updates/{filename}", s.handleReleaseDownload)
|
||||
|
||||
return s.withLogging(mux)
|
||||
}
|
||||
@@ -134,15 +148,33 @@ func (s *Server) withLogging(next http.Handler) http.Handler {
|
||||
rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
||||
next.ServeHTTP(rec, r)
|
||||
// Path only: query strings can carry image tokens.
|
||||
s.log.Info("request",
|
||||
level := requestLogLevel(r.URL.Path, rec.status)
|
||||
s.log.Log(r.Context(), level, "HTTP request",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"status", rec.status,
|
||||
"ms", time.Since(start).Milliseconds(),
|
||||
"duration", time.Since(start).Round(time.Millisecond),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Successful high-frequency probes and artwork fetches stay available at DEBUG without
|
||||
// overwhelming the normal Docker log. Failures are always promoted so they remain
|
||||
// visible regardless of path.
|
||||
func requestLogLevel(path string, status int) slog.Level {
|
||||
switch {
|
||||
case status >= http.StatusInternalServerError:
|
||||
return slog.LevelError
|
||||
case status >= http.StatusBadRequest:
|
||||
return slog.LevelWarn
|
||||
case path == "/healthz", path == "/readyz", path == "/v1/status",
|
||||
strings.HasPrefix(path, "/v1/images/"):
|
||||
return slog.LevelDebug
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
type statusRecorder struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
@@ -184,6 +216,7 @@ type cachedSession struct {
|
||||
Username string `json:"n"`
|
||||
ServerID string `json:"s"`
|
||||
DeviceID string `json:"d"`
|
||||
DeviceName string `json:"dn,omitempty"`
|
||||
}
|
||||
|
||||
// sessionFor resolves a token, using Redis to keep the hot path off Postgres.
|
||||
@@ -201,6 +234,7 @@ func (s *Server) sessionFor(ctx context.Context, token string) (store.Session, e
|
||||
Username: cs.Username,
|
||||
ServerID: cs.ServerID,
|
||||
DeviceID: cs.DeviceID,
|
||||
DeviceName: cs.DeviceName,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -220,6 +254,7 @@ func (s *Server) sessionFor(ctx context.Context, token string) (store.Session, e
|
||||
Username: sess.Username,
|
||||
ServerID: sess.ServerID,
|
||||
DeviceID: sess.DeviceID,
|
||||
DeviceName: sess.DeviceName,
|
||||
}); err == nil {
|
||||
_ = s.cache.Set(ctx, key, raw, s.cfg.SessionTTL)
|
||||
}
|
||||
@@ -231,7 +266,10 @@ func (s *Server) sessionFor(ctx context.Context, token string) (store.Session, e
|
||||
}
|
||||
|
||||
func credentials(sess store.Session) emby.Credentials {
|
||||
return emby.Credentials{UserID: sess.EmbyUserID, Token: sess.EmbyToken, DeviceID: sess.DeviceID}
|
||||
return emby.Credentials{
|
||||
UserID: sess.EmbyUserID, Token: sess.EmbyToken,
|
||||
DeviceID: sess.DeviceID, DeviceName: sess.DeviceName,
|
||||
}
|
||||
}
|
||||
|
||||
// --- responses --------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user