from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app import crud from app.config import settings from app.database import get_db from app.models import GameServer, Match, MatchPlayer from app.schemas import MatchReportRequest router = APIRouter(prefix="/matches", tags=["matches"]) # Called once by the hosting server's MatchManager (spacewar/autoload/ # match_manager.gd's _report_match_result()) when a match's POST_MATCH phase # begins -- persists this match's final kills/deaths/win-loss into each # player's running Player totals (kills, deaths, wins, losses, hours_played), # and records a Match/MatchPlayer row if the reporting server is a known, # registered GameServer. No auth -- same posture as /servers/register today, # not a new gap introduced here (see overview/map1.md's design notes). # # Independent of any Match row the matchmaking queue may have already created # when this match was *formed* (see queue_manager.py's _try_form_match) -- # this always writes a fresh Match/MatchPlayer pair representing how the # match actually ended. A queued match can therefore end up with two Match # rows (one "how it was formed", one "how it ended"); acceptable since no # existing code reads these tables today and there's no migrations tooling to # reconcile the schema around it. @router.post("/report") async def report_match(req: MatchReportRequest, db: AsyncSession = Depends(get_db)) -> dict: server = ( await db.execute( select(GameServer).where( GameServer.ip == req.server_ip, GameServer.port == req.server_port, GameServer.mode == req.mode, ) ) ).scalars().first() match_row = None if server is not None: match_row = Match(mode=req.mode, server_id=server.id) db.add(match_row) await db.flush() for result in req.players: player = await crud.get_or_create_player(db, result.callsign) player.kills += result.kills player.deaths += result.deaths player.hours_played += result.seconds_played / 3600.0 player.ram_kb += ( settings.ram_payout_participation_kb + result.kills * settings.ram_payout_per_kill_kb + (settings.ram_payout_win_bonus_kb if result.is_winner else 0) ) if result.is_winner: player.wins += 1 elif req.winner_team is not None: player.losses += 1 if match_row is not None: db.add(MatchPlayer(match_id=match_row.id, player_id=player.id, team=result.team)) await db.commit() return {"status": "recorded"}