mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
feat(admin): add announcements list + dismiss endpoint
Fourth piece of #419: a small in-code (not admin-authored) announcement list, target-scoped ("app"/"admin", "chooser" reserved but not wired since the landing page has no JS yet) and filterable by live server state via ShowWhile. First entry: the admin-area-gate heads-up, shown on the admin target while AdminAreaAuth is unset. GET /api/announcements?target=... and POST /api/announcements/{id}/dismiss are deliberately NOT behind BasicAuthAdmin — the whole point of the gate notice is to reach operators who haven't set up credentials yet, the exact audience an admin-only endpoint would exclude. The dismiss endpoint validates id against the known announcement list before it reaches RecordActivity, since this is the one call site where an id comes from an HTTP request rather than a compile-time constant. Updated the router snapshot (testdata/router_routes.txt) for the two new routes. Not wired into any UI yet — nothing calls these endpoints. Refs #419
This commit is contained in:
@@ -17,10 +17,12 @@ import (
|
||||
// BasicAuthAdmin middleware in isolation, to pin two things at once:
|
||||
// 1. /admin and /api/setup/* (and their /setup/* legacy aliases) are open
|
||||
// by default and become gated once AdminAreaAuth is "enabled".
|
||||
// 2. The three routes shared with soundtouch-cli/soundtouch-player
|
||||
// (ca.crt, tts/speak, tts/config) stay reachable WITHOUT credentials
|
||||
// regardless of the gate — the whole reason mountSetupAPI was split
|
||||
// into mountSetupAPIShared/mountSetupAPIAdmin.
|
||||
// 2. A handful of routes deliberately stay reachable WITHOUT credentials
|
||||
// regardless of the gate: ca.crt/tts/speak/tts/config because
|
||||
// soundtouch-cli/soundtouch-player call them directly (the whole reason
|
||||
// mountSetupAPI was split into mountSetupAPIShared/mountSetupAPIAdmin),
|
||||
// and /api/announcements because it specifically needs to reach
|
||||
// operators who haven't set up credentials yet.
|
||||
func TestAdminAreaAuthGate(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
@@ -46,11 +48,12 @@ func TestAdminAreaAuthGate(t *testing.T) {
|
||||
"/setup/settings",
|
||||
"/api/setup/settings",
|
||||
}
|
||||
sharedUngatedPaths := []string{
|
||||
alwaysUngatedPaths := []string{
|
||||
"/setup/ca.crt",
|
||||
"/api/setup/ca.crt",
|
||||
"/setup/tts/config",
|
||||
"/api/setup/tts/config",
|
||||
"/api/announcements?target=admin",
|
||||
}
|
||||
|
||||
t.Run("open by default (AdminAreaAuth unset)", func(t *testing.T) {
|
||||
@@ -83,8 +86,8 @@ func TestAdminAreaAuthGate(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("shared cli/player routes stay reachable without credentials", func(t *testing.T) {
|
||||
for _, path := range sharedUngatedPaths {
|
||||
t.Run("routes intentionally left outside the gate stay reachable without credentials", func(t *testing.T) {
|
||||
for _, path := range alwaysUngatedPaths {
|
||||
status := getStatus(t, ts.URL, path, "", "")
|
||||
if status != http.StatusOK {
|
||||
t.Errorf("%s: expected 200 without credentials even with the gate enabled, got %d", path, status)
|
||||
|
||||
@@ -1297,6 +1297,10 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler, w
|
||||
r.Get("/", server.HandleRoot)
|
||||
r.With(server.BasicAuthAdmin()).Get("/admin", server.HandleAdmin)
|
||||
r.Get("/health", server.HandleHealth)
|
||||
// Deliberately not behind BasicAuthAdmin — see HandleListAnnouncements'
|
||||
// doc comment. #419.
|
||||
r.Get("/api/announcements", server.HandleListAnnouncements)
|
||||
r.Post("/api/announcements/{id}/dismiss", server.HandleDismissAnnouncement)
|
||||
r.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
|
||||
// The favicon lives in the embedded web/img bundle, not under
|
||||
// static/media — HandleMedia would 404. HandleWeb serves from
|
||||
|
||||
@@ -36,6 +36,7 @@ GET /accounts/{account}/devices/{device}/recents handlers.(
|
||||
GET /accounts/{account}/full handlers.(*Server).HandleUnsupported-fm
|
||||
GET /accounts/{account}/sources handlers.(*Server).HandleUnsupported-fm
|
||||
GET /admin handlers.(*Server).HandleAdmin-fm
|
||||
GET /api/announcements handlers.(*Server).HandleListAnnouncements-fm
|
||||
GET /api/control/devices/ soundtouchweb.(*WebApp).HandleAPIDevices-fm
|
||||
GET /api/control/devices/{id}/ soundtouchweb.(*WebApp).HandleAPIDevice-fm
|
||||
GET /api/control/devices/{id}/action/{action} soundtouchweb.(*WebApp).HandleAPIControl-fm
|
||||
@@ -182,6 +183,7 @@ POST /accounts/{account}/group handlers.(
|
||||
POST /accounts/{account}/group/ handlers.(*Server).HandleUnsupported-fm
|
||||
POST /accounts/{account}/group/{groupId} handlers.(*Server).HandleUnsupported-fm
|
||||
POST /alexa/certificate handlers.(*Server).HandleAlexaCertificate-fm
|
||||
POST /api/announcements/{id}/dismiss handlers.(*Server).HandleDismissAnnouncement-fm
|
||||
POST /api/control/devices/{id}/action/{action} soundtouchweb.(*WebApp).HandleAPIControl-fm
|
||||
POST /api/control/devices/{id}/key/{key} soundtouchweb.(*WebApp).HandleDeviceKey-fm
|
||||
POST /api/control/devices/{id}/library/play soundtouchweb.(*WebApp).HandlePlayLibrary-fm
|
||||
|
||||
Reference in New Issue
Block a user