diff --git a/dev-api.py b/dev-api.py new file mode 100644 index 0000000..9b604e0 --- /dev/null +++ b/dev-api.py @@ -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": ""} diff --git a/web/vite.config.ts b/web/vite.config.ts index c22afdb..ceef245 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -4,6 +4,16 @@ import UnoCSS from 'unocss/vite' export default defineConfig({ plugins: [vue(), UnoCSS()], + server: { + host: '0.0.0.0', + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8601', + changeOrigin: true, + }, + }, + }, test: { environment: 'jsdom', globals: true,