87 lines
3.4 KiB
Go
87 lines
3.4 KiB
Go
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,
|
|
}),
|
|
})
|
|
}
|