142 lines
5.0 KiB
Go
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)
|
||
|
|
}
|