feat(api): display_state im Player-Status-Report persistieren

This commit is contained in:
Jesko Anschütz 2026-03-26 23:05:25 +01:00
parent fbcda1e2b8
commit f985a99ea1
2 changed files with 23 additions and 13 deletions

View file

@ -2,12 +2,15 @@ package httpapi
import ( import (
"errors" "errors"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
storePackage "git.az-it.net/az/morz-infoboard/server/backend/internal/store"
) )
type screenStatusSummary struct { type screenStatusSummary struct {
@ -47,6 +50,7 @@ type playerStatusRequest struct {
HeartbeatEverySeconds int `json:"heartbeat_every_seconds"` HeartbeatEverySeconds int `json:"heartbeat_every_seconds"`
StartedAt string `json:"started_at"` StartedAt string `json:"started_at"`
LastHeartbeatAt string `json:"last_heartbeat_at"` LastHeartbeatAt string `json:"last_heartbeat_at"`
DisplayState string `json:"display_state,omitempty"`
} }
// playerStatusMQTTConfig is the MQTT configuration returned to agents in the // playerStatusMQTTConfig is the MQTT configuration returned to agents in the
@ -63,7 +67,7 @@ type playerStatusResponse struct {
MQTT *playerStatusMQTTConfig `json:"mqtt,omitempty"` MQTT *playerStatusMQTTConfig `json:"mqtt,omitempty"`
} }
func handlePlayerStatus(store playerStatusStore, mqttBroker, mqttUsername, mqttPassword string) http.HandlerFunc { func handlePlayerStatus(store playerStatusStore, screenStore *storePackage.ScreenStore, mqttBroker, mqttUsername, mqttPassword string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var request playerStatusRequest var request playerStatusRequest
if err := decodeJSON(r, &request); err != nil { if err := decodeJSON(r, &request); err != nil {
@ -130,6 +134,12 @@ func handlePlayerStatus(store playerStatusStore, mqttBroker, mqttUsername, mqttP
LastHeartbeatAt: request.LastHeartbeatAt, LastHeartbeatAt: request.LastHeartbeatAt,
}) })
if request.DisplayState != "" && screenStore != nil {
if err := screenStore.UpsertDisplayState(r.Context(), request.ScreenID, request.DisplayState); err != nil {
slog.Error("upsert display state", "screen_id", request.ScreenID, "err", err)
}
}
resp := playerStatusResponse{Status: "accepted"} resp := playerStatusResponse{Status: "accepted"}
if mqttBroker != "" { if mqttBroker != "" {
resp.MQTT = &playerStatusMQTTConfig{ resp.MQTT = &playerStatusMQTTConfig{

View file

@ -26,7 +26,7 @@ func TestHandlePlayerStatusAccepted(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(store, "", "", "")(w, req) handlePlayerStatus(store, nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusOK; got != want { if got, want := w.Code, http.StatusOK; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -66,7 +66,7 @@ func TestHandlePlayerStatusRejectsInvalidJSON(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewBufferString("{")) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewBufferString("{"))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -83,7 +83,7 @@ func TestHandlePlayerStatusRejectsMissingScreenID(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -102,7 +102,7 @@ func TestHandlePlayerStatusStoresNormalizedScreenID(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(store, "", "", "")(w, req) handlePlayerStatus(store, nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusOK; got != want { if got, want := w.Code, http.StatusOK; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -122,7 +122,7 @@ func TestHandlePlayerStatusRejectsMissingTimestamp(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -138,7 +138,7 @@ func TestHandlePlayerStatusRejectsMissingStatus(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -155,7 +155,7 @@ func TestHandlePlayerStatusRejectsUnknownStatus(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -173,7 +173,7 @@ func TestHandlePlayerStatusRejectsUnknownServerConnectivity(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -191,7 +191,7 @@ func TestHandlePlayerStatusRejectsNonPositiveHeartbeatInterval(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -208,7 +208,7 @@ func TestHandlePlayerStatusRejectsMalformedTimestamps(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -226,7 +226,7 @@ func TestHandlePlayerStatusRejectsMalformedStartedAt(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)
@ -244,7 +244,7 @@ func TestHandlePlayerStatusRejectsMalformedLastHeartbeatAt(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body))
w := httptest.NewRecorder() w := httptest.NewRecorder()
handlePlayerStatus(newInMemoryPlayerStatusStore(), "", "", "")(w, req) handlePlayerStatus(newInMemoryPlayerStatusStore(), nil, "", "", "")(w, req)
if got, want := w.Code, http.StatusBadRequest; got != want { if got, want := w.Code, http.StatusBadRequest; got != want {
t.Fatalf("status = %d, want %d", got, want) t.Fatalf("status = %d, want %d", got, want)