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) }