""" Read-only widget endpoints for external dashboards (e.g. backstage). Auth: X-Widget-Key header must match WIDGET_API_KEY env var. No user session required — returns data for the first admin user. """ from datetime import datetime from typing import Optional from fastapi import APIRouter, Depends, Header, HTTPException from sqlalchemy.orm import Session from sqlalchemy import text from ..config import settings from ..database import get_db from ..models import User router = APIRouter() def _require_widget_key(x_widget_key: Optional[str] = Header(default=None)): if not settings.widget_api_key: raise HTTPException(status_code=503, detail="widget API not configured") if x_widget_key != settings.widget_api_key: raise HTTPException(status_code=401, detail="invalid widget key") def _get_widget_user(db: Session) -> User: user = db.query(User).filter_by(is_admin=True).order_by(User.id).first() if not user: user = db.query(User).order_by(User.id).first() if not user: raise HTTPException(status_code=503, detail="no users") return user @router.get("/recent") def recent_videos( limit: int = 12, db: Session = Depends(get_db), _: None = Depends(_require_widget_key), ): """Recent unwatched videos from followed channels.""" user = _get_widget_user(db) rows = db.execute( text(""" SELECT v.youtube_video_id, v.title, v.thumbnail_url, v.duration_seconds, v.published_at, c.name AS channel_name, c.youtube_channel_id AS channel_yt_id, COALESCE(uv.watched, 0) AS watched FROM videos v JOIN channels c ON v.channel_id = c.id JOIN user_channels uc ON c.id = uc.channel_id AND uc.user_id = :uid AND uc.status = 'followed' LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :uid WHERE COALESCE(uv.watched, 0) = 0 AND v.published_at IS NOT NULL AND (uc.muted_until IS NULL OR datetime(uc.muted_until) < datetime('now')) ORDER BY v.published_at DESC LIMIT :limit """), {"uid": user.id, "limit": limit}, ).mappings().all() return { "videos": [ { "youtube_video_id": r["youtube_video_id"], "title": r["title"], "channel_name": r["channel_name"], "thumbnail_url": r["thumbnail_url"], "published_at": r["published_at"], "duration_seconds": r["duration_seconds"], "url": f"https://yt.nullinput.io/watch/{r['youtube_video_id']}", } for r in rows ] } @router.get("/stats") def widget_stats( db: Session = Depends(get_db), _: None = Depends(_require_widget_key), ): """Quick stats: unwatched count, channel count, recent activity.""" user = _get_widget_user(db) row = db.execute( text(""" SELECT COUNT(*) FILTER (WHERE COALESCE(uv.watched, 0) = 0) AS unwatched, COUNT(*) FILTER (WHERE v.published_at >= datetime('now', '-7 days') AND COALESCE(uv.watched, 0) = 0) AS new_this_week, COUNT(DISTINCT uc.channel_id) AS channel_count FROM user_channels uc JOIN channels c ON c.id = uc.channel_id JOIN videos v ON v.channel_id = c.id LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :uid WHERE uc.user_id = :uid AND uc.status = 'followed' """), {"uid": user.id}, ).mappings().first() return { "unwatched": row["unwatched"] if row else 0, "new_this_week": row["new_this_week"] if row else 0, "channel_count": row["channel_count"] if row else 0, }