App v0.2.26 and gateway 0.1.20

Client: seek controls, Bazarr subtitle download and cast panel in the
player; MDBList ratings strip; episode and schedule detail pages; series
pace estimate; what's new panel; install-permission onboarding step;
synced per-profile preferences; Emby outage banner.

Gateway: rebuilt admin console (one fragment per page), preference
history and restore, merged Continue Watching, Emby health probe,
subtitle selection and Bazarr download, structured request logging with
per-request identity, and embedded build version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ponzischeme89
2026-08-06 22:33:56 +12:00
co-authored by Claude Opus 5
parent 2675e6d82b
commit 4a4df7a73c
257 changed files with 24868 additions and 3108 deletions
+14 -11
View File
@@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"errors"
"net/http"
@@ -34,7 +35,7 @@ func (s *Server) requestAllowed(r *http.Request, sess store.Session) bool {
}
policy, err := s.store.RequestPolicy(r.Context())
if err != nil {
s.log.Error("request policy read failed", "error", err)
s.loggerFor(r.Context()).Error("request policy read failed", "error", err)
return false
}
return policy.Allows(sess.EmbyUserID)
@@ -60,7 +61,7 @@ func (s *Server) handleRequestLookup(w http.ResponseWriter, r *http.Request, ses
defer wg.Done()
movies, err := s.radarr.Lookup(r.Context(), term)
if err != nil {
s.log.Warn("Radarr request lookup failed", "error", err)
s.loggerFor(r.Context()).Warn("Radarr request lookup failed", "error", err)
return
}
for _, movie := range movies {
@@ -81,7 +82,7 @@ func (s *Server) handleRequestLookup(w http.ResponseWriter, r *http.Request, ses
defer wg.Done()
series, err := s.sonarr.Lookup(r.Context(), term)
if err != nil {
s.log.Warn("Sonarr request lookup failed", "error", err)
s.loggerFor(r.Context()).Warn("Sonarr request lookup failed", "error", err)
return
}
for _, show := range series {
@@ -171,7 +172,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, sess stor
}
movies, err := s.radarr.Lookup(r.Context(), "tmdb:"+strconv.Itoa(req.ForeignID))
if err != nil {
s.writeRequestUpstreamError(w, err, "Radarr lookup failed")
s.writeRequestUpstreamError(r.Context(), w, err, "Radarr lookup failed")
return
}
for _, movie := range movies {
@@ -184,10 +185,10 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, sess stor
}
added, err := s.radarr.AddUnmonitored(r.Context(), movie)
if err != nil {
s.writeRequestUpstreamError(w, err, "could not add movie to Radarr")
s.writeRequestUpstreamError(r.Context(), w, err, "could not add movie to Radarr")
return
}
s.log.Info("media requested", "user", sess.Username, "type", "movie", "title", added.Title)
s.loggerFor(r.Context()).Info("media requested", "user", sess.Username, "type", "movie", "title", added.Title)
writeJSON(w, http.StatusCreated, map[string]any{"status": "requested", "title": added.Title})
return
}
@@ -198,7 +199,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, sess stor
}
series, err := s.sonarr.Lookup(r.Context(), "tvdb:"+strconv.Itoa(req.ForeignID))
if err != nil {
s.writeRequestUpstreamError(w, err, "Sonarr lookup failed")
s.writeRequestUpstreamError(r.Context(), w, err, "Sonarr lookup failed")
return
}
for _, show := range series {
@@ -211,10 +212,10 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, sess stor
}
added, err := s.sonarr.AddUnmonitored(r.Context(), show)
if err != nil {
s.writeRequestUpstreamError(w, err, "could not add series to Sonarr")
s.writeRequestUpstreamError(r.Context(), w, err, "could not add series to Sonarr")
return
}
s.log.Info("media requested", "user", sess.Username, "type", "series", "title", added.Title)
s.loggerFor(r.Context()).Info("media requested", "user", sess.Username, "type", "series", "title", added.Title)
writeJSON(w, http.StatusCreated, map[string]any{"status": "requested", "title": added.Title})
return
}
@@ -225,7 +226,9 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request, sess stor
writeError(w, http.StatusNotFound, "title was not found")
}
func (s *Server) writeRequestUpstreamError(w http.ResponseWriter, err error, message string) {
func (s *Server) writeRequestUpstreamError(
ctx context.Context, w http.ResponseWriter, err error, message string,
) {
var radarrErr *radarr.APIError
var sonarrErr *sonarr.APIError
if (errors.As(err, &radarrErr) && radarrErr.StatusCode == http.StatusBadRequest) ||
@@ -233,6 +236,6 @@ func (s *Server) writeRequestUpstreamError(w http.ResponseWriter, err error, mes
writeError(w, http.StatusConflict, "the title could not be added; it may already exist")
return
}
s.log.Error(message, "error", err)
s.loggerFor(ctx).Error(message, "error", err)
writeError(w, http.StatusBadGateway, message)
}