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
This commit is contained in:
@@ -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 |
|
||||
|
||||
32
docs/api.md
32
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**
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user