Files
memby/server/internal/api/admin_credits.go
T
2026-08-16 12:13:51 +12:00

142 lines
5.0 KiB
Go

package api
import (
"encoding/json"
"net/http"
"time"
"github.com/ponzischeme89/memby/server/internal/credits"
"github.com/ponzischeme89/memby/server/internal/store"
)
type adminCreditsSettings struct {
CandidateLimit int `json:"candidateLimit"`
PrefetchEpisodes int `json:"prefetchEpisodes"`
MaxPrefetch int `json:"maxPrefetch"`
RetryHours int `json:"retryHours"`
}
type adminCreditsCandidate struct {
ItemID string `json:"itemId"`
SeriesID string `json:"seriesId"`
Season int `json:"season"`
Episode int `json:"episode"`
Priority int `json:"priority"`
Reason string `json:"reason"`
UserCount int `json:"userCount"`
LastViewed time.Time `json:"lastViewed"`
}
type adminCreditsResponse struct {
Enabled bool `json:"enabled"`
Settings adminCreditsSettings `json:"settings"`
QueueDepth int `json:"queueDepth"`
Pending []adminCreditsCandidate `json:"pending"`
History []store.CreditsScanHistoryRow `json:"history"`
}
func creditsAdminSettings(cfg credits.Config) adminCreditsSettings {
cfg = credits.NormaliseConfig(cfg)
return adminCreditsSettings{
CandidateLimit: cfg.QueueLimit, PrefetchEpisodes: cfg.PrefetchEpisodes,
MaxPrefetch: cfg.MaxPrefetchEpisodes,
RetryHours: int(cfg.RetryCooldown / time.Hour),
}
}
func (s *Server) defaultCreditsConfig() credits.Config {
defaults := credits.DefaultConfig()
defaults.PrefetchEpisodes = s.cfg.CreditsPrefetchEpisodes
defaults.MaxPrefetchEpisodes = s.cfg.CreditsMaxPrefetch
defaults.QueueLimit = s.cfg.CreditsQueueLimit
return credits.NormaliseConfig(defaults)
}
func (s *Server) handleAdminCredits(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPut {
s.handleAdminCreditsSettings(w, r)
return
}
s.writeAdminCredits(w, r)
}
func (s *Server) writeAdminCredits(w http.ResponseWriter, r *http.Request) {
cfg := s.defaultCreditsConfig()
pending := []adminCreditsCandidate{}
queueDepth := 0
if s.credits != nil {
cfg = s.credits.Configuration()
queueDepth = s.credits.QueueDepth()
for _, candidate := range s.credits.Pending() {
pending = append(pending, adminCreditsCandidate{
ItemID: candidate.ItemID, SeriesID: candidate.SeriesID,
Season: candidate.Season, Episode: candidate.Episode,
Priority: candidate.Priority, Reason: candidate.Reason,
UserCount: candidate.UserCount, LastViewed: candidate.LastViewed,
})
}
} else if saved, found, err := s.store.CreditsSettings(r.Context()); err == nil && found {
cfg.PrefetchEpisodes = saved.PrefetchEpisodes
cfg.MaxPrefetchEpisodes = saved.MaxPrefetch
cfg.QueueLimit = saved.CandidateLimit
cfg.RetryCooldown = time.Duration(saved.RetryHours) * time.Hour
cfg = credits.NormaliseConfig(cfg)
}
history, err := s.store.CreditsScanHistory(
r.Context(), queryInt(r, "limit", 100, 500),
)
if err != nil {
s.loggerFor(r.Context()).Warn("credits scan history read failed", "error", err)
history = []store.CreditsScanHistoryRow{}
}
w.Header().Set("Cache-Control", "no-store")
writeJSON(w, http.StatusOK, adminCreditsResponse{
Enabled: s.credits != nil, Settings: creditsAdminSettings(cfg),
QueueDepth: queueDepth, Pending: pending, History: history,
})
}
func (s *Server) handleAdminCreditsSettings(w http.ResponseWriter, r *http.Request) {
var req adminCreditsSettings
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<10)).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "malformed request body")
return
}
if req.CandidateLimit < 1 || req.CandidateLimit > 100 {
writeError(w, http.StatusBadRequest, "candidate limit must be between 1 and 100")
return
}
if req.PrefetchEpisodes < 1 || req.PrefetchEpisodes > 10 {
writeError(w, http.StatusBadRequest, "ordinary look-ahead must be between 1 and 10 episodes")
return
}
if req.MaxPrefetch < req.PrefetchEpisodes || req.MaxPrefetch > 20 {
writeError(w, http.StatusBadRequest, "maximum look-ahead must be at least the ordinary look-ahead and no more than 20")
return
}
if req.RetryHours < 0 || req.RetryHours > 24*30 {
writeError(w, http.StatusBadRequest, "retry delay must be between 0 and 720 hours")
return
}
settings := store.CreditsSettings{
CandidateLimit: req.CandidateLimit, PrefetchEpisodes: req.PrefetchEpisodes,
MaxPrefetch: req.MaxPrefetch, RetryHours: req.RetryHours,
}
if err := s.store.SetCreditsSettings(r.Context(), settings); err != nil {
writeError(w, http.StatusInternalServerError, "could not save credits settings")
return
}
if s.credits != nil {
cfg := s.credits.Configuration()
cfg.QueueLimit = req.CandidateLimit
cfg.PrefetchEpisodes = req.PrefetchEpisodes
cfg.MaxPrefetchEpisodes = req.MaxPrefetch
cfg.RetryCooldown = time.Duration(req.RetryHours) * time.Hour
s.credits.Configure(cfg)
}
s.loggerFor(r.Context()).Info("credits detection settings changed",
"candidate_limit", req.CandidateLimit, "prefetch", req.PrefetchEpisodes,
"max_prefetch", req.MaxPrefetch, "retry_hours", req.RetryHours)
s.writeAdminCredits(w, r)
}