package httpapi import ( "log" "net/http" "git.az-it.net/az/morz-infoboard/server/backend/internal/httpapi/manage" "git.az-it.net/az/morz-infoboard/server/backend/internal/store" ) // RouterDeps holds all dependencies needed to build the HTTP router. type RouterDeps struct { StatusStore playerStatusStore TenantStore *store.TenantStore ScreenStore *store.ScreenStore MediaStore *store.MediaStore PlaylistStore *store.PlaylistStore UploadDir string Logger *log.Logger } func NewRouter(deps RouterDeps) http.Handler { mux := http.NewServeMux() // ── Health ─────────────────────────────────────────────────────────── mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", "service": "morz-infoboard-backend", }) }) // ── Status / diagnostic UI ─────────────────────────────────────────── mux.HandleFunc("GET /status", handleStatusPage(deps.StatusStore)) mux.HandleFunc("GET /status/{screenId}", handleScreenDetailPage(deps.StatusStore)) // ── API meta ───────────────────────────────────────────────────────── mux.HandleFunc("GET /api/v1", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "name": "morz-infoboard-backend", "version": "dev", "tools": []string{ "message-wall-resolve", "screen-status-list", "screen-status-detail", "player-status-ingest", "screen-status-delete", }, }) }) mux.HandleFunc("GET /api/v1/meta", handleMeta) // ── Player status (existing) ────────────────────────────────────────── mux.HandleFunc("POST /api/v1/player/status", handlePlayerStatus(deps.StatusStore)) mux.HandleFunc("GET /api/v1/screens/status", handleListLatestPlayerStatuses(deps.StatusStore)) mux.HandleFunc("GET /api/v1/screens/{screenId}/status", handleGetLatestPlayerStatus(deps.StatusStore)) mux.HandleFunc("DELETE /api/v1/screens/{screenId}/status", handleDeletePlayerStatus(deps.StatusStore)) // ── Message wall ────────────────────────────────────────────────────── mux.HandleFunc("POST /api/v1/tools/message-wall/resolve", handleResolveMessageWall) // ── Playlist management — only register if stores are wired up ──────── if deps.TenantStore != nil { registerManageRoutes(mux, deps) } return mux } func registerManageRoutes(mux *http.ServeMux, d RouterDeps) { uploadDir := d.UploadDir if uploadDir == "" { uploadDir = "/tmp/morz-uploads" } // Serve uploaded files. mux.Handle("GET /uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir(uploadDir)))) // Serve embedded static assets (Bulma CSS, SortableJS) — no external CDN needed. mux.HandleFunc("GET /static/bulma.min.css", manage.HandleStaticBulmaCSS()) mux.HandleFunc("GET /static/Sortable.min.js", manage.HandleStaticSortableJS()) // ── Admin UI ────────────────────────────────────────────────────────── mux.HandleFunc("GET /admin", manage.HandleAdminUI(d.TenantStore, d.ScreenStore)) mux.HandleFunc("POST /admin/screens/provision", manage.HandleProvisionUI(d.TenantStore, d.ScreenStore)) mux.HandleFunc("POST /admin/screens", manage.HandleCreateScreenUI(d.TenantStore, d.ScreenStore)) mux.HandleFunc("POST /admin/screens/{screenId}/delete", manage.HandleDeleteScreenUI(d.ScreenStore)) // ── Playlist management UI ──────────────────────────────────────────── mux.HandleFunc("GET /manage/{screenSlug}", manage.HandleManageUI(d.TenantStore, d.ScreenStore, d.MediaStore, d.PlaylistStore)) mux.HandleFunc("POST /manage/{screenSlug}/upload", manage.HandleUploadMediaUI(d.MediaStore, d.ScreenStore, uploadDir)) mux.HandleFunc("POST /manage/{screenSlug}/items", manage.HandleAddItemUI(d.PlaylistStore, d.MediaStore, d.ScreenStore)) mux.HandleFunc("POST /manage/{screenSlug}/items/{itemId}", manage.HandleUpdateItemUI(d.PlaylistStore)) mux.HandleFunc("POST /manage/{screenSlug}/items/{itemId}/delete", manage.HandleDeleteItemUI(d.PlaylistStore)) mux.HandleFunc("POST /manage/{screenSlug}/reorder", manage.HandleReorderUI(d.PlaylistStore, d.ScreenStore)) mux.HandleFunc("POST /manage/{screenSlug}/media/{mediaId}/delete", manage.HandleDeleteMediaUI(d.MediaStore, d.ScreenStore, uploadDir)) // ── JSON API — screens ──────────────────────────────────────────────── // Self-registration: called by agent on startup (must be before /{tenantSlug}/ routes) mux.HandleFunc("POST /api/v1/screens/register", manage.HandleRegisterScreen(d.TenantStore, d.ScreenStore)) mux.HandleFunc("GET /api/v1/tenants/{tenantSlug}/screens", manage.HandleListScreens(d.TenantStore, d.ScreenStore)) mux.HandleFunc("POST /api/v1/tenants/{tenantSlug}/screens", manage.HandleCreateScreen(d.TenantStore, d.ScreenStore)) // ── JSON API — media ────────────────────────────────────────────────── mux.HandleFunc("GET /api/v1/tenants/{tenantSlug}/media", manage.HandleListMedia(d.TenantStore, d.MediaStore)) mux.HandleFunc("POST /api/v1/tenants/{tenantSlug}/media", manage.HandleUploadMedia(d.TenantStore, d.MediaStore, uploadDir)) mux.HandleFunc("DELETE /api/v1/media/{id}", manage.HandleDeleteMedia(d.MediaStore, uploadDir)) // ── JSON API — playlists ────────────────────────────────────────────── mux.HandleFunc("GET /api/v1/screens/{screenId}/playlist", manage.HandlePlayerPlaylist(d.ScreenStore, d.PlaylistStore)) mux.HandleFunc("GET /api/v1/playlists/{screenId}", manage.HandleGetPlaylist(d.ScreenStore, d.PlaylistStore)) mux.HandleFunc("POST /api/v1/playlists/{playlistId}/items", manage.HandleAddItem(d.PlaylistStore, d.MediaStore)) mux.HandleFunc("PATCH /api/v1/items/{itemId}", manage.HandleUpdateItem(d.PlaylistStore)) mux.HandleFunc("DELETE /api/v1/items/{itemId}", manage.HandleDeleteItem(d.PlaylistStore)) mux.HandleFunc("PUT /api/v1/playlists/{playlistId}/order", manage.HandleReorder(d.PlaylistStore)) mux.HandleFunc("PATCH /api/v1/playlists/{playlistId}/duration", manage.HandleUpdatePlaylistDuration(d.PlaylistStore)) }