подгрузка данных для api из env

This commit is contained in:
2026-08-19 16:28:09 +03:00
parent bddc0967b3
commit c643a183d8
15 changed files with 891 additions and 86 deletions

View File

@@ -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: