подгрузка данных для api из env
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import date
|
||||
from time import perf_counter
|
||||
from typing import Any, Callable
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy import text
|
||||
|
||||
from .auth_bridge import HockeyUser
|
||||
from .service import HockeyDataService
|
||||
@@ -347,6 +350,158 @@ def create_hockey_router(
|
||||
except Exception as error:
|
||||
raise HTTPException(status_code=502, detail=str(error)) from error
|
||||
|
||||
@router.get("/admin/connection-diagnostics", dependencies=admin)
|
||||
async def admin_connection_diagnostics(
|
||||
tournament_id: str = Query("", max_length=64),
|
||||
game_id: str = Query("", max_length=64),
|
||||
language: str = Query("ru", pattern="^(ru|en)$"),
|
||||
deep: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Admin-only health check for the dependencies used by match loading.
|
||||
|
||||
The lightweight check never requires an Agent/vMix to open a match: the
|
||||
database is the only hard dependency for cached schedule selection, while
|
||||
Stat2TV is required only for remote enrichment/sync. ``deep=true`` also
|
||||
exercises the selected match enrichment path and returns its warnings.
|
||||
"""
|
||||
checks: dict[str, Any] = {}
|
||||
|
||||
started = perf_counter()
|
||||
try:
|
||||
with service.database.engine.connect() as connection:
|
||||
connection.execute(text("SELECT 1"))
|
||||
checks["database"] = {
|
||||
"ok": True,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"target": service.database.schema_status(),
|
||||
}
|
||||
except Exception as error:
|
||||
checks["database"] = {
|
||||
"ok": False,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"error": str(error),
|
||||
}
|
||||
|
||||
settings_status = service.settings.status()
|
||||
checks["settings"] = {
|
||||
"ok": bool(settings_status.get("base_url")),
|
||||
"base_url": settings_status.get("base_url", ""),
|
||||
"credentials_configured": bool(settings_status.get("credentials_configured")),
|
||||
"credentials_source": settings_status.get("credentials_source", "missing"),
|
||||
"username_masked": settings_status.get("username_masked", ""),
|
||||
"verify_ssl": bool(settings_status.get("verify_ssl")),
|
||||
}
|
||||
|
||||
started = perf_counter()
|
||||
try:
|
||||
stat2tv = await asyncio.wait_for(service.test_connection(), timeout=12.0)
|
||||
checks["stat2tv"] = {
|
||||
"ok": True,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"status_code": stat2tv.get("status_code"),
|
||||
"auth_mode": stat2tv.get("auth_mode"),
|
||||
"content_type": stat2tv.get("content_type"),
|
||||
"items_found": stat2tv.get("items_found", 0),
|
||||
"url": stat2tv.get("url", ""),
|
||||
}
|
||||
except Exception as error:
|
||||
checks["stat2tv"] = {
|
||||
"ok": False,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"error": str(error),
|
||||
}
|
||||
|
||||
if agent_hub is not None:
|
||||
try:
|
||||
devices_payload = await agent_hub.list_mapping_devices()
|
||||
devices = list(devices_payload.get("devices") or [])
|
||||
checks["agents"] = {
|
||||
"ok": any(bool(item.get("online")) for item in devices),
|
||||
"optional": True,
|
||||
"online": sum(1 for item in devices if item.get("online")),
|
||||
"vmix_connected": sum(1 for item in devices if item.get("online") and item.get("vmix_connected")),
|
||||
"devices": [
|
||||
{
|
||||
"device_id": item.get("device_id", ""),
|
||||
"name": item.get("name", ""),
|
||||
"owner": item.get("owner", ""),
|
||||
"online": bool(item.get("online")),
|
||||
"vmix_connected": bool(item.get("vmix_connected")),
|
||||
"vmix_version": item.get("vmix_version", ""),
|
||||
}
|
||||
for item in devices
|
||||
],
|
||||
}
|
||||
except Exception as error:
|
||||
checks["agents"] = {"ok": False, "optional": True, "error": str(error), "devices": []}
|
||||
else:
|
||||
checks["agents"] = {"ok": False, "optional": True, "error": "Agent hub недоступен", "devices": []}
|
||||
|
||||
tournament_id = str(tournament_id or "").strip()
|
||||
game_id = str(game_id or "").strip()
|
||||
if tournament_id:
|
||||
tournament = service.tournament(tournament_id, language=language)
|
||||
endpoint = service.endpoint_diagnostics(tournament_external_id=tournament_id)
|
||||
checks["tournament"] = {
|
||||
"ok": tournament is not None,
|
||||
"tournament_id": tournament_id,
|
||||
"local_found": tournament is not None,
|
||||
"endpoint_diagnostics": endpoint,
|
||||
}
|
||||
|
||||
if game_id:
|
||||
local_game = service.game(game_id, language=language)
|
||||
match_check: dict[str, Any] = {
|
||||
"ok": local_game is not None,
|
||||
"game_id": game_id,
|
||||
"tournament_id": tournament_id,
|
||||
"local_found": local_game is not None,
|
||||
"deep_requested": bool(deep),
|
||||
}
|
||||
if deep and tournament_id:
|
||||
started = perf_counter()
|
||||
try:
|
||||
payload = await asyncio.wait_for(
|
||||
service.sync_selected_game_details(
|
||||
tournament_external_id=tournament_id,
|
||||
game_external_id=game_id,
|
||||
language=language,
|
||||
),
|
||||
timeout=20.0,
|
||||
)
|
||||
details = payload.get("details") if isinstance(payload, dict) else None
|
||||
match_check.update({
|
||||
"ok": True,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"degraded": bool(payload.get("degraded")) if isinstance(payload, dict) else False,
|
||||
"details_sync_failed": bool(payload.get("details_sync_failed")) if isinstance(payload, dict) else False,
|
||||
"warnings": list(payload.get("warnings") or []) if isinstance(payload, dict) else [],
|
||||
"details_loaded": bool(details.get("loaded")) if isinstance(details, dict) else False,
|
||||
})
|
||||
except Exception as error:
|
||||
match_check.update({
|
||||
"ok": False,
|
||||
"latency_ms": round((perf_counter() - started) * 1000, 1),
|
||||
"error": str(error),
|
||||
})
|
||||
checks["match"] = match_check
|
||||
|
||||
database_ok = bool(checks.get("database", {}).get("ok"))
|
||||
stat2tv_ok = bool(checks.get("stat2tv", {}).get("ok"))
|
||||
return {
|
||||
"ok": database_ok,
|
||||
"required_for_match_selection": {
|
||||
"ok": database_ok,
|
||||
"database": database_ok,
|
||||
},
|
||||
"required_for_remote_sync": {
|
||||
"ok": database_ok and stat2tv_ok,
|
||||
"database": database_ok,
|
||||
"stat2tv": stat2tv_ok,
|
||||
},
|
||||
"checks": checks,
|
||||
}
|
||||
|
||||
@router.post("/sync/tournaments", dependencies=admin)
|
||||
async def sync_tournaments() -> dict[str, Any]:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user