0.1.38 gateway

This commit is contained in:
ponzischeme89
2026-08-14 09:40:03 +12:00
parent abc392d30b
commit 5e2ed3d12e
2847 changed files with 1072928 additions and 3783 deletions
+48 -1
View File
@@ -22,17 +22,20 @@ import (
"sync"
"time"
"github.com/ponzischeme89/memby/server/internal/adminevents"
"github.com/ponzischeme89/memby/server/internal/appupdate"
"github.com/ponzischeme89/memby/server/internal/bazarr"
"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/foryou"
"github.com/ponzischeme89/memby/server/internal/integrations"
serverlogging "github.com/ponzischeme89/memby/server/internal/logging"
"github.com/ponzischeme89/memby/server/internal/mdblist"
"github.com/ponzischeme89/memby/server/internal/opensubtitles"
"github.com/ponzischeme89/memby/server/internal/radarr"
"github.com/ponzischeme89/memby/server/internal/recommend"
"github.com/ponzischeme89/memby/server/internal/scheduler"
"github.com/ponzischeme89/memby/server/internal/sonarr"
"github.com/ponzischeme89/memby/server/internal/store"
)
@@ -51,7 +54,14 @@ type Server struct {
syncer syncerHandle
log *slog.Logger
events *serverlogging.Buffer
sonarrMu sync.Mutex
// adminEvents is the administrative feed: the console's notification bell and every
// outgoing integration read from it. Distinct from `events` above, which is the
// structured log ring — a log line is what the gateway did, an admin event is
// something an operator would want to be told.
adminEvents *adminevents.Bus
scheduler *scheduler.Scheduler
integrations *integrations.Dispatcher
sonarrMu sync.Mutex
// sonarrSeriesMu guards the catalogue cache separately from the calendar's, so an add
// to My Shows never waits behind a launcher rebuilding the schedule row.
sonarrSeriesMu sync.Mutex
@@ -85,6 +95,9 @@ type Server struct {
// embyHealth is the reachability probe's live finding, which /v1/status publishes so
// a TV can show why playback stopped even if it missed the announcement.
embyHealth embyHealth
// The console is a separate container the gateway proxies; see admin_spa.go.
adminUIHandle
}
// Deps are the collaborators the API needs. A struct rather than positional arguments:
@@ -102,6 +115,10 @@ type Deps struct {
Syncer syncerHandle
Log *slog.Logger
Events *serverlogging.Buffer
AdminEvents *adminevents.Bus
Scheduler *scheduler.Scheduler
Integrations *integrations.Dispatcher
}
func New(cfg config.Config, deps Deps) *Server {
@@ -119,9 +136,23 @@ func New(cfg config.Config, deps Deps) *Server {
syncer: deps.Syncer,
log: deps.Log,
events: deps.Events,
adminEvents: deps.AdminEvents,
scheduler: deps.Scheduler,
integrations: deps.Integrations,
}
}
// publishAdmin reports something an operator would want to know about.
//
// Every caller treats it as fire-and-forget, which is why it returns nothing: the feed is
// a convenience over things that are already logged, and a bell that could fail a sign-in
// would be worse than no bell. A server built without a bus — every unit test in this
// package — publishes into a nil receiver, which is a no-op.
func (s *Server) publishAdmin(ctx context.Context, event adminevents.Event) {
s.adminEvents.Publish(ctx, event)
}
func (s *Server) Routes() http.Handler {
// The client API lives on its own mux so maintenance mode can gate all of it at
// once, without the gate ever touching health checks or the admin page.
@@ -225,6 +256,12 @@ func (s *Server) Routes() http.Handler {
// Radarr pushes here when an import finishes. Outside the gate on purpose: an event
// arriving during maintenance would otherwise be lost rather than delayed.
mux.HandleFunc("POST /hooks/radarr", s.handleRadarrWebhook)
// State the canonical console URL explicitly. The console and its assets live below
// /admin/, while a bare /admin is routinely typed and some reverse proxies do not
// preserve ServeMux's implicit trailing-slash redirect for a mounted subtree.
mux.HandleFunc("GET /admin", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/", http.StatusFound)
})
mux.Handle("/admin/", s.adminRoutes())
mux.HandleFunc("GET /{$}", s.handleInstallPage)
mux.HandleFunc("GET /install", s.handleInstallPage)
@@ -441,6 +478,16 @@ func (r *statusRecorder) WriteHeader(code int) {
r.ResponseWriter.WriteHeader(code)
}
// Flush preserves streaming support through the request logger. In particular, the admin
// notification feed is Server-Sent Events and correctly refuses to start unless its writer
// implements http.Flusher. Embedding ResponseWriter alone does not promote optional
// interfaces, so the old recorder turned every stream request into a 500.
func (r *statusRecorder) Flush() {
if flusher, ok := r.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
// --- sessions ---------------------------------------------------------------
func bearerToken(r *http.Request) string {