● Zion Boggan
repos/Sightline/backend/app/api/routes/admin.py
zionboggan.com ↗
84 lines · python
History for this file →
1
from __future__ import annotations
2
 
3
from fastapi import APIRouter, Depends, Query
4
from sqlalchemy import select
5
from sqlalchemy.orm import Session
6
 
7
from app.api.deps import Perm, get_db, require
8
from app.core.config import settings
9
from app.models import AuditLog, Source, User
10
from app.schemas import SourceCreate, UserOut
11
 
12
router = APIRouter(tags=["admin"])
13
 
14
 
15
@router.get("/sources")
16
def list_sources(db: Session = Depends(get_db), _: User = Depends(require(Perm.READ))):
17
    return db.scalars(select(Source).order_by(Source.created_at.desc())).all()
18
 
19
 
20
@router.post("/sources", status_code=201)
21
def create_source(payload: SourceCreate, db: Session = Depends(get_db),
22
                  user: User = Depends(require(Perm.INGEST))):
23
    src = Source(name=payload.name, system_type=payload.system_type,
24
                 organization=payload.organization, jurisdiction=payload.jurisdiction,
25
                 trust_level=payload.trust_level)
26
    db.add(src)
27
    db.commit()
28
    db.refresh(src)
29
    return src
30
 
31
 
32
@router.get("/users", response_model=list[UserOut])
33
def list_users(db: Session = Depends(get_db), _: User = Depends(require(Perm.ADMIN))):
34
    return db.scalars(select(User)).all()
35
 
36
 
37
@router.get("/audit")
38
def audit_log(entity_type: str | None = None, entity_id: str | None = None,
39
              actor: str | None = None, limit: int = Query(default=200, le=1000),
40
              db: Session = Depends(get_db), _: User = Depends(require(Perm.READ))):
41
    q = select(AuditLog).order_by(AuditLog.created_at.desc()).limit(limit)
42
    if entity_type:
43
        q = q.where(AuditLog.entity_type == entity_type)
44
    if entity_id:
45
        q = q.where(AuditLog.entity_id == entity_id)
46
    if actor:
47
        q = q.where(AuditLog.actor_user_id == actor)
48
    rows = db.scalars(q).all()
49
    return [{"id": r.id, "actor": r.actor_user_id, "action": r.action,
50
             "entity_type": r.entity_type, "entity_id": r.entity_id,
51
             "metadata": r.meta, "at": r.created_at} for r in rows]
52
 
53
 
54
@router.post("/admin/retention/run")
55
def run_retention(dry_run: bool = True, db: Session = Depends(get_db),
56
                  user: User = Depends(require(Perm.ADMIN))):
57
    """Enforce the retention window. dry_run reports how many reads WOULD be deleted without
58
    deleting; set dry_run=false to actually purge. Always audited."""
59
    from datetime import datetime, timedelta, timezone
60
    from app.services.retention import count_expired, purge_expired
61
    cutoff = datetime.now(timezone.utc) - timedelta(days=settings.default_retention_days)
62
    count = count_expired(db, cutoff)
63
    deleted = 0
64
    if not dry_run:
65
        deleted = purge_expired(db, cutoff)
66
    db.add(AuditLog(actor_user_id=user.id, action="retention.run",
67
                    entity_type="settings", entity_id=None,
68
                    meta={"dry_run": dry_run, "cutoff": cutoff.isoformat(),
69
                          "candidates": count, "deleted": deleted}))
70
    db.commit()
71
    return {"cutoff": cutoff.isoformat(), "retention_days": settings.default_retention_days,
72
            "candidates": count, "deleted": deleted, "dry_run": dry_run}
73
 
74
 
75
@router.get("/admin/settings")
76
def get_settings(_: User = Depends(require(Perm.ADMIN))):
77
    return {
78
        "default_retention_days": settings.default_retention_days,
79
        "rate_limit_per_minute": settings.rate_limit_per_minute,
80
        "dedup_window_seconds": settings.dedup_window_seconds,
81
        "impossible_speed_kmh": settings.impossible_speed_kmh,
82
        "cotravel_window_seconds": settings.cotravel_window_seconds,
83
        "environment": settings.environment,
84
    }