25 Commits

Author SHA1 Message Date
cottongin
4dd3f43ae3 feat: lock playlist/shows endpoints behind Bearer token auth
The five internal endpoints (/playlist, /playlist/{position}, /shows,
/shows/by-episode/{ep}, /shows/{id}) now require admin authentication.
Dashboard JS, Sopel plugin, and Limnoria plugin updated to send the
token on GET requests. Six new 401 tests added.

Made-with: Cursor
2026-04-02 12:06:27 -04:00
cottongin
425a7047c3 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
2026-04-01 23:50:41 -04:00
cottongin
11f13c86b5 feat: add announced checkbox to track rows
Add a persistent "announced" checkbox after each track's Announce button.
The state is stored in a new `announced` column on `show_tracks` and is
auto-set when the Announce button is pressed. The checkbox is also freely
togglable, and announced tracks have their Announce button disabled.

Also fixes .env leakage in test_config.py (pass _env_file=None) and adds
tests for the new DB method, API endpoint, and announce side-effect.

Made-with: Cursor
2026-04-01 23:00:09 -04:00
cottongin
82049ab47f feat: delay show rotation during live recording window
Decouple the like-window boundary (Wed 10pm ET) from when the system
rotates to a new show. NTR_SHOW_ROTATION_DELAY_HOURS=2 keeps the
previous week's show visible during the ~2 hour recording, then creates
the new show at midnight.

Made-with: Cursor
2026-04-01 21:29:42 -04:00
cottongin
a328684af0 fix: handle SoundCloud API 5xx errors with client_id refresh, backoff, and cursor fallback
SoundCloud began rejecting the fabricated pagination cursor with 500
errors. Fixed cursor user_id padding (zfill 22→20) to match the
documented format, added 5xx retry with exponential backoff in _api_get,
and added a fallback in fetch_likes that drops the fabricated cursor
when it causes persistent 500s.

Made-with: Cursor
2026-03-25 08:20:20 -04:00
cottongin
d6d5ac10e6 fix: separate bot vs viewer WebSocket connections, add client identification
The dashboard's own WS connection was being counted as a bot subscriber,
causing "1 bot connected" with no bots actually present. Now WS clients
send a role ("bot" or "viewer") in the subscribe message. Only bots count
toward the subscriber total. Bot plugins also send a configurable client_id
so the dashboard shows which specific bots are connected.

Made-with: Cursor
2026-03-12 07:51:55 -04:00
cottongin
92136f0508 Wire dashboard into app
- Add web_user, web_password, secret_key as optional params to create_app
- Conditionally mount dashboard router when all three are set
- Pass settings from main.py to create_app
- Add tests for no dashboard/login routes when config absent

Made-with: Cursor
2026-03-12 07:19:01 -04:00
cottongin
e90f44439b Add dashboard router with session auth, announce endpoint, and WebSocket handler
- Login/logout/dashboard with HMAC-signed session cookies
- POST /admin/announce with session or bearer auth
- WS /ws/announce for subscribe/broadcast
- Static stubs: login.html, dashboard.html

Made-with: Cursor
2026-03-12 07:17:20 -04:00
cottongin
788225b3b6 feat: add WebSocket announce manager
Made-with: Cursor
2026-03-12 07:14:04 -04:00
cottongin
7ed7ace578 feat: add optional dashboard config fields
Made-with: Cursor
2026-03-12 07:12:10 -04:00
cottongin
9664b8225d feat: add human-readable datetime formatting and !lastshow command
IRC plugins now format datetimes as "Wed Mar 11, 10:00 PM EDT" instead
of raw ISO 8601. Configurable timezone defaults to America/New_York.

Adds !lastshow N command to both Limnoria and Sopel plugins, returning
track N from the previous week's show via existing API endpoints.

Made-with: Cursor
2026-03-12 05:31:50 -04:00
cottongin
05bcf184ac fix: playlist truncation overflow and align logging across plugins
- Fix format_playlist truncation: +4 → +5 to account for ", ..." suffix
- Add API error logging to all Sopel command handlers (matching Limnoria)
- Add long-track truncation test case

Made-with: Cursor
2026-03-12 03:24:29 -04:00
cottongin
5c227766f1 test: add tests for IRC plugin formatting and API helpers
Made-with: Cursor
2026-03-12 03:20:05 -04:00
cottongin
1d08580a45 feat: add GET /shows/by-episode/{episode_number} endpoint
Allows looking up shows by episode number instead of internal DB ID,
enabling IRC bot commands like !playlist 530 to resolve directly.

Made-with: Cursor
2026-03-12 02:33:42 -04:00
cottongin
cb3ae403cf feat: add historical backfill with --init CLI and episode numbering
Adds a --init mode that seeds the database with past shows from a given
anchor episode/date forward, batch-fetching likes from SoundCloud and
partitioning them into weekly buckets. Episode numbers are tracked in
the shows table and auto-incremented by the poller for new shows.

Includes full API documentation (docs/api.md) and updated README.

Made-with: Cursor
2026-03-12 02:09:15 -04:00
cottongin
485f0c1e35 fix: address linting issues
Made-with: Cursor
2026-03-12 01:44:00 -04:00
cottongin
2e22a2b3ff feat: add FastAPI routes for playlist, shows, admin, and health
Made-with: Cursor
2026-03-12 01:40:04 -04:00
cottongin
ab81ad4fd4 feat: add poller with supervised restart loop
Made-with: Cursor
2026-03-12 01:40:00 -04:00
cottongin
f7dde6781a feat: add user resolution and likes fetching with 401 retry
Made-with: Cursor
2026-03-12 01:36:28 -04:00
cottongin
49846f9d7e feat: add SoundCloud client with client_id extraction
Made-with: Cursor
2026-03-12 01:36:03 -04:00
cottongin
d2ab4e94c0 feat: add database query methods for tracks, shows, and show_tracks
Made-with: Cursor
2026-03-12 01:33:17 -04:00
cottongin
2b38fda25a feat: add database module with schema initialization
Made-with: Cursor
2026-03-12 01:21:33 -04:00
cottongin
38dda5dda0 feat: add week boundary computation with DST handling
Made-with: Cursor
2026-03-12 01:18:12 -04:00
cottongin
7e41f55e26 feat: add configuration module with pydantic-settings
Made-with: Cursor
2026-03-12 01:16:51 -04:00
cottongin
c40559822e scaffold: project structure with pyproject.toml and test config
Made-with: Cursor
2026-03-12 01:16:16 -04:00