"""Channel API routes.""" from __future__ import annotations import asyncio from concurrent.futures import ThreadPoolExecutor from fastapi import APIRouter, HTTPException from pydantic import BaseModel from core import youtube _executor = ThreadPoolExecutor(max_workers=4) router = APIRouter() class ChannelCreate(BaseModel): channel_id: str channel_name: str title_filter: str | None = None @router.get("") def list_channels(): return youtube.get_active_channels() @router.post("", status_code=201) def create_channel(body: ChannelCreate): try: row_id = youtube.add_channel(body.channel_id, body.channel_name, body.title_filter) return {"id": row_id, "channel_id": body.channel_id} except Exception as e: if "UQ_CHANNELS_CID" in str(e).upper(): raise HTTPException(409, "Channel already exists") raise def _do_scan(channel_id: str, full: bool): """Sync scan logic, runs in thread pool.""" channels = youtube.get_active_channels() ch = next((c for c in channels if c["channel_id"] == channel_id), None) if not ch: return None after = None if full else youtube.get_latest_video_date(ch["id"]) title_filter = ch.get("title_filter") existing_vids = youtube.get_existing_video_ids(ch["id"]) candidates = [] total_fetched = 0 for videos_page in youtube.fetch_channel_videos_iter(channel_id, published_after=after): total_fetched += len(videos_page) new_in_page = 0 for v in videos_page: if title_filter and title_filter not in v["title"]: continue if v["video_id"] in existing_vids: continue candidates.append(v) new_in_page += 1 if not full and new_in_page == 0 and total_fetched > 50: break new_count = youtube.save_videos_batch(ch["id"], candidates) return {"total_fetched": total_fetched, "new_videos": new_count} @router.post("/{channel_id}/scan") async def scan_channel(channel_id: str, full: bool = False): """Trigger a scan for new videos from this channel (non-blocking).""" loop = asyncio.get_event_loop() result = await loop.run_in_executor(_executor, _do_scan, channel_id, full) if result is None: raise HTTPException(404, "Channel not found") return result @router.delete("/{channel_id:path}") def delete_channel(channel_id: str): """Deactivate a channel. Accepts channel_id or DB id.""" deleted = youtube.deactivate_channel(channel_id) if not deleted: # Try by DB id deleted = youtube.deactivate_channel_by_db_id(channel_id) if not deleted: raise HTTPException(404, "Channel not found") return {"ok": True}