Auto merge dev → main (#1353)
* feat(gateway): hashed key store with grant + hot reload Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gateway): channel registry + aiohttp app (keyed auth, whoami, per-channel ws/proxy) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gateway): manage_keys CLI (add/list/revoke) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gateway): retire srebot_external, run relay-gateway under PM2 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(gateway): point ecosystem + README at relay-gateway Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss): replay outbox producer for relay gateway Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss): forward processed games to relay outbox Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss-api): db helpers, app skeleton, info endpoint, fixtures Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss-api): player, games, history, search endpoints Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss-api): live, match, scoreboard, matches-search, maps Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss-api): filter-required leaderboards (players/vehicles/stats) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(tss-api): tournament list/detail/standings/matches Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: wire tss upstream through gateway + tssbot-api PM2 app Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from web.api import db
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _window(start_date, end_date, tournament_id):
|
||||
"""Return (where_sql, params) or raise 400 if no filter given."""
|
||||
clauses: list[str] = []
|
||||
params: list = []
|
||||
if tournament_id is not None:
|
||||
clauses.append("p.session_id IN (SELECT session_id FROM match_summary WHERE tournament_id = ?)")
|
||||
params.append(tournament_id)
|
||||
if start_date is not None:
|
||||
clauses.append("p.endtime_unix >= ?"); params.append(start_date)
|
||||
if end_date is not None:
|
||||
clauses.append("p.endtime_unix <= ?"); params.append(end_date)
|
||||
if not clauses:
|
||||
raise HTTPException(status_code=400, detail={
|
||||
"error": "a date window (start_date/end_date) or tournament_id is required",
|
||||
"code": "FILTER_REQUIRED"})
|
||||
return "WHERE " + " AND ".join(clauses), params
|
||||
|
||||
|
||||
@router.get("/api/tss/leaderboard/players")
|
||||
async def leaderboard_players(start_date: int | None = None, end_date: int | None = None,
|
||||
tournament_id: int | None = None, limit: int = 100) -> dict:
|
||||
where, params = _window(start_date, end_date, tournament_id)
|
||||
params.append(max(1, min(limit, 500)))
|
||||
rows = await db.query(db.battles_path(), f"""
|
||||
SELECT p.UID uid, MAX(p.nick) nick, COUNT(*) battles,
|
||||
SUM(CASE WHEN p.victor_bool='Win' THEN 1 ELSE 0 END) wins,
|
||||
SUM(p.air_kills+p.ground_kills) kills, SUM(p.deaths) deaths, SUM(p.score) score
|
||||
FROM player_games_hist p {where}
|
||||
GROUP BY p.UID ORDER BY score DESC LIMIT ?""", tuple(params))
|
||||
for r in rows:
|
||||
r["uid"] = str(r["uid"])
|
||||
return {"players": rows}
|
||||
|
||||
|
||||
@router.get("/api/tss/leaderboard/vehicles")
|
||||
async def leaderboard_vehicles(start_date: int | None = None, end_date: int | None = None,
|
||||
tournament_id: int | None = None, limit: int = 100) -> dict:
|
||||
where, params = _window(start_date, end_date, tournament_id)
|
||||
params.append(max(1, min(limit, 500)))
|
||||
rows = await db.query(db.battles_path(), f"""
|
||||
SELECT p.vehicle_internal, MAX(p.vehicle) vehicle, COUNT(*) battles,
|
||||
SUM(p.air_kills+p.ground_kills) kills, SUM(p.deaths) deaths, SUM(p.score) score
|
||||
FROM player_games_hist p {where}
|
||||
GROUP BY p.vehicle_internal ORDER BY battles DESC LIMIT ?""", tuple(params))
|
||||
return {"vehicles": rows}
|
||||
|
||||
|
||||
@router.get("/api/tss/leaderboard/stats")
|
||||
async def leaderboard_stats(start_date: int | None = None, end_date: int | None = None,
|
||||
tournament_id: int | None = None) -> dict:
|
||||
where, params = _window(start_date, end_date, tournament_id)
|
||||
totals = await db.query_one(db.battles_path(), f"""
|
||||
SELECT COUNT(*) battles, COUNT(DISTINCT p.UID) players,
|
||||
SUM(p.air_kills+p.ground_kills) kills, SUM(p.score) score
|
||||
FROM player_games_hist p {where}""", tuple(params))
|
||||
top = await db.query(db.battles_path(), f"""
|
||||
SELECT p.vehicle_internal, MAX(p.vehicle) vehicle, COUNT(*) battles
|
||||
FROM player_games_hist p {where}
|
||||
GROUP BY p.vehicle_internal ORDER BY battles DESC LIMIT 10""", tuple(params))
|
||||
return {"totals": totals or {}, "top_vehicles": top}
|
||||
Reference in New Issue
Block a user