Files
wov-api/app/routers/apps.py
T

138 lines
4.0 KiB
Python

from __future__ import annotations
import uuid
from datetime import datetime, timezone
from pathlib import Path
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.responses import FileResponse
from app.db import Database
router = APIRouter(tags=["apps"])
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def _get_db() -> Database:
from app.main import app
return app.state.db
@router.get("/api/apps")
def list_apps(db: Database = Depends(_get_db)) -> list[dict]:
apps = []
for workflow in db.list_workflows():
if not workflow["published"]:
continue
latest = db.get_latest_workflow_version(workflow["id"])
apps.append(
{
"id": workflow["id"],
"name": workflow["name"],
"description": workflow["description"],
"version": workflow["latest_version"],
"definition": latest["definition"] if latest else None,
}
)
return apps
@router.post("/api/apps/{workflow_id}/runs")
async def create_run(
workflow_id: str,
file: UploadFile = File(...),
db: Database = Depends(_get_db),
) -> dict:
workflow = db.get_workflow(workflow_id)
if workflow is None or not workflow["published"]:
raise HTTPException(status_code=404, detail="published workflow not found")
latest = db.get_latest_workflow_version(workflow_id)
if latest is None:
raise HTTPException(status_code=422, detail="workflow has no version")
run_id = f"run_{uuid.uuid4().hex[:12]}"
filename = Path(file.filename or "upload.bin").name
from app.config import STORAGE_DIR
input_dir = STORAGE_DIR / "uploads" / run_id
input_dir.mkdir(parents=True, exist_ok=True)
input_uri = input_dir / filename
content = await file.read()
input_uri.write_bytes(content)
now = _now_iso()
db.create_run(
{
"id": run_id,
"workflow_id": workflow_id,
"workflow_version": latest["version"],
"status": "QUEUED",
"progress": 0,
"input_uri": str(input_uri),
"created_at": now,
"updated_at": now,
}
)
return {
"id": run_id,
"status": "QUEUED",
"progress": 0,
"artifacts": [],
}
@router.get("/api/runs")
def list_runs(db: Database = Depends(_get_db)) -> list[dict]:
return db.list_runs()
@router.get("/api/runs/{run_id}")
def get_run(run_id: str, db: Database = Depends(_get_db)) -> dict:
run = db.get_run(run_id)
if run is None:
raise HTTPException(status_code=404, detail="run not found")
run["artifacts"] = db.list_artifacts(run_id)
return run
@router.post("/api/runs/{run_id}/retry")
def retry_run(run_id: str, db: Database = Depends(_get_db)) -> dict:
run = db.get_run(run_id)
if run is None:
raise HTTPException(status_code=404, detail="run not found")
if run["status"] != "FAILED":
raise HTTPException(status_code=422, detail="only failed runs can be retried")
db.reset_run(run_id, _now_iso())
return {"id": run_id, "status": "QUEUED"}
@router.get("/api/runs/{run_id}/artifacts")
def list_run_artifacts(run_id: str, db: Database = Depends(_get_db)) -> list[dict]:
if db.get_run(run_id) is None:
raise HTTPException(status_code=404, detail="run not found")
return db.list_artifacts(run_id)
@router.get("/api/runs/{run_id}/artifacts/{artifact_name}")
def download_artifact(
run_id: str,
artifact_name: str,
db: Database = Depends(_get_db),
) -> FileResponse:
artifact = db.get_artifact(run_id, artifact_name)
if artifact is None:
raise HTTPException(status_code=404, detail="artifact not found")
path = Path(artifact["uri"])
if not path.is_file():
raise HTTPException(status_code=404, detail="artifact file missing")
return FileResponse(
path,
media_type=artifact["mime_type"],
filename=path.name,
)