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

@@ -1,5 +1,5 @@
import pytest
from ntr_fetcher.websocket import AnnounceManager
from ntr_fetcher.websocket import AnnounceManager, PublicManager
@pytest.fixture
@@ -98,3 +98,61 @@ async def test_status_broadcast_includes_clients(manager):
assert msg["subscribers"] == 1
assert len(msg["clients"]) == 1
assert msg["clients"][0]["client_id"] == "my-bot"
# --- PublicManager tests ---
@pytest.fixture
def public_manager():
return PublicManager()
def test_public_no_clients_initially(public_manager):
assert public_manager.client_count == 0
@pytest.mark.asyncio
async def test_public_add_remove_client(public_manager):
class FakeWS:
async def send_json(self, data):
pass
ws = FakeWS()
public_manager.add_client(ws)
assert public_manager.client_count == 1
public_manager.remove_client(ws)
assert public_manager.client_count == 0
@pytest.mark.asyncio
async def test_public_broadcast(public_manager):
received = []
class FakeWS:
async def send_json(self, data):
received.append(data)
ws1 = FakeWS()
ws2 = FakeWS()
public_manager.add_client(ws1)
public_manager.add_client(ws2)
await public_manager.broadcast({"type": "reveal", "position": 1})
assert len(received) == 2
assert all(m["type"] == "reveal" for m in received)
@pytest.mark.asyncio
async def test_public_broadcast_removes_dead_clients(public_manager):
class DeadWS:
async def send_json(self, data):
raise Exception("closed")
ws = DeadWS()
public_manager.add_client(ws)
assert public_manager.client_count == 1
await public_manager.broadcast({"type": "reveal", "position": 1})
assert public_manager.client_count == 0