Files
bxh/app/api/publish_jobs.py

123 lines
3.6 KiB
Python

"""STEP 05 — Publish Jobs + Rollback + Diff."""
import json
from fastapi import APIRouter, Depends, HTTPException
from app.auth import CurrentUser
from app.config import settings
from app.contracts import RollbackRequest
from app.db import (
get_publish_job,
list_publish_jobs,
update_publish_job,
update_candidate_entity,
get_conn,
)
from app.project_context import ProjectContext, get_project_context
router = APIRouter()
@router.get("/publish-jobs")
async def _list(
limit: int = 50,
context: ProjectContext = Depends(get_project_context),
_user: CurrentUser = None,
):
return await list_publish_jobs(context.tenant_id, context.project_id, limit)
@router.get("/publish-jobs/{job_id}")
async def _get(job_id: int, _user: CurrentUser = None):
job = await get_publish_job(job_id)
if not job:
raise HTTPException(404, "Publish job not found")
return job
@router.post("/publish-jobs")
async def _create(
body: dict,
user: CurrentUser,
context: ProjectContext = Depends(get_project_context),
):
"""Create a publish job for approved candidates."""
s = settings.db_schema
candidate_ids = body.get("candidate_ids", [])
diff = {
"entities_added": len(candidate_ids),
"entities_updated": 0,
"relations_added": 0,
"field_changes": {},
}
async with get_conn() as conn:
async with conn.cursor() as cur:
await cur.execute(
f"""INSERT INTO {s}.publish_jobs
(tenant_id, project_id, candidate_ids, status, actor, diff_summary_jsonb)
VALUES (%s, %s, %s, 'pending', %s, %s)
RETURNING *""",
(
context.tenant_id,
context.project_id,
json.dumps(candidate_ids),
user["username"],
json.dumps(diff),
),
)
job = await cur.fetchone()
await conn.commit()
# Mark candidates as published
for cid in candidate_ids:
await update_candidate_entity(cid, {"status": "published"})
# Mark job as completed
await update_publish_job(job["id"], {"status": "completed"})
return {**job, "status": "completed"}
@router.get("/publish-jobs/{job_id}/diff")
async def _diff(job_id: int, _user: CurrentUser = None):
"""Return the diff summary before/after a publish."""
job = await get_publish_job(job_id)
if not job:
raise HTTPException(404, "Publish job not found")
diff = job.get("diff_summary_jsonb")
if isinstance(diff, str):
diff = json.loads(diff)
return {"job_id": job_id, "diff": diff}
@router.post("/publish-jobs/{job_id}/rollback")
async def _rollback(job_id: int, body: RollbackRequest | None = None, user: CurrentUser = None):
"""Rollback a publish — revert candidate statuses."""
job = await get_publish_job(job_id)
if not job:
raise HTTPException(404, "Publish job not found")
if job["status"] != "completed":
raise HTTPException(400, "Can only rollback completed jobs")
cids = job.get("candidate_ids")
if isinstance(cids, str):
cids = json.loads(cids)
# Revert candidates back to approved
for cid in (cids or []):
await update_candidate_entity(cid, {"status": "approved"})
await update_publish_job(job_id, {
"status": "rolled_back",
"rollback_target_release_id": job_id,
})
return {
"rolled_back": True,
"job_id": job_id,
"reason": body.reason if body else None,
"reverted_candidates": len(cids or []),
}