import json from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from app.database import get_db from app.models import JobModel from app.schemas import EntityResponse, Job, JobStatus, ListResponse router = APIRouter(prefix='/jobs', tags=['jobs']) def _to_job(m: JobModel) -> Job: return Job( id=m.id, type=m.type, status=m.status, progress=m.progress, message=m.message, error=m.error, payload=json.loads(m.payload_json or '{}'), result=json.loads(m.result_json or 'null'), created_at=m.created_at, started_at=m.started_at, finished_at=m.finished_at, ) @router.get('', response_model=ListResponse[Job]) def list_jobs( status: JobStatus | None = Query(default=None), type: str | None = Query(default=None), db: Session = Depends(get_db), ): q = db.query(JobModel) if status: q = q.filter(JobModel.status == status) if type: q = q.filter(JobModel.type == type) rows = q.order_by(JobModel.created_at.desc()).all() return ListResponse(count=len(rows), items=[_to_job(r) for r in rows]) @router.get('/{job_id}', response_model=EntityResponse[Job]) def get_job(job_id: str, db: Session = Depends(get_db)): row = db.query(JobModel).filter(JobModel.id == job_id).first() if not row: raise HTTPException(status_code=404, detail='Job not found') return EntityResponse(item=_to_job(row)) @router.post('/{job_id}/cancel', response_model=EntityResponse[Job]) def cancel_job(job_id: str, db: Session = Depends(get_db)): row = db.query(JobModel).filter(JobModel.id == job_id).first() if not row: raise HTTPException(status_code=404, detail='Job not found') if row.status not in ('pending', 'running'): raise HTTPException( status_code=409, detail=f'Cannot cancel job with status {row.status!r}', ) row.status = 'cancelled' row.finished_at = datetime.now(timezone.utc) db.commit() db.refresh(row) return EntityResponse(item=_to_job(row))