forge/backend/app/api/v1/jobs.py
DJP 7a804e896d Initial commit - FORGE AI unified platform
Features:
- Image generation (OpenAI, Gemini, Leonardo, Bria, Stability, Flux)
- Nano Banana iterative editing
- Video generation and upscaling
- Audio TTS, STT, sound effects (ElevenLabs)
- Text prompt studio and alt text
- User authentication with JWT/cookies
- Admin panel with voice management
- Job queue with Celery
- PostgreSQL + Redis backend
- Next.js 15 + FastAPI architecture

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2025-12-09 20:39:00 -05:00

133 lines
3.9 KiB
Python

"""Job API Routes"""
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Optional
from uuid import UUID
from datetime import datetime
from app.database import get_db
from app.models.job import Job
from app.models.user import User
from app.schemas.job import JobCreate, JobResponse, JobUpdate
from app.services.job_processor import process_job
router = APIRouter()
@router.get("/")
def get_jobs(
page: int = 1,
limit: int = 50,
status: Optional[str] = None,
module: Optional[str] = None,
db: Session = Depends(get_db)
):
"""Get all jobs with optional filtering and pagination"""
query = db.query(Job)
if status:
query = query.filter(Job.status == status)
if module:
query = query.filter(Job.module == module)
# Get total count
total = query.count()
# Calculate offset from page
skip = (page - 1) * limit
jobs = query.order_by(Job.created_at.desc()).offset(skip).limit(limit).all()
return {
"items": [
{
"id": str(job.id),
"module": job.module,
"action": job.action,
"status": job.status,
"progress": job.progress or 0,
"input_data": job.input_data,
"output_data": job.output_data,
"input_asset_ids": [str(a) for a in job.input_asset_ids] if job.input_asset_ids else None,
"output_asset_ids": [str(a) for a in job.output_asset_ids] if job.output_asset_ids else None,
"error_message": job.error_message,
"api_provider": job.api_provider,
"api_model": job.api_model,
"created_at": job.created_at.isoformat() if job.created_at else None,
"completed_at": job.completed_at.isoformat() if job.completed_at else None,
}
for job in jobs
],
"total": total,
"page": page,
"limit": limit
}
@router.get("/{job_id}", response_model=JobResponse)
def get_job(job_id: UUID, db: Session = Depends(get_db)):
"""Get job by ID"""
job = db.query(Job).filter(Job.id == job_id).first()
if not job:
raise HTTPException(status_code=404, detail="Job not found")
return job
@router.post("/", response_model=JobResponse)
def create_job(
job: JobCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db)
):
"""Create a new job and queue it for processing"""
# Get test user if no user_id provided
if not job.user_id:
user = db.query(User).filter(User.email == "test@forge.ai").first()
if user:
job.user_id = user.id
# Create job
db_job = Job(
**job.model_dump(),
status="queued",
queued_at=datetime.utcnow()
)
db.add(db_job)
db.commit()
db.refresh(db_job)
# Queue for background processing
background_tasks.add_task(process_job, str(db_job.id))
return db_job
@router.patch("/{job_id}", response_model=JobResponse)
def update_job(job_id: UUID, job: JobUpdate, db: Session = Depends(get_db)):
"""Update a job"""
db_job = db.query(Job).filter(Job.id == job_id).first()
if not db_job:
raise HTTPException(status_code=404, detail="Job not found")
for key, value in job.model_dump(exclude_unset=True).items():
setattr(db_job, key, value)
db.commit()
db.refresh(db_job)
return db_job
@router.delete("/{job_id}")
def cancel_job(job_id: UUID, db: Session = Depends(get_db)):
"""Cancel a job"""
db_job = db.query(Job).filter(Job.id == job_id).first()
if not db_job:
raise HTTPException(status_code=404, detail="Job not found")
if db_job.status in ["completed", "failed"]:
raise HTTPException(status_code=400, detail="Cannot cancel completed or failed job")
db_job.status = "cancelled"
db.commit()
return {"message": "Job cancelled"}