from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.db import Base, engine, SessionLocal from app.schemas import CreateJobRequest, JobInfo, TaskInfo from app.services import create_job, get_job_with_tasks from app.models import Job, Task from typing import List from uuid import UUID import re, idna app = FastAPI(title="Task Queue API", version="0.3.0") async def get_session() -> AsyncSession: async with SessionLocal() as session: yield session @app.on_event("startup") async def on_startup(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) DOMAIN_RE = re.compile(r"^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(? str: try: _ = idna.encode(domain).decode() except Exception: raise HTTPException(status_code=400, detail="Invalid domain (IDNA)") if not DOMAIN_RE.match(domain): raise HTTPException(status_code=400, detail="Invalid domain format") return domain @app.post("/jobs", response_model=JobInfo) async def create_job_endpoint(req: CreateJobRequest, session: AsyncSession = Depends(get_session)): if not req.job_type: raise HTTPException(status_code=400, detail="job_type is required") domain = _validate_domain(req.domain.strip().lower()) job = await create_job(session, req.job_type, domain, req.payload) job = await get_job_with_tasks(session, job.id) return _job_to_schema(job) @app.get("/jobs/{job_id}", response_model=JobInfo) async def get_job_endpoint(job_id: UUID, session: AsyncSession = Depends(get_session)): job = await get_job_with_tasks(session, job_id) if not job: raise HTTPException(status_code=404, detail="Job not found") return _job_to_schema(job) def _job_to_schema(job: Job) -> JobInfo: tasks: List[TaskInfo] = [ TaskInfo( id=t.id, name=t.name, status=t.status.value if hasattr(t.status, "value") else str(t.status), retries=t.retries, max_retries=t.max_retries, result=t.result, error=t.error, ) for t in job.tasks ] return JobInfo( id=job.id, job_type=job.job_type, status=job.status.value if hasattr(job.status, "value") else str(job.status), domain=job.domain, payload=job.payload, tasks=tasks, )