diff --git a/README.md b/README.md index b0f9262..6d902e8 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ Full documentation: [`docs/api.md`](docs/api.md) | `/playlist` | GET | -- | Current week's playlist | | `/playlist/{position}` | GET | -- | Single track by position (1-indexed) | | `/shows` | GET | -- | List all shows (paginated) | -| `/shows/{show_id}` | GET | -- | Specific show with tracks | +| `/shows/by-episode/{episode_number}` | GET | -- | Look up show by episode number | +| `/shows/{show_id}` | GET | -- | Specific show by internal ID | | `/admin/refresh` | POST | Bearer | Trigger immediate SoundCloud fetch | | `/admin/tracks` | POST | Bearer | Add track to current show | | `/admin/tracks/{track_id}` | DELETE | Bearer | Remove track from current show | diff --git a/docs/api.md b/docs/api.md index 31a83f0..252af22 100644 --- a/docs/api.md +++ b/docs/api.md @@ -117,9 +117,39 @@ Lists all shows, ordered by week start date (newest first). --- +### `GET /shows/by-episode/{episode_number}` + +Look up a show by its episode number. This is the recommended endpoint for IRC bot integrations (e.g. `!playlist 530`). + +**Path Parameters** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `episode_number` | integer | The episode number (e.g. 530) | + +**Response** + +```json +{ + "show_id": 10, + "episode_number": 530, + "week_start": "2026-03-05T02:00:00+00:00", + "week_end": "2026-03-12T02:00:00+00:00", + "tracks": [...] +} +``` + +**Errors** + +| Status | Detail | +|--------|--------| +| 404 | `"No show with episode number {n}"` | + +--- + ### `GET /shows/{show_id}` -Returns a specific show with its full track listing. +Returns a specific show by internal database ID. **Path Parameters** diff --git a/src/ntr_fetcher/api.py b/src/ntr_fetcher/api.py index 2568029..88924fa 100644 --- a/src/ntr_fetcher/api.py +++ b/src/ntr_fetcher/api.py @@ -89,6 +89,20 @@ def create_app( for s in shows ] + @app.get("/shows/by-episode/{episode_number}") + def show_by_episode(episode_number: int): + show = db.get_show_by_episode_number(episode_number) + if show is None: + raise HTTPException(status_code=404, detail=f"No show with episode number {episode_number}") + tracks = db.get_show_tracks(show.id) + return { + "show_id": show.id, + "episode_number": show.episode_number, + "week_start": show.week_start.isoformat(), + "week_end": show.week_end.isoformat(), + "tracks": tracks, + } + @app.get("/shows/{show_id}") def show_detail(show_id: int): shows = db.list_shows(limit=1000, offset=0) diff --git a/src/ntr_fetcher/db.py b/src/ntr_fetcher/db.py index 42c0fcd..703f2fe 100644 --- a/src/ntr_fetcher/db.py +++ b/src/ntr_fetcher/db.py @@ -239,6 +239,24 @@ class Database: for row in rows ] + def get_show_by_episode_number(self, episode_number: int) -> Show | None: + conn = self._connect() + row = conn.execute( + "SELECT id, week_start, week_end, created_at, episode_number " + "FROM shows WHERE episode_number = ? LIMIT 1", + (episode_number,), + ).fetchone() + conn.close() + if row is None: + return None + return Show( + id=row["id"], + week_start=datetime.fromisoformat(row["week_start"]), + week_end=datetime.fromisoformat(row["week_end"]), + created_at=datetime.fromisoformat(row["created_at"]), + episode_number=row["episode_number"], + ) + def get_latest_episode_number(self) -> int | None: conn = self._connect() row = conn.execute( diff --git a/tests/test_api.py b/tests/test_api.py index dfedd21..d8609bd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -122,3 +122,23 @@ def test_admin_remove_track(client, db): assert resp.status_code == 200 tracks = db.get_show_tracks(show.id) assert len(tracks) == 1 + + +def test_show_by_episode(client, db): + week_start = datetime(2026, 3, 12, 2, 0, 0, tzinfo=timezone.utc) + week_end = datetime(2026, 3, 19, 2, 0, 0, tzinfo=timezone.utc) + show = db.get_or_create_show(week_start, week_end, episode_number=530) + t1 = Track(1, "Song A", "Artist A", "https://soundcloud.com/a/1", None, 180000, "cc-by", + datetime(2026, 3, 14, 1, 0, 0, tzinfo=timezone.utc), "{}") + db.upsert_track(t1) + db.set_show_tracks(show.id, [t1.id]) + resp = client.get("/shows/by-episode/530") + assert resp.status_code == 200 + data = resp.json() + assert data["episode_number"] == 530 + assert len(data["tracks"]) == 1 + + +def test_show_by_episode_not_found(client): + resp = client.get("/shows/by-episode/999") + assert resp.status_code == 404 diff --git a/tests/test_db.py b/tests/test_db.py index c94b454..6af48c9 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -272,6 +272,20 @@ def test_update_show_episode_number(db): assert show2.episode_number == 521 +def test_get_show_by_episode_number(db): + week_start = datetime(2026, 1, 8, 3, 0, 0, tzinfo=timezone.utc) + week_end = datetime(2026, 1, 15, 3, 0, 0, tzinfo=timezone.utc) + show = db.get_or_create_show(week_start, week_end, episode_number=521) + result = db.get_show_by_episode_number(521) + assert result is not None + assert result.id == show.id + assert result.episode_number == 521 + + +def test_get_show_by_episode_number_missing(db): + assert db.get_show_by_episode_number(999) is None + + def test_has_track_in_show(db): week_start = datetime(2026, 3, 13, 2, 0, 0, tzinfo=timezone.utc) week_end = datetime(2026, 3, 20, 2, 0, 0, tzinfo=timezone.utc)