Files
miem_workers/app/api.py
T
codex 508aae4f8a
CI / Fingerprint diagnostics (production deployment temporarily disabled) (pull_request) Canceled after 0s
CI / test (pull_request) Canceled after 10m43s
feat: add council user role and restrict crawl runs
2026-11-12 09:35:26 +00:00

156 lines
4.6 KiB
Python

from datetime import date
from fastapi import APIRouter, BackgroundTasks, Depends, Query, Request
from sqlalchemy import desc, 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, require_user, check_csrf
from app.services.admin_data import employee_display_payload, list_employees_page, run_detail_payload, run_payload, stats_payload
from app.services.crawl_control import get_running_run, run_crawl_if_idle
from app.version import BACKEND_VERSION, FRONTEND_VERSION
router = APIRouter(prefix="/api", dependencies=[Depends(check_csrf)])
@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,
started_from: date | None = None,
started_to: date | None = None,
has_email: bool | None = None,
has_academic_degree: bool | None = None,
pps: bool = False,
has_training: bool | None = None,
training_since_year: int | None = Query(default=None, ge=1000, le=9999),
sort: str = "full_name",
direction: str = "asc",
limit: int = 50,
offset: int = 0,
db: Session = Depends(get_db),
settings: Settings = Depends(get_settings),
) -> dict:
require_user(request, settings, db)
return list_employees_page(
db,
status=status,
q=q,
started_from=started_from,
started_to=started_to,
has_email=has_email,
has_academic_degree=has_academic_degree,
pps=pps,
has_training=has_training,
training_since_year=training_since_year,
sort=sort,
direction=direction,
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_user(request, settings, db)
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, db)
runs = db.scalars(select(CrawlRun).order_by(desc(CrawlRun.started_at)).limit(limit)).all()
return {"items": [run_payload(run) for run in runs]}
@router.get("/crawl-runs/latest")
def latest_crawl_run(
request: Request,
db: Session = Depends(get_db),
settings: Settings = Depends(get_settings),
) -> dict:
require_admin(request, settings, db)
running = get_running_run(db)
latest = db.scalar(select(CrawlRun).order_by(desc(CrawlRun.started_at)).limit(1))
return {"running": run_payload(running), "latest": run_payload(latest)}
@router.get("/crawl-runs/{run_id}")
def get_crawl_run(
run_id: int,
request: Request,
db: Session = Depends(get_db),
settings: Settings = Depends(get_settings),
) -> dict:
require_admin(request, settings, db)
run = db.get(CrawlRun, run_id)
if not run:
return {"error": "not_found"}
return run_detail_payload(db, run) or {"error": "not_found"}
@router.post("/crawl-runs")
def trigger_crawl(
request: Request,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
settings: Settings = Depends(get_settings),
) -> dict:
require_admin(request, settings, db)
running = get_running_run(db)
if running:
return {"status": "already_running", "run": run_payload(running)}
def _crawl() -> None:
with SessionLocal() as db:
run_crawl_if_idle(db, settings)
background_tasks.add_task(_crawl)
return {"status": "scheduled"}
@router.get("/stats")
def stats(
request: Request,
db: Session = Depends(get_db),
settings: Settings = Depends(get_settings),
) -> dict:
user = require_user(request, settings, db)
return stats_payload(db, include_runs=user.role == "admin")
def _employee_summary(employee: Employee) -> dict:
return employee_display_payload(employee)
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 run_payload(run) or {}