Spaces:
Running
Running
sync: 194 file da Baida98/AI@7d89d62a (2026-08-30 06:05 UTC)
#148
by Baida07 - opened
- api/public_snapshot.py +3 -1
- api/public_snapshot_diagnostics.py +68 -0
- main.py +1 -0
api/public_snapshot.py
CHANGED
|
@@ -62,9 +62,11 @@ async def write_public_dashboard_snapshot() -> bool:
|
|
| 62 |
if attempt < 3:
|
| 63 |
await asyncio.sleep(2)
|
| 64 |
|
|
|
|
| 65 |
_logger.warning(
|
| 66 |
-
"BOOT: public snapshot upsert failed after retries
|
| 67 |
type(last_error).__name__ if last_error else "unknown",
|
|
|
|
| 68 |
)
|
| 69 |
return False
|
| 70 |
|
|
|
|
| 62 |
if attempt < 3:
|
| 63 |
await asyncio.sleep(2)
|
| 64 |
|
| 65 |
+
error_code = getattr(last_error, "code", None) or getattr(last_error, "status_code", None)
|
| 66 |
_logger.warning(
|
| 67 |
+
"BOOT: public snapshot upsert failed after retries type=%s code=%s; public status remains degraded",
|
| 68 |
type(last_error).__name__ if last_error else "unknown",
|
| 69 |
+
str(error_code)[:32] if error_code is not None else "unknown",
|
| 70 |
)
|
| 71 |
return False
|
| 72 |
|
api/public_snapshot_diagnostics.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Protected diagnostics for the public dashboard snapshot.
|
| 2 |
+
|
| 3 |
+
The route is MACHINE-only and returns a sanitized Supabase error code so an
|
| 4 |
+
operator can distinguish missing schema, permissions, and connectivity without
|
| 5 |
+
exposing URLs, keys, SQL, or row contents.
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import re
|
| 10 |
+
from typing import Any, Optional
|
| 11 |
+
|
| 12 |
+
from fastapi import APIRouter, Depends
|
| 13 |
+
|
| 14 |
+
from .auth_guard import AuthRole, require_role
|
| 15 |
+
from .state import sb
|
| 16 |
+
|
| 17 |
+
router = APIRouter(
|
| 18 |
+
prefix="/api/diagnostics/public-snapshot",
|
| 19 |
+
tags=["diagnostics"],
|
| 20 |
+
dependencies=[Depends(require_role(AuthRole.MACHINE))],
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
_PUBLIC_FIELDS = "singleton,service_status,active_sessions,queued_tasks,in_progress_tasks,app_version,updated_at"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _safe_error(exc: Exception) -> dict[str, Optional[str]]:
|
| 27 |
+
code = getattr(exc, "code", None) or getattr(exc, "status_code", None)
|
| 28 |
+
raw = str(getattr(exc, "message", None) or exc)
|
| 29 |
+
# Keep useful PostgreSQL/Supabase codes, remove URLs, credentials and long SQL.
|
| 30 |
+
message = re.sub(r"https?://\S+", "[url]", raw)
|
| 31 |
+
message = re.sub(r"(?i)(authorization|apikey|api[_-]?key|token|password)=?\s*\S+", r"\1=[redacted]", message)
|
| 32 |
+
message = re.sub(r"\s+", " ", message).strip()[:240]
|
| 33 |
+
return {
|
| 34 |
+
"code": str(code) if code is not None else None,
|
| 35 |
+
"type": type(exc).__name__,
|
| 36 |
+
"message": message,
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@router.get("")
|
| 41 |
+
async def public_snapshot_diagnostics() -> dict[str, Any]:
|
| 42 |
+
"""Return snapshot presence and a sanitized exact Supabase error, if any."""
|
| 43 |
+
client = sb()
|
| 44 |
+
if client is None:
|
| 45 |
+
return {
|
| 46 |
+
"ok": False,
|
| 47 |
+
"client_available": False,
|
| 48 |
+
"row_present": False,
|
| 49 |
+
"error": {"code": None, "type": "SupabaseClientUnavailable", "message": "client unavailable"},
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
result = client.table("public_dashboard_snapshot").select(_PUBLIC_FIELDS).eq("singleton", True).limit(1).execute()
|
| 54 |
+
rows = result.data or []
|
| 55 |
+
return {
|
| 56 |
+
"ok": True,
|
| 57 |
+
"client_available": True,
|
| 58 |
+
"row_present": bool(rows),
|
| 59 |
+
"snapshot": rows[0] if rows else None,
|
| 60 |
+
"error": None,
|
| 61 |
+
}
|
| 62 |
+
except Exception as exc:
|
| 63 |
+
return {
|
| 64 |
+
"ok": False,
|
| 65 |
+
"client_available": True,
|
| 66 |
+
"row_present": False,
|
| 67 |
+
"error": _safe_error(exc),
|
| 68 |
+
}
|
main.py
CHANGED
|
@@ -165,6 +165,7 @@ _ROUTER_MAP = {
|
|
| 165 |
"private_state": "private_state",
|
| 166 |
"auth": "auth_managed",
|
| 167 |
"public_status": "public_status",
|
|
|
|
| 168 |
"me_tasks": "me_tasks",
|
| 169 |
"admin_state": "admin_state",
|
| 170 |
# ββ Aggiunti ROUTER-COMPLETE (29 moduli orfani rimontati) βββββββββββββββββ
|
|
|
|
| 165 |
"private_state": "private_state",
|
| 166 |
"auth": "auth_managed",
|
| 167 |
"public_status": "public_status",
|
| 168 |
+
"public_snapshot_diagnostics": "public_snapshot_diagnostics",
|
| 169 |
"me_tasks": "me_tasks",
|
| 170 |
"admin_state": "admin_state",
|
| 171 |
# ββ Aggiunti ROUTER-COMPLETE (29 moduli orfani rimontati) βββββββββββββββββ
|