feat(interviews): add /api/interviews and /api/jobs/:id/move endpoints
Adds GET /api/interviews to fetch all pipeline-stage jobs in one call, and POST /api/jobs/:id/move to transition a job between kanban statuses with automatic timestamp stamping (or rejection_stage capture).
This commit is contained in:
parent
cce0f8195a
commit
6fb366e499
1 changed files with 70 additions and 0 deletions
70
dev-api.py
70
dev-api.py
|
|
@ -274,6 +274,76 @@ def download_pdf(job_id: int):
|
||||||
raise HTTPException(501, "reportlab not installed — install it to generate PDFs")
|
raise HTTPException(501, "reportlab not installed — install it to generate PDFs")
|
||||||
|
|
||||||
|
|
||||||
|
# ── GET /api/interviews ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
PIPELINE_STATUSES = {
|
||||||
|
"applied", "survey",
|
||||||
|
"phone_screen", "interviewing",
|
||||||
|
"offer", "hired",
|
||||||
|
"interview_rejected",
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/api/interviews")
|
||||||
|
def list_interviews():
|
||||||
|
db = _get_db()
|
||||||
|
placeholders = ",".join("?" * len(PIPELINE_STATUSES))
|
||||||
|
rows = db.execute(
|
||||||
|
f"SELECT id, title, company, url, location, is_remote, salary, "
|
||||||
|
f"match_score, keyword_gaps, status, "
|
||||||
|
f"interview_date, rejection_stage, "
|
||||||
|
f"applied_at, phone_screen_at, interviewing_at, offer_at, hired_at, survey_at "
|
||||||
|
f"FROM jobs WHERE status IN ({placeholders}) "
|
||||||
|
f"ORDER BY match_score DESC NULLS LAST",
|
||||||
|
list(PIPELINE_STATUSES),
|
||||||
|
).fetchall()
|
||||||
|
db.close()
|
||||||
|
# Cast is_remote to bool for consistency with other endpoints
|
||||||
|
return [{**dict(r), "is_remote": bool(r["is_remote"])} for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
# ── POST /api/jobs/{id}/move ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
STATUS_TIMESTAMP_COL = {
|
||||||
|
"applied": "applied_at",
|
||||||
|
"survey": "survey_at",
|
||||||
|
"phone_screen": "phone_screen_at",
|
||||||
|
"interviewing": "interviewing_at",
|
||||||
|
"offer": "offer_at",
|
||||||
|
"hired": "hired_at",
|
||||||
|
"interview_rejected": None, # uses rejection_stage instead
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveBody(BaseModel):
|
||||||
|
status: str
|
||||||
|
interview_date: str | None = None
|
||||||
|
rejection_stage: str | None = None
|
||||||
|
|
||||||
|
@app.post("/api/jobs/{job_id}/move")
|
||||||
|
def move_job(job_id: int, body: MoveBody):
|
||||||
|
if body.status not in STATUS_TIMESTAMP_COL:
|
||||||
|
raise HTTPException(400, f"Invalid pipeline status: {body.status}")
|
||||||
|
db = _get_db()
|
||||||
|
ts_col = STATUS_TIMESTAMP_COL[body.status]
|
||||||
|
if ts_col:
|
||||||
|
db.execute(
|
||||||
|
f"UPDATE jobs SET status = ?, {ts_col} = datetime('now') WHERE id = ?",
|
||||||
|
(body.status, job_id),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
db.execute(
|
||||||
|
"UPDATE jobs SET status = ?, rejection_stage = ? WHERE id = ?",
|
||||||
|
(body.status, body.rejection_stage, job_id),
|
||||||
|
)
|
||||||
|
if body.interview_date is not None:
|
||||||
|
db.execute(
|
||||||
|
"UPDATE jobs SET interview_date = ? WHERE id = ?",
|
||||||
|
(body.interview_date, job_id),
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
# ── GET /api/config/user ──────────────────────────────────────────────────────
|
# ── GET /api/config/user ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
@app.get("/api/config/user")
|
@app.get("/api/config/user")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue