123 lines
3.6 KiB
Python
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 []),
|
|
}
|