107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""JellyStat REST API — watch history, genre summary, and user summary."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncpg
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
|
from gateway.jellystat.db import get_pool
|
|
from gateway.jellystat.models import (
|
|
GenreSummaryResponse,
|
|
UserSummaryResponse,
|
|
WatchHistoryResponse,
|
|
)
|
|
|
|
router = APIRouter(prefix="/jellystat", tags=["jellystat"])
|
|
|
|
DEFAULT_WINDOW_MINUTES = 10080 # 7 days
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /jellystat/history/{user_id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.get("/history/{user_id}", response_model=WatchHistoryResponse)
|
|
async def get_watch_history(
|
|
user_id: str,
|
|
minutes: int = Query(
|
|
default=DEFAULT_WINDOW_MINUTES, ge=1, description="Time window in minutes"
|
|
),
|
|
pool: asyncpg.Pool = Depends(get_pool),
|
|
):
|
|
"""Return watch history grouped by title, ordered by most-watched first."""
|
|
rows = await pool.fetch(
|
|
"SELECT * FROM fn_user_watch_history($1, $2)", user_id, minutes
|
|
)
|
|
return WatchHistoryResponse(
|
|
user_id=user_id,
|
|
window_minutes=minutes,
|
|
items=[
|
|
{
|
|
"title": r["title"],
|
|
"watch_time_sec": float(r["watch_time_sec"]),
|
|
"media_type": r["media_type"],
|
|
}
|
|
for r in rows
|
|
],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /jellystat/genres/{user_id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.get("/genres/{user_id}", response_model=GenreSummaryResponse)
|
|
async def get_genre_summary(
|
|
user_id: str,
|
|
minutes: int = Query(
|
|
default=DEFAULT_WINDOW_MINUTES, ge=1, description="Time window in minutes"
|
|
),
|
|
pool: asyncpg.Pool = Depends(get_pool),
|
|
):
|
|
"""Return total watch time per genre, ordered by most-watched first."""
|
|
rows = await pool.fetch(
|
|
"SELECT * FROM fn_user_genre_summary($1, $2)", user_id, minutes
|
|
)
|
|
return GenreSummaryResponse(
|
|
user_id=user_id,
|
|
window_minutes=minutes,
|
|
genres=[
|
|
{"genre": r["genre"], "watch_time_sec": float(r["watch_time_sec"])}
|
|
for r in rows
|
|
],
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /jellystat/summary/{user_id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.get("/summary/{user_id}", response_model=UserSummaryResponse)
|
|
async def get_user_summary(
|
|
user_id: str,
|
|
pool: asyncpg.Pool = Depends(get_pool),
|
|
):
|
|
"""Return all-time summary: total watch time, most-watched titles, top genres."""
|
|
rows = await pool.fetch("SELECT * FROM fn_user_summary($1)", user_id)
|
|
|
|
# fn_user_summary returns key-value rows — build a dict
|
|
# asyncpg already deserialises JSONB → Python objects
|
|
metrics: dict[str, object] = {r["metric"]: r["value"] for r in rows}
|
|
|
|
top_genres_raw = metrics.get("top_genres", [])
|
|
top_genres: list[str] = top_genres_raw if isinstance(top_genres_raw, list) else []
|
|
|
|
return UserSummaryResponse(
|
|
user_id=user_id,
|
|
total_watch_time_sec=float(metrics.get("total_watch_time", 0)),
|
|
most_watched_series=metrics.get("most_watched_series"),
|
|
most_watched_movie=metrics.get("most_watched_movie"),
|
|
total_last_30d_sec=float(metrics.get("total_last_30d", 0)),
|
|
total_last_7d_sec=float(metrics.get("total_last_7d", 0)),
|
|
top_genres=top_genres,
|
|
)
|