Release v4928.1.4.2 stable

This commit is contained in:
2026-06-09 22:55:58 +01:00
commit 6445044ac6
280 changed files with 41775 additions and 0 deletions

0
app/api/__init__.py Normal file
View File

100
app/api/internal.py Normal file
View 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)}