0.1.52 Gateway

This commit is contained in:
ponzischeme89
2026-08-17 19:09:17 +12:00
parent d5a8d3ad88
commit 1da91e40a1
46 changed files with 1626 additions and 106 deletions
+86
View File
@@ -0,0 +1,86 @@
package api
import (
"context"
"fmt"
"net/url"
"time"
"github.com/ponzischeme89/memby/server/internal/adminevents"
"github.com/ponzischeme89/memby/server/internal/store"
)
// noteDailyFirstUse announces the first time a television opened Memby on a given day.
//
// It is called from the home request rather than from the auth middleware, which every
// call passes through, because "opened Memby" means the launcher composing. A set left on
// overnight polls /v1/status every ten seconds, so anchoring on any authenticated request
// would announce that set at midnight — an event nobody did, timed to the hour nobody is
// reading the feed.
//
// It is called before the cached response is served, deliberately: a household whose home
// row cache is still warm from the last set to switch on has still just been opened by
// this one, and the mark is a single indexed insert either way.
//
// Everything about it is best-effort. It cannot fail a launcher.
func (s *Server) noteDailyFirstUse(ctx context.Context, sess store.Session) {
if s.store == nil || sess.DeviceID == "" {
return
}
first, err := s.store.MarkDeviceDay(
ctx, sess.DeviceID, sess.EmbyUserID, time.Now().In(s.sonarrLocation()),
)
if err != nil {
s.loggerFor(ctx).Warn("device day mark failed",
"device_id", sess.DeviceID, "error", err)
return
}
if !first {
return
}
s.loggerFor(ctx).Info("first use today",
"device_id", sess.DeviceID, "client", clientLogValue(sess.ClientVersion))
s.publishAdmin(ctx, adminevents.Event{
Type: adminevents.TypeDeviceFirstUse,
Title: "Opened Memby",
Summary: fmt.Sprintf("%s opened Memby on %s for the first time today",
displayName(sess.Username), displayName(sess.DeviceName)),
Actor: sess.Username, Target: sess.DeviceName,
Link: "/admin/devices/" + url.PathEscape(sess.DeviceID),
Metadata: adminevents.Meta(map[string]any{
"deviceId": sess.DeviceID, "userId": sess.EmbyUserID,
"version": sess.ClientVersion,
}),
})
}
// announceDeviceUpdate reports a television that has finished updating itself.
//
// A set that updates in place never signs in again, so the first request carrying the new
// build is the only evidence there is that the update worked — and an update that was
// offered and never taken looks exactly like one that was, until this arrives.
//
// `from` being empty is not an update: it is a television this gateway had never been
// told the build of, which is an old APK or a session predating identity capture, and
// announcing it as an upgrade would claim a version moved when nothing is known to have
// moved. A version that went *backwards* is still announced, because a sideloaded
// downgrade is news an operator wants at least as much as an upgrade.
func (s *Server) announceDeviceUpdate(ctx context.Context, sess store.Session, from string) {
if from == "" || sess.ClientVersion == "" || from == sess.ClientVersion {
return
}
s.loggerFor(ctx).Info("client build changed",
"device_id", sess.DeviceID, "from", from, "to", sess.ClientVersion)
s.publishAdmin(ctx, adminevents.Event{
Type: adminevents.TypeDeviceUpdated,
Title: "App updated",
Summary: fmt.Sprintf("%s updated from %s to %s",
displayName(sess.DeviceName), from, sess.ClientVersion),
Actor: sess.Username, Target: sess.DeviceName,
Link: "/admin/devices/" + url.PathEscape(sess.DeviceID),
Metadata: adminevents.Meta(map[string]any{
"deviceId": sess.DeviceID, "userId": sess.EmbyUserID,
"from": from, "to": sess.ClientVersion,
}),
})
}