From 289fe6b00319207ed235eeeb3f743688957136bb Mon Sep 17 00:00:00 2001 From: clxud Date: Thu, 2 Jul 2026 02:49:01 +0000 Subject: [PATCH] Remove web/ directory (TSS API now in tssbot.web-backend) --- web/.gitkeep | 0 web/README.md | 1 - web/api/__init__.py | 0 web/api/app.py | 60 -------------------- web/api/db.py | 47 --------------- web/api/routes_leaderboard.py | 69 ---------------------- web/api/routes_matches.py | 104 ---------------------------------- web/api/routes_players.py | 77 ------------------------- web/api/routes_tournaments.py | 76 ------------------------- web/main.py | 19 ------- 10 files changed, 453 deletions(-) delete mode 100644 web/.gitkeep delete mode 100644 web/README.md delete mode 100644 web/api/__init__.py delete mode 100644 web/api/app.py delete mode 100644 web/api/db.py delete mode 100644 web/api/routes_leaderboard.py delete mode 100644 web/api/routes_matches.py delete mode 100644 web/api/routes_players.py delete mode 100644 web/api/routes_tournaments.py delete mode 100644 web/main.py diff --git a/web/.gitkeep b/web/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 21684dd..0000000 --- a/web/README.md +++ /dev/null @@ -1 +0,0 @@ -FOR TSSBOT WEBSITE, READ ~/GitHub/tssbot.web OR on server ~/tssbot.web, clippi is fucking annoying with this shit \ No newline at end of file diff --git a/web/api/__init__.py b/web/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/web/api/app.py b/web/api/app.py deleted file mode 100644 index 234f636..0000000 --- a/web/api/app.py +++ /dev/null @@ -1,60 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, FastAPI, Request -from fastapi.responses import JSONResponse -from starlette.exceptions import HTTPException as StarletteHTTPException - -from web.api import db - -ENDPOINTS = [ - "/api/tss/info", "/api/tss/live", "/api/tss/player/{uid}", - "/api/tss/player/{uid}/games", "/api/tss/player/{uid}/history", - "/api/tss/search/{nick}", "/api/tss/match/{session_id}", - "/api/tss/match/{session_id}/scoreboard", "/api/tss/matches/search", - "/api/tss/maps", "/api/tss/leaderboard/players", - "/api/tss/leaderboard/vehicles", "/api/tss/leaderboard/stats", - "/api/tss/tournaments", "/api/tss/tournament/{id}", - "/api/tss/tournament/{id}/standings", "/api/tss/tournament/{id}/matches", -] - -info_router = APIRouter() - - -@info_router.get("/api/tss/info") -async def info() -> dict: - matches = await db.query_one(db.battles_path(), "SELECT COUNT(*) c FROM match_summary") - pg = await db.query_one(db.battles_path(), "SELECT COUNT(*) c FROM player_games_hist") - tn = await db.query_one(db.tournaments_path(), "SELECT COUNT(*) c FROM tournaments") - return { - "service": "tss", - "counts": { - "matches": matches["c"] if matches else 0, - "player_games": pg["c"] if pg else 0, - "tournaments": tn["c"] if tn else 0, - }, - "endpoints": ENDPOINTS, - } - - -def create_app() -> FastAPI: - app = FastAPI(title="TSS HTTP API", version="1.0.0") - - @app.exception_handler(StarletteHTTPException) - async def _err(_: Request, exc: StarletteHTTPException) -> JSONResponse: - detail = exc.detail - body = detail if isinstance(detail, dict) else {"error": str(detail)} - return JSONResponse(status_code=exc.status_code, content=body) - - app.include_router(info_router) - from web.api.routes_players import router as players_router - from web.api.routes_matches import router as matches_router - from web.api.routes_leaderboard import router as lb_router - from web.api.routes_tournaments import router as tour_router - app.include_router(players_router) - app.include_router(matches_router) - app.include_router(lb_router) - app.include_router(tour_router) - return app - - -app = create_app() diff --git a/web/api/db.py b/web/api/db.py deleted file mode 100644 index 40563ac..0000000 --- a/web/api/db.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -import os -import sqlite3 -import sys -from pathlib import Path -from typing import Any - -import aiosqlite - -# Make TSSBOT root importable for BOT.storage defaults. -_TSSBOT_ROOT = Path(__file__).resolve().parents[2] -if str(_TSSBOT_ROOT) not in sys.path: - sys.path.insert(0, str(_TSSBOT_ROOT)) - - -def battles_path() -> Path: - override = os.getenv("TSS_API_BATTLES_DB", "").strip() - if override: - return Path(override) - from BOT.storage import TSS_BATTLES_DB_PATH - return TSS_BATTLES_DB_PATH - - -def tournaments_path() -> Path: - override = os.getenv("TSS_API_TOURNAMENTS_DB", "").strip() - if override: - return Path(override) - from BOT.storage import STORAGE_DIR - return STORAGE_DIR / "tss_tournaments.db" - - -def _ro_uri(path: Path) -> str: - return f"file:{path}?mode=ro" - - -async def query(path: Path, sql: str, params: tuple[Any, ...] = ()) -> list[dict]: - async with aiosqlite.connect(_ro_uri(path), uri=True) as conn: - conn.row_factory = sqlite3.Row - async with conn.execute(sql, params) as cur: - rows = await cur.fetchall() - return [dict(r) for r in rows] - - -async def query_one(path: Path, sql: str, params: tuple[Any, ...] = ()) -> dict | None: - rows = await query(path, sql, params) - return rows[0] if rows else None diff --git a/web/api/routes_leaderboard.py b/web/api/routes_leaderboard.py deleted file mode 100644 index e721d76..0000000 --- a/web/api/routes_leaderboard.py +++ /dev/null @@ -1,69 +0,0 @@ -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} diff --git a/web/api/routes_matches.py b/web/api/routes_matches.py deleted file mode 100644 index 1be8372..0000000 --- a/web/api/routes_matches.py +++ /dev/null @@ -1,104 +0,0 @@ -from __future__ import annotations - -import json - -from fastapi import APIRouter, HTTPException - -from web.api import db - -router = APIRouter() - - -@router.get("/api/tss/live") -async def live(limit: int = 50) -> dict: - rows = await db.query(db.battles_path(), - "SELECT * FROM match_summary ORDER BY endtime_unix DESC LIMIT ?", - (max(1, min(limit, 200)),)) - return {"total": len(rows), "matches": rows} - - -def _roster_team(rows: list[dict], slot: str, winning: str, losing: str, draw: int) -> dict: - players = [r for r in rows if (r.get("team_slot") or "") == slot] - for p in players: - p["UID"] = str(p["UID"]) - return { - "team_slot": slot, - "team_name": players[0]["team_name"] if players else None, - "team_id": players[0]["team_id"] if players else None, - "is_winner": (not draw) and slot == winning, - "is_loser": (not draw) and slot == losing, - "players": players, - } - - -@router.get("/api/tss/match/{session_id}") -async def match(session_id: str) -> dict: - summary = await db.query_one(db.battles_path(), - "SELECT * FROM match_summary WHERE session_id = ?", (session_id,)) - if not summary: - raise HTTPException(status_code=404, detail=f"match {session_id} not found") - rows = await db.query(db.battles_path(), - "SELECT * FROM player_games_hist WHERE session_id = ?", (session_id,)) - slots = sorted({(r.get("team_slot") or "") for r in rows if r.get("team_slot")}) - teams = [_roster_team(rows, s, summary["winning_slot"], summary["losing_slot"], - summary["draw"]) for s in slots] - return {"match": summary, "teams": teams} - - -@router.get("/api/tss/match/{session_id}/scoreboard") -async def scoreboard(session_id: str) -> dict: - base = await match(session_id) # reuses 404 + roster logic - logs_row = await db.query_one(db.battles_path(), - "SELECT chat_log_json, battle_log_json, event_log_json FROM match_logs WHERE session_id = ?", - (session_id,)) - if logs_row: - def _parse(v): - try: - return json.loads(v) if v else None - except (json.JSONDecodeError, TypeError): - return None - logs = {"available": True, - "chat": _parse(logs_row["chat_log_json"]), - "battle": _parse(logs_row["battle_log_json"]), - "events": _parse(logs_row["event_log_json"])} - else: - logs = {"available": False} - return {**base, "logs": logs} - - -@router.get("/api/tss/matches/search") -async def matches_search(player: str | None = None, team: str | None = None, - mission: str | None = None, tournament: str | None = None, - time_from: int | None = None, time_to: int | None = None, - limit: int = 50) -> dict: - clauses: list[str] = [] - params: list = [] - join = "" - if player or team: - join = "JOIN player_games_hist p ON p.session_id = m.session_id" - if player: - clauses.append("p.UID = ?"); params.append(player) - if team: - clauses.append("p.team_name = ?"); params.append(team) - if mission: - clauses.append("m.mission_name LIKE ?"); params.append(f"%{mission}%") - if tournament: - clauses.append("m.tournament_name LIKE ?"); params.append(f"%{tournament}%") - if time_from is not None: - clauses.append("m.endtime_unix >= ?"); params.append(time_from) - if time_to is not None: - clauses.append("m.endtime_unix <= ?"); params.append(time_to) - where = ("WHERE " + " AND ".join(clauses)) if clauses else "" - params.append(max(1, min(limit, 200))) - rows = await db.query(db.battles_path(), - f"SELECT DISTINCT m.* FROM match_summary m {join} {where} " - f"ORDER BY m.endtime_unix DESC LIMIT ?", tuple(params)) - return {"total": len(rows), "matches": rows} - - -@router.get("/api/tss/maps") -async def maps() -> dict: - rows = await db.query(db.battles_path(), - "SELECT DISTINCT mission_name, level_path FROM match_summary " - "WHERE mission_name IS NOT NULL ORDER BY mission_name") - return {"total": len(rows), "maps": rows} diff --git a/web/api/routes_players.py b/web/api/routes_players.py deleted file mode 100644 index 4169ea6..0000000 --- a/web/api/routes_players.py +++ /dev/null @@ -1,77 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, HTTPException - -from web.api import db - -router = APIRouter() - - -@router.get("/api/tss/player/{uid}") -async def player(uid: str) -> dict: - bp = db.battles_path() - summary = await db.query_one(bp, """ - SELECT COUNT(*) battles, - SUM(CASE WHEN victor_bool='Win' THEN 1 ELSE 0 END) wins, - SUM(CASE WHEN victor_bool!='Win' THEN 1 ELSE 0 END) losses, - SUM(ground_kills) ground_kills, SUM(air_kills) air_kills, - SUM(assists) assists, SUM(captures) captures, SUM(deaths) deaths, - SUM(score) score, AVG(pvp_ratio) avg_pvp_ratio - FROM player_games_hist WHERE UID = ?""", (uid,)) - if not summary or summary["battles"] == 0: - raise HTTPException(status_code=404, detail=f"player {uid} not found") - vehicles = await db.query(bp, """ - SELECT vehicle, vehicle_internal, COUNT(*) battles, SUM(air_kills+ground_kills) kills, - SUM(deaths) deaths, SUM(score) score - FROM player_games_hist WHERE UID = ? - GROUP BY vehicle_internal ORDER BY battles DESC""", (uid,)) - nicks = await db.query(bp, """ - SELECT nick, MIN(endtime_unix) first_seen, MAX(endtime_unix) last_seen - FROM player_games_hist WHERE UID = ? GROUP BY nick ORDER BY last_seen DESC""", (uid,)) - team_history = await db.query(bp, """ - SELECT team_id, team_name, tss_role, COUNT(*) battles, - MIN(endtime_unix) first_seen, MAX(endtime_unix) last_seen - FROM player_games_hist WHERE UID = ? - GROUP BY team_id, team_name, tss_role ORDER BY last_seen DESC""", (uid,)) - win_rate = round(summary["wins"] / summary["battles"], 4) if summary["battles"] else 0.0 - return {"uid": str(uid), "summary": {**summary, "win_rate": win_rate}, - "vehicles": vehicles, "nicks": nicks, "team_history": team_history} - - -@router.get("/api/tss/player/{uid}/games") -async def player_games(uid: str, limit: int = 100, - time_from: int | None = None, time_to: int | None = None) -> dict: - clauses = ["p.UID = ?"] - params: list = [uid] - if time_from is not None: - clauses.append("p.endtime_unix >= ?"); params.append(time_from) - if time_to is not None: - clauses.append("p.endtime_unix <= ?"); params.append(time_to) - params.append(max(1, min(limit, 1000))) - rows = await db.query(db.battles_path(), f""" - SELECT p.*, m.mission_name, m.tournament_name - FROM player_games_hist p LEFT JOIN match_summary m ON m.session_id = p.session_id - WHERE {' AND '.join(clauses)} ORDER BY p.endtime_unix DESC LIMIT ?""", tuple(params)) - return {"uid": str(uid), "total": len(rows), "games": rows} - - -@router.get("/api/tss/player/{uid}/history") -async def player_history(uid: str) -> dict: - rows = await db.query(db.battles_path(), """ - SELECT date(endtime_unix, 'unixepoch') day, COUNT(*) battles, - SUM(CASE WHEN victor_bool='Win' THEN 1 ELSE 0 END) wins, - SUM(air_kills+ground_kills) kills, SUM(deaths) deaths, SUM(score) score - FROM player_games_hist WHERE UID = ? GROUP BY day ORDER BY day DESC""", (uid,)) - return {"uid": str(uid), "days_with_battles_only": True, "history": rows} - - -@router.get("/api/tss/search/{nick}") -async def search(nick: str, limit: int = 25) -> dict: - rows = await db.query(db.battles_path(), """ - SELECT UID uid, nick, MAX(endtime_unix) last_seen, COUNT(*) battles - FROM player_games_hist WHERE nick LIKE ? COLLATE NOCASE - GROUP BY UID, nick ORDER BY last_seen DESC LIMIT ?""", - (f"%{nick}%", max(1, min(limit, 100)))) - for r in rows: - r["uid"] = str(r["uid"]) - return {"query": nick, "players": rows} diff --git a/web/api/routes_tournaments.py b/web/api/routes_tournaments.py deleted file mode 100644 index c88fae5..0000000 --- a/web/api/routes_tournaments.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import annotations - -from fastapi import APIRouter, HTTPException - -from web.api import db - -router = APIRouter() - - -def _nullify(row: dict, keys: tuple[str, ...]) -> dict: - for k in keys: - if row.get(k) == "": - row[k] = None - return row - - -@router.get("/api/tss/tournaments") -async def tournaments(status: str | None = None, game_mode: str | None = None, - cluster: str | None = None, limit: int = 100) -> dict: - clauses: list[str] = [] - params: list = [] - if status: - clauses.append("status = ?"); params.append(status) - if game_mode: - clauses.append("game_mode = ?"); params.append(game_mode) - if cluster: - clauses.append("cluster = ?"); params.append(cluster) - where = ("WHERE " + " AND ".join(clauses)) if clauses else "" - params.append(max(1, min(limit, 500))) - rows = await db.query(db.tournaments_path(), - f"SELECT * FROM tournaments {where} ORDER BY date_end DESC LIMIT ?", tuple(params)) - return {"total": len(rows), "tournaments": rows} - - -async def _matches(tid: int) -> list[dict]: - rows = await db.query(db.tournaments_path(), - "SELECT * FROM tournament_matches WHERE tournament_id = ? ORDER BY round, position", - (tid,)) - battles = await db.query(db.tournaments_path(), - "SELECT match_id, type_bracket, session_hex FROM tournament_battles WHERE tournament_id = ?", - (tid,)) - by_match: dict[tuple, list[str]] = {} - for b in battles: - by_match.setdefault((b["match_id"], b["type_bracket"]), []).append(b["session_hex"]) - out = [] - for m in rows: - _nullify(m, ("round", "position")) - m["session_ids"] = by_match.get((m["match_id"], m["type_bracket"]), []) - out.append(m) - return out - - -@router.get("/api/tss/tournament/{tid}") -async def tournament(tid: int) -> dict: - meta = await db.query_one(db.tournaments_path(), - "SELECT * FROM tournaments WHERE tournament_id = ?", (tid,)) - if not meta: - raise HTTPException(status_code=404, detail=f"tournament {tid} not found") - standings = await db.query(db.tournaments_path(), - "SELECT * FROM tournament_standings WHERE tournament_id = ? ORDER BY group_index, rank", - (tid,)) - return {"tournament": meta, "standings": standings, "matches": await _matches(tid)} - - -@router.get("/api/tss/tournament/{tid}/standings") -async def standings(tid: int) -> dict: - rows = await db.query(db.tournaments_path(), - "SELECT * FROM tournament_standings WHERE tournament_id = ? ORDER BY group_index, rank", - (tid,)) - return {"tournament_id": tid, "standings": rows} - - -@router.get("/api/tss/tournament/{tid}/matches") -async def tournament_matches(tid: int) -> dict: - rows = await _matches(tid) - return {"tournament_id": tid, "total": len(rows), "matches": rows} diff --git a/web/main.py b/web/main.py deleted file mode 100644 index c99b322..0000000 --- a/web/main.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path - -from dotenv import load_dotenv - -_HERE = Path(__file__).resolve().parent.parent -load_dotenv(_HERE / ".env") - -import uvicorn # noqa: E402 - -if __name__ == "__main__": - uvicorn.run( - "web.api.app:app", - host=os.getenv("TSS_API_HOST", "127.0.0.1"), - port=int(os.getenv("TSS_API_PORT", "6100")), - reload=False, - )