- dev-api.py: minimal FastAPI on :8601 reading /devl/job-seeker/staging.db
Endpoints: GET /api/jobs, /api/jobs/counts, POST /api/jobs/{id}/approve|reject|revert,
GET /api/system/status, /api/config/user
- vite.config.ts: server.proxy /api/* → localhost:8601; host: 0.0.0.0 for LAN access
139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
"""
|
|
Minimal dev-only FastAPI server for the Vue SPA.
|
|
Reads directly from /devl/job-seeker/staging.db.
|
|
Run with:
|
|
conda run -n job-seeker uvicorn dev-api:app --port 8600 --reload
|
|
"""
|
|
import sqlite3
|
|
import os
|
|
import json
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
|
|
DB_PATH = os.environ.get("STAGING_DB", "/devl/job-seeker/staging.db")
|
|
|
|
app = FastAPI(title="Peregrine Dev API")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:5173", "http://10.1.10.71:5173"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
def _get_db():
|
|
db = sqlite3.connect(DB_PATH)
|
|
db.row_factory = sqlite3.Row
|
|
return db
|
|
|
|
|
|
def _row_to_job(row) -> dict:
|
|
d = dict(row)
|
|
d["is_remote"] = bool(d.get("is_remote", 0))
|
|
return d
|
|
|
|
|
|
# ── GET /api/jobs ─────────────────────────────────────────────────────────────
|
|
|
|
@app.get("/api/jobs")
|
|
def list_jobs(status: str = "pending", limit: int = 50):
|
|
db = _get_db()
|
|
rows = db.execute(
|
|
"SELECT id, title, company, url, source, location, is_remote, salary, "
|
|
"description, match_score, keyword_gaps, date_found, status "
|
|
"FROM jobs WHERE status = ? ORDER BY match_score DESC NULLS LAST LIMIT ?",
|
|
(status, limit),
|
|
).fetchall()
|
|
db.close()
|
|
return [_row_to_job(r) for r in rows]
|
|
|
|
|
|
# ── GET /api/jobs/counts ──────────────────────────────────────────────────────
|
|
|
|
@app.get("/api/jobs/counts")
|
|
def job_counts():
|
|
db = _get_db()
|
|
rows = db.execute("SELECT status, count(*) as n FROM jobs GROUP BY status").fetchall()
|
|
db.close()
|
|
counts = {r["status"]: r["n"] for r in rows}
|
|
return {
|
|
"pending": counts.get("pending", 0),
|
|
"approved": counts.get("approved", 0),
|
|
"applied": counts.get("applied", 0),
|
|
"synced": counts.get("synced", 0),
|
|
"rejected": counts.get("rejected", 0),
|
|
"total": sum(counts.values()),
|
|
}
|
|
|
|
|
|
# ── POST /api/jobs/{id}/approve ───────────────────────────────────────────────
|
|
|
|
@app.post("/api/jobs/{job_id}/approve")
|
|
def approve_job(job_id: int):
|
|
db = _get_db()
|
|
db.execute("UPDATE jobs SET status = 'approved' WHERE id = ?", (job_id,))
|
|
db.commit()
|
|
db.close()
|
|
return {"ok": True}
|
|
|
|
|
|
# ── POST /api/jobs/{id}/reject ────────────────────────────────────────────────
|
|
|
|
@app.post("/api/jobs/{job_id}/reject")
|
|
def reject_job(job_id: int):
|
|
db = _get_db()
|
|
db.execute("UPDATE jobs SET status = 'rejected' WHERE id = ?", (job_id,))
|
|
db.commit()
|
|
db.close()
|
|
return {"ok": True}
|
|
|
|
|
|
# ── POST /api/jobs/{id}/revert ────────────────────────────────────────────────
|
|
|
|
class RevertBody(BaseModel):
|
|
status: str
|
|
|
|
@app.post("/api/jobs/{job_id}/revert")
|
|
def revert_job(job_id: int, body: RevertBody):
|
|
allowed = {"pending", "approved", "rejected", "applied", "synced"}
|
|
if body.status not in allowed:
|
|
raise HTTPException(400, f"Invalid status: {body.status}")
|
|
db = _get_db()
|
|
db.execute("UPDATE jobs SET status = ? WHERE id = ?", (body.status, job_id))
|
|
db.commit()
|
|
db.close()
|
|
return {"ok": True}
|
|
|
|
|
|
# ── GET /api/system/status ────────────────────────────────────────────────────
|
|
|
|
@app.get("/api/system/status")
|
|
def system_status():
|
|
return {
|
|
"enrichment_enabled": False,
|
|
"enrichment_last_run": None,
|
|
"enrichment_next_run": None,
|
|
"tasks_running": 0,
|
|
"integration_name": "Notion",
|
|
"integration_unsynced": 0,
|
|
}
|
|
|
|
|
|
# ── GET /api/config/user ──────────────────────────────────────────────────────
|
|
|
|
@app.get("/api/config/user")
|
|
def config_user():
|
|
# Try to read name from user.yaml if present
|
|
try:
|
|
import yaml
|
|
cfg_path = os.path.join(os.path.dirname(DB_PATH), "config", "user.yaml")
|
|
if not os.path.exists(cfg_path):
|
|
cfg_path = "/devl/job-seeker/config/user.yaml"
|
|
with open(cfg_path) as f:
|
|
cfg = yaml.safe_load(f)
|
|
return {"name": cfg.get("name", "")}
|
|
except Exception:
|
|
return {"name": ""}
|