"""Фоновые задачи: видео или плейлист любой длины. POST ставит задачу в очередь и сразу возвращает id; прогресс — в GET /jobs. """ from __future__ import annotations import uuid as _uuid from datetime import datetime from fastapi import APIRouter, HTTPException from pydantic import BaseModel from api.db.job import create_job, delete_job, get_job, list_jobs from api.lib import jobqueue router = APIRouter(prefix="/jobs", tags=["jobs"]) class JobIn(BaseModel): url: str section_id: int | None = None # None → берутся из сохранённых настроек keep_video: bool | None = None summary_max_sentences: int | None = None model_path: str | None = None class JobOut(BaseModel): id: str url: str kind: str title: str status: str total_items: int done_items: int failed_items: int current_item: str live: str error: str created_at: datetime def _to_out(j) -> JobOut: return JobOut( id=j.id, url=j.url, kind=j.kind, title=j.title, status=j.status, total_items=j.total_items, done_items=j.done_items, failed_items=j.failed_items, current_item=j.current_item, live=jobqueue.live_status(j.id) or j.current_item, error=j.error, created_at=j.created_at, ) @router.post("/", response_model=JobOut) async def create(body: JobIn): url = body.url.strip() if not url: raise HTTPException(status_code=400, detail="URL is empty") job_id = _uuid.uuid4().hex j = await create_job(job_id, url) jobqueue.submit( job_id, { "section_id": body.section_id, "keep_video": body.keep_video, "summary_max_sentences": body.summary_max_sentences, "model_path": body.model_path, }, ) return _to_out(j) @router.get("/", response_model=list[JobOut]) async def list_all(limit: int = 50): return [_to_out(j) for j in await list_jobs(limit)] @router.get("/{job_id}", response_model=JobOut) async def get_one(job_id: str): j = await get_job(job_id) if j is None: raise HTTPException(status_code=404, detail="Job not found") return _to_out(j) @router.post("/{job_id}/cancel") async def cancel(job_id: str): j = await get_job(job_id) if j is None: raise HTTPException(status_code=404, detail="Job not found") if j.status not in ("queued", "running"): raise HTTPException(status_code=400, detail=f"Job is {j.status}") jobqueue.request_cancel(job_id) return {"ok": True, "note": "остановится после текущего элемента"} @router.delete("/{job_id}") async def delete(job_id: str): j = await get_job(job_id) if j is None: raise HTTPException(status_code=404, detail="Job not found") if j.status in ("queued", "running"): raise HTTPException(status_code=400, detail="Сначала отмените задачу") await delete_job(job_id) return {"ok": True}