131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
|
|
import logging
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
from fastapi import FastAPI, HTTPException, Depends, Header
|
||
|
|
from pydantic import BaseModel
|
||
|
|
|
||
|
|
from ntr_fetcher.db import Database
|
||
|
|
from ntr_fetcher.week import get_show_week
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class RefreshRequest(BaseModel):
|
||
|
|
full: bool = False
|
||
|
|
|
||
|
|
|
||
|
|
class AddTrackRequest(BaseModel):
|
||
|
|
soundcloud_url: str | None = None
|
||
|
|
track_id: int | None = None
|
||
|
|
position: int | None = None
|
||
|
|
|
||
|
|
|
||
|
|
class MoveTrackRequest(BaseModel):
|
||
|
|
position: int
|
||
|
|
|
||
|
|
|
||
|
|
def create_app(db: Database, poller, admin_token: str) -> FastAPI:
|
||
|
|
app = FastAPI(title="NtR SoundCloud Fetcher")
|
||
|
|
|
||
|
|
def _require_admin(authorization: str | None = Header(None)):
|
||
|
|
if authorization is None or not authorization.startswith("Bearer "):
|
||
|
|
raise HTTPException(status_code=401, detail="Missing or invalid token")
|
||
|
|
token = authorization.removeprefix("Bearer ")
|
||
|
|
if token != admin_token:
|
||
|
|
raise HTTPException(status_code=401, detail="Invalid token")
|
||
|
|
|
||
|
|
def _current_show():
|
||
|
|
now = datetime.now(timezone.utc)
|
||
|
|
week_start, week_end = get_show_week(now, show_day=2, show_hour=22)
|
||
|
|
return db.get_or_create_show(week_start, week_end)
|
||
|
|
|
||
|
|
@app.get("/health")
|
||
|
|
def health():
|
||
|
|
show = _current_show()
|
||
|
|
tracks = db.get_show_tracks(show.id)
|
||
|
|
return {
|
||
|
|
"status": "ok",
|
||
|
|
"poller_alive": poller.alive,
|
||
|
|
"last_fetch": poller.last_fetch.isoformat() if poller.last_fetch else None,
|
||
|
|
"current_week_track_count": len(tracks),
|
||
|
|
}
|
||
|
|
|
||
|
|
@app.get("/playlist")
|
||
|
|
def playlist():
|
||
|
|
show = _current_show()
|
||
|
|
tracks = db.get_show_tracks(show.id)
|
||
|
|
return {
|
||
|
|
"show_id": show.id,
|
||
|
|
"week_start": show.week_start.isoformat(),
|
||
|
|
"week_end": show.week_end.isoformat(),
|
||
|
|
"tracks": tracks,
|
||
|
|
}
|
||
|
|
|
||
|
|
@app.get("/playlist/{position}")
|
||
|
|
def playlist_track(position: int):
|
||
|
|
show = _current_show()
|
||
|
|
track = db.get_show_track_by_position(show.id, position)
|
||
|
|
if track is None:
|
||
|
|
raise HTTPException(status_code=404, detail=f"No track at position {position}")
|
||
|
|
return track
|
||
|
|
|
||
|
|
@app.get("/shows")
|
||
|
|
def list_shows(limit: int = 20, offset: int = 0):
|
||
|
|
shows = db.list_shows(limit=limit, offset=offset)
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
"id": s.id,
|
||
|
|
"week_start": s.week_start.isoformat(),
|
||
|
|
"week_end": s.week_end.isoformat(),
|
||
|
|
"created_at": s.created_at.isoformat(),
|
||
|
|
}
|
||
|
|
for s in shows
|
||
|
|
]
|
||
|
|
|
||
|
|
@app.get("/shows/{show_id}")
|
||
|
|
def show_detail(show_id: int):
|
||
|
|
shows = db.list_shows(limit=1000, offset=0)
|
||
|
|
show = next((s for s in shows if s.id == show_id), None)
|
||
|
|
if show is None:
|
||
|
|
raise HTTPException(status_code=404, detail="Show not found")
|
||
|
|
tracks = db.get_show_tracks(show.id)
|
||
|
|
return {
|
||
|
|
"show_id": show.id,
|
||
|
|
"week_start": show.week_start.isoformat(),
|
||
|
|
"week_end": show.week_end.isoformat(),
|
||
|
|
"tracks": tracks,
|
||
|
|
}
|
||
|
|
|
||
|
|
@app.post("/admin/refresh")
|
||
|
|
async def admin_refresh(body: RefreshRequest = RefreshRequest(), _=Depends(_require_admin)):
|
||
|
|
await poller.poll_once(full=body.full)
|
||
|
|
show = _current_show()
|
||
|
|
tracks = db.get_show_tracks(show.id)
|
||
|
|
return {"status": "refreshed", "track_count": len(tracks)}
|
||
|
|
|
||
|
|
@app.post("/admin/tracks")
|
||
|
|
def admin_add_track(body: AddTrackRequest, _=Depends(_require_admin)):
|
||
|
|
show = _current_show()
|
||
|
|
if body.track_id is not None:
|
||
|
|
db.add_track_to_show(show.id, body.track_id, body.position)
|
||
|
|
return {"status": "added"}
|
||
|
|
raise HTTPException(status_code=400, detail="Provide track_id")
|
||
|
|
|
||
|
|
@app.delete("/admin/tracks/{track_id}")
|
||
|
|
def admin_remove_track(track_id: int, _=Depends(_require_admin)):
|
||
|
|
show = _current_show()
|
||
|
|
if not db.has_track_in_show(show.id, track_id):
|
||
|
|
raise HTTPException(status_code=404, detail="Track not in current show")
|
||
|
|
db.remove_show_track(show.id, track_id)
|
||
|
|
return {"status": "removed"}
|
||
|
|
|
||
|
|
@app.put("/admin/tracks/{track_id}/position")
|
||
|
|
def admin_move_track(track_id: int, body: MoveTrackRequest, _=Depends(_require_admin)):
|
||
|
|
show = _current_show()
|
||
|
|
if not db.has_track_in_show(show.id, track_id):
|
||
|
|
raise HTTPException(status_code=404, detail="Track not in current show")
|
||
|
|
db.move_show_track(show.id, track_id, body.position)
|
||
|
|
return {"status": "moved"}
|
||
|
|
|
||
|
|
return app
|