from fastapi import APIRouter, BackgroundTasks, Depends, Request from sqlalchemy import desc, or_, select from sqlalchemy.orm import Session from app.config import Settings, get_settings from app.db import SessionLocal, get_db from app.models import CrawlRun, Employee from app.security import require_admin from app.services.crawler import run_crawl from app.version import BACKEND_VERSION, FRONTEND_VERSION router = APIRouter(prefix="/api") @router.get("/health") def health() -> dict: return {"status": "ok", "backend_version": BACKEND_VERSION, "frontend_version": FRONTEND_VERSION} @router.get("/employees") def list_employees( request: Request, status: str | None = None, q: str | None = None, limit: int = 50, offset: int = 0, db: Session = Depends(get_db), settings: Settings = Depends(get_settings), ) -> dict: require_admin(request, settings) stmt = select(Employee) if status: stmt = stmt.where(Employee.status == status) if q: pattern = f"%{q}%" stmt = stmt.where(or_(Employee.full_name.ilike(pattern), Employee.canonical_url.ilike(pattern))) employees = db.scalars(stmt.order_by(Employee.full_name).limit(limit).offset(offset)).all() return {"items": [_employee_summary(item) for item in employees], "limit": limit, "offset": offset} @router.get("/employees/{employee_id}") def get_employee( employee_id: int, request: Request, db: Session = Depends(get_db), settings: Settings = Depends(get_settings), ) -> dict: require_admin(request, settings) employee = db.get(Employee, employee_id) if not employee: return {"error": "not_found"} return _employee_detail(employee) @router.get("/crawl-runs") def list_crawl_runs( request: Request, limit: int = 20, db: Session = Depends(get_db), settings: Settings = Depends(get_settings), ) -> dict: require_admin(request, settings) runs = db.scalars(select(CrawlRun).order_by(desc(CrawlRun.started_at)).limit(limit)).all() return {"items": [_run_summary(run) for run in runs]} @router.post("/crawl-runs") def trigger_crawl( request: Request, background_tasks: BackgroundTasks, settings: Settings = Depends(get_settings), ) -> dict: require_admin(request, settings) def _crawl() -> None: with SessionLocal() as db: run_crawl(db, settings) background_tasks.add_task(_crawl) return {"status": "scheduled"} def _employee_summary(employee: Employee) -> dict: return { "id": employee.id, "full_name": employee.full_name, "status": employee.status, "canonical_url": employee.canonical_url, "last_seen_at": employee.last_seen_at.isoformat() if employee.last_seen_at else None, "dismissed_at": employee.dismissed_at.isoformat() if employee.dismissed_at else None, } def _employee_detail(employee: Employee) -> dict: data = _employee_summary(employee) data["current_data"] = employee.current_data data["tabs"] = [{"title": tab.title, "href": tab.href, "data_index": tab.data_index} for tab in employee.tabs] return data def _run_summary(run: CrawlRun) -> dict: return { "id": run.id, "source_url": run.source_url, "status": run.status, "started_at": run.started_at.isoformat() if run.started_at else None, "finished_at": run.finished_at.isoformat() if run.finished_at else None, "found_count": run.found_count, "parsed_count": run.parsed_count, "error_count": run.error_count, "dismissed_count": run.dismissed_count, "message": run.message, }