chore(vue-spa): dev API + Vite proxy for live data during development
- 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
This commit is contained in:
parent
75cc0760e1
commit
1f5ab2df37
2 changed files with 149 additions and 0 deletions
139
dev-api.py
Normal file
139
dev-api.py
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
"""
|
||||||
|
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": ""}
|
||||||
|
|
@ -4,6 +4,16 @@ import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), UnoCSS()],
|
plugins: [vue(), UnoCSS()],
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8601',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
globals: true,
|
globals: true,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue