Release v4928.1.4.2 stable
This commit is contained in:
0
app/api/__init__.py
Normal file
0
app/api/__init__.py
Normal file
100
app/api/internal.py
Normal file
100
app/api/internal.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""Small internal operational API.
|
||||
|
||||
These endpoints are intentionally read-only and are used by future HTMX/API
|
||||
partials and external monitoring. They do not replace the current admin pages.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from hmac import compare_digest
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.config import settings
|
||||
from app.db import engine
|
||||
|
||||
def require_internal_access(request: Request) -> None:
|
||||
expected = (settings.clientflow_admin_token or "").strip()
|
||||
if not expected:
|
||||
return
|
||||
received = (
|
||||
request.headers.get("X-ClientFlow-Admin-Token")
|
||||
or request.cookies.get("clientflow_admin_token")
|
||||
or request.query_params.get("admin_token")
|
||||
or ""
|
||||
).strip()
|
||||
if not received or not compare_digest(received, expected):
|
||||
raise HTTPException(status_code=401, detail="internal api auth required")
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/internal", tags=["internal"], dependencies=[Depends(require_internal_access)])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def internal_health() -> dict:
|
||||
return {
|
||||
"status": "ok",
|
||||
"app": settings.app_name,
|
||||
"env": settings.env,
|
||||
"jasmin_enabled": bool(settings.jasmin_enabled),
|
||||
"packlink_enabled": bool(settings.packlink_enabled),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/outbox/summary")
|
||||
def outbox_summary() -> dict:
|
||||
with engine.begin() as conn:
|
||||
rows = conn.execute(text("""
|
||||
SELECT target_system, status, count(*)::int AS total
|
||||
FROM integration_outbox
|
||||
GROUP BY target_system, status
|
||||
ORDER BY target_system, status
|
||||
""")).mappings().all()
|
||||
|
||||
summary: dict[str, dict[str, int]] = {}
|
||||
for row in rows:
|
||||
target = row["target_system"] or "unknown"
|
||||
status = row["status"] or "unknown"
|
||||
summary.setdefault(target, {})[status] = row["total"]
|
||||
return {"items": summary}
|
||||
|
||||
|
||||
@router.get("/documents/summary")
|
||||
def documents_summary() -> dict:
|
||||
with engine.begin() as conn:
|
||||
rows = conn.execute(text("""
|
||||
SELECT document_kind, status, count(*)::int AS total
|
||||
FROM commercial_documents
|
||||
GROUP BY document_kind, status
|
||||
ORDER BY document_kind, status
|
||||
""")).mappings().all()
|
||||
|
||||
summary: dict[str, dict[str, int]] = {}
|
||||
for row in rows:
|
||||
kind = row["document_kind"] or "unknown"
|
||||
status = row["status"] or "unknown"
|
||||
summary.setdefault(kind, {})[status] = row["total"]
|
||||
return {"items": summary}
|
||||
|
||||
@router.get("/operations/summary")
|
||||
def operations_summary() -> dict:
|
||||
from app.operations_service import get_operations_summary
|
||||
return get_operations_summary(limit=10)
|
||||
|
||||
|
||||
@router.get("/system/health")
|
||||
def system_health_summary() -> dict:
|
||||
from app.operations_service import get_system_health_summary
|
||||
return get_system_health_summary()
|
||||
|
||||
|
||||
@router.get("/communications/summary")
|
||||
def communications_summary() -> dict:
|
||||
from app.communication_service import get_communications_summary
|
||||
return get_communications_summary()
|
||||
|
||||
|
||||
@router.get("/communications/recent")
|
||||
def communications_recent(limit: int = 20) -> dict:
|
||||
from app.communication_service import list_communications
|
||||
return {"items": list_communications(limit=limit)}
|
||||
Reference in New Issue
Block a user