feat(api): POST /api/v1/screens/{slug}/schedule + Scheduler verdrahtet
ScheduleStore in RouterDeps, HandleUpdateSchedule-Handler, Scheduler-Goroutine in app.Run(), ScreenStore.GetByID hinzugefügt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9b766f9086
commit
83af005fad
3 changed files with 81 additions and 20 deletions
|
|
@ -17,16 +17,19 @@ import (
|
||||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/db"
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/db"
|
||||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi"
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi"
|
||||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/mqttnotifier"
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/scheduler"
|
||||||
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
server *http.Server
|
server *http.Server
|
||||||
notifier *mqttnotifier.Notifier
|
notifier *mqttnotifier.Notifier
|
||||||
authStore *store.AuthStore
|
authStore *store.AuthStore
|
||||||
dbPool *db.Pool // V7: für db.Close() im Shutdown
|
scheduleStore *store.ScreenScheduleStore
|
||||||
logger *log.Logger
|
screenStore *store.ScreenStore
|
||||||
|
dbPool *db.Pool // V7: für db.Close() im Shutdown
|
||||||
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*App, error) {
|
func New() (*App, error) {
|
||||||
|
|
@ -58,6 +61,7 @@ func New() (*App, error) {
|
||||||
media := store.NewMediaStore(pool.Pool)
|
media := store.NewMediaStore(pool.Pool)
|
||||||
playlists := store.NewPlaylistStore(pool.Pool)
|
playlists := store.NewPlaylistStore(pool.Pool)
|
||||||
authStore := store.NewAuthStore(pool.Pool)
|
authStore := store.NewAuthStore(pool.Pool)
|
||||||
|
schedules := store.NewScreenScheduleStore(pool.Pool)
|
||||||
|
|
||||||
// Ensure admin user exists — generate a random password if none is configured.
|
// Ensure admin user exists — generate a random password if none is configured.
|
||||||
adminPassword := cfg.AdminPassword
|
adminPassword := cfg.AdminPassword
|
||||||
|
|
@ -96,18 +100,21 @@ func New() (*App, error) {
|
||||||
AuthStore: authStore,
|
AuthStore: authStore,
|
||||||
Notifier: notifier,
|
Notifier: notifier,
|
||||||
ScreenshotStore: ss,
|
ScreenshotStore: ss,
|
||||||
|
ScheduleStore: schedules,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
UploadDir: cfg.UploadDir,
|
UploadDir: cfg.UploadDir,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
server: &http.Server{Addr: cfg.HTTPAddress, Handler: handler},
|
server: &http.Server{Addr: cfg.HTTPAddress, Handler: handler},
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
authStore: authStore,
|
authStore: authStore,
|
||||||
dbPool: pool, // V7: Referenz für Shutdown
|
scheduleStore: schedules,
|
||||||
logger: logger,
|
screenStore: screens,
|
||||||
|
dbPool: pool, // V7: Referenz für Shutdown
|
||||||
|
logger: logger,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +144,9 @@ func (a *App) Run() error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Display-Zeitplan-Scheduler
|
||||||
|
go scheduler.Run(ctx, a.scheduleStore, a.screenStore, a.notifier)
|
||||||
|
|
||||||
// W2: Signal-Handler für Graceful Shutdown.
|
// W2: Signal-Handler für Graceful Shutdown.
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
|
|
||||||
46
server/backend/internal/httpapi/manage/schedule.go
Normal file
46
server/backend/internal/httpapi/manage/schedule.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package manage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.az-it.net/az/morz-infoboard/server/backend/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleUpdateSchedule speichert den Zeitplan für ein Display.
|
||||||
|
// Body: {"schedule_enabled":true,"power_on_time":"06:00","power_off_time":"22:00"}
|
||||||
|
func HandleUpdateSchedule(screens *store.ScreenStore, schedules *store.ScreenScheduleStore) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
screenSlug := r.PathValue("screenSlug")
|
||||||
|
screen, err := screens.GetBySlug(r.Context(), screenSlug)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "screen not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !requireScreenAccess(w, r, screen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
ScheduleEnabled bool `json:"schedule_enabled"`
|
||||||
|
PowerOnTime string `json:"power_on_time"`
|
||||||
|
PowerOffTime string `json:"power_off_time"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := schedules.Upsert(r.Context(), &store.ScreenSchedule{
|
||||||
|
ScreenID: screen.ID,
|
||||||
|
ScheduleEnabled: body.ScheduleEnabled,
|
||||||
|
PowerOnTime: body.PowerOnTime,
|
||||||
|
PowerOffTime: body.PowerOffTime,
|
||||||
|
}); err != nil {
|
||||||
|
http.Error(w, "db error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,17 +13,18 @@ import (
|
||||||
|
|
||||||
// RouterDeps holds all dependencies needed to build the HTTP router.
|
// RouterDeps holds all dependencies needed to build the HTTP router.
|
||||||
type RouterDeps struct {
|
type RouterDeps struct {
|
||||||
StatusStore playerStatusStore
|
StatusStore playerStatusStore
|
||||||
TenantStore *store.TenantStore
|
TenantStore *store.TenantStore
|
||||||
ScreenStore *store.ScreenStore
|
ScreenStore *store.ScreenStore
|
||||||
MediaStore *store.MediaStore
|
MediaStore *store.MediaStore
|
||||||
PlaylistStore *store.PlaylistStore
|
PlaylistStore *store.PlaylistStore
|
||||||
AuthStore *store.AuthStore
|
AuthStore *store.AuthStore
|
||||||
Notifier *mqttnotifier.Notifier
|
Notifier *mqttnotifier.Notifier
|
||||||
ScreenshotStore *ScreenshotStore
|
ScreenshotStore *ScreenshotStore
|
||||||
|
ScheduleStore *store.ScreenScheduleStore
|
||||||
Config config.Config
|
Config config.Config
|
||||||
UploadDir string
|
UploadDir string
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(deps RouterDeps) http.Handler {
|
func NewRouter(deps RouterDeps) http.Handler {
|
||||||
|
|
@ -187,6 +188,10 @@ func registerManageRoutes(mux *http.ServeMux, d RouterDeps) {
|
||||||
mux.Handle("POST /api/v1/screens/{screenSlug}/display",
|
mux.Handle("POST /api/v1/screens/{screenSlug}/display",
|
||||||
authScreen(http.HandlerFunc(manage.HandleDisplayCommand(notifier))))
|
authScreen(http.HandlerFunc(manage.HandleDisplayCommand(notifier))))
|
||||||
|
|
||||||
|
// ── Schedule control ──────────────────────────────────────────────────
|
||||||
|
mux.Handle("POST /api/v1/screens/{screenSlug}/schedule",
|
||||||
|
authScreen(http.HandlerFunc(manage.HandleUpdateSchedule(d.ScreenStore, d.ScheduleStore))))
|
||||||
|
|
||||||
// ── JSON API — screens ────────────────────────────────────────────────
|
// ── JSON API — screens ────────────────────────────────────────────────
|
||||||
// Self-registration: no auth (player calls this on startup).
|
// Self-registration: no auth (player calls this on startup).
|
||||||
mux.HandleFunc("POST /api/v1/screens/register",
|
mux.HandleFunc("POST /api/v1/screens/register",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue