feat: add public index page with censored playlist and live reveals

Public-facing page at / shows the current show's playlist with tracks
obscured until the admin marks them as announced. Tracks reveal in
real-time via a new unauthenticated /ws/public WebSocket. Server-side
censorship on /public/playlist strips track details from unannounced
items and sanitizes announced tracks to only expose frontend-needed
fields (no raw_json, track_id, etc). Past episodes are browsable with
fully revealed but sanitized tracklists.

Also fixes RuntimeError on backfill shutdown by closing the httpx
client on the same event loop that created it.

Made-with: Cursor
This commit is contained in:
cottongin
2026-04-01 23:41:17 -04:00
parent 11f13c86b5
commit 425a7047c3
8 changed files with 786 additions and 17 deletions

View File

@@ -152,3 +152,72 @@ def test_no_dashboard_routes_without_config(client):
def test_no_login_route_without_config(client):
resp = client.get("/login")
assert resp.status_code == 404
# --- Public endpoint tests ---
def test_index_page(client):
resp = client.get("/")
assert resp.status_code == 200
assert "NtR Playlist" in resp.text
def test_public_playlist_censors_unannounced(client, db):
show = _seed_show(db)
db.set_track_announced(show.id, 1, True)
resp = client.get("/public/playlist")
assert resp.status_code == 200
data = resp.json()
assert len(data["tracks"]) == 2
revealed = data["tracks"][0]
assert revealed["announced"] == 1
assert revealed["title"] == "Song A"
assert revealed["artist"] == "Artist A"
assert "raw_json" not in revealed
assert "track_id" not in revealed
assert "show_id" not in revealed
hidden = data["tracks"][1]
assert hidden["announced"] == 0
assert hidden["position"] == 2
assert "title" not in hidden
assert "artist" not in hidden
def test_public_playlist_all_hidden(client, db):
_seed_show(db)
resp = client.get("/public/playlist")
data = resp.json()
for t in data["tracks"]:
assert t["announced"] == 0
assert "title" not in t
def test_public_shows_list(client, db):
_seed_show(db)
resp = client.get("/public/shows")
assert resp.status_code == 200
data = resp.json()
assert len(data) >= 1
assert "id" in data[0]
assert "episode_number" in data[0]
def test_public_show_detail_fully_revealed(client, db):
show = _seed_show(db)
resp = client.get(f"/public/shows/{show.id}")
assert resp.status_code == 200
data = resp.json()
assert len(data["tracks"]) == 2
assert data["tracks"][0]["title"] == "Song A"
assert data["tracks"][1]["title"] == "Song B"
assert "raw_json" not in data["tracks"][0]
assert "track_id" not in data["tracks"][0]
def test_public_show_detail_not_found(client):
resp = client.get("/public/shows/999")
assert resp.status_code == 404