# backend/app/routes/tournaments/report.py from typing import Optional from fastapi import Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy.orm.attributes import flag_modified from ... import crud, logic, models, schemas from ...constants import SUCCESS, MatchStatus from ...core.auth import get_optional_user from ...core.websocket_manager import send_ws_update from ...database import get_db from . import router def _check_auth(t: models.Tournament, user: Optional[str], code: Optional[str]): is_admin = user is not None code_matches = code is not None and str(code).strip() == str(t.code).strip() if not is_admin and not code_matches: raise HTTPException(403, "Invalid tournament code or admin privileges required") @router.post("/{id}/matches/{match_id}/score") async def report_score( id: str, match_id: str, report: schemas.ScoreReport, db: Session = Depends(get_db), user: Optional[str] = Depends(get_optional_user), ): t = crud.get_tournament(db, id) if not t: raise HTTPException(404, "Tournament not found") _check_auth(t, user, report.code) match = crud.get_match(db, match_id) if not match: raise HTTPException(404, "Match not found") if not report.sets: raise HTTPException(400, "No sets submitted") if match.winner_team_id: logic.undo_advancement(db, match) _apply_score(match, report.sets) flag_modified(match, "sets") if match.winner_team_id: logic.advance_winner(db, match, match.winner_team_id) db.commit() await send_ws_update(id) return SUCCESS @router.patch("/{id}/matches/{match_id}/score") async def edit_score( id: str, match_id: str, report: schemas.ScoreReport, db: Session = Depends(get_db), user: Optional[str] = Depends(get_optional_user), ): t = crud.get_tournament(db, id) if not t: raise HTTPException(404, "Tournament not found") _check_auth(t, user, report.code) match = crud.get_match(db, match_id) if not match: raise HTTPException(404, "Match not found") logic.undo_advancement(db, match) if report.sets: _apply_score(match, report.sets) flag_modified(match, "sets") if match.winner_team_id: logic.advance_winner(db, match, match.winner_team_id) db.commit() await send_ws_update(id) return SUCCESS @router.delete("/{id}/matches/{match_id}/score") async def clear_score( id: str, match_id: str, code: Optional[str] = Query(None), db: Session = Depends(get_db), user: Optional[str] = Depends(get_optional_user), ): t = crud.get_tournament(db, id) if not t: raise HTTPException(404, "Tournament not found") _check_auth(t, user, code) match = crud.get_match(db, match_id) if not match: raise HTTPException(404, "Match not found") logic.undo_advancement(db, match) match.winner_team_id = None if match.p1_team_id and match.p2_team_id: match.status = MatchStatus.PENDING else: match.status = MatchStatus.SCHEDULED match.sets = [] flag_modified(match, "sets") db.commit() await send_ws_update(id) return SUCCESS def _apply_score(match: models.Match, sets: list[schemas.SetScore]): """ Calculates winner based on sets and updates the match object. Does NOT commit to DB. """ p1_wins = sum(1 for s in sets if s.p1 > s.p2) p2_wins = sum(1 for s in sets if s.p2 > s.p1) if p1_wins > p2_wins: match.winner_team_id = match.p1_team_id elif p2_wins > p1_wins: match.winner_team_id = match.p2_team_id else: p1_points = sum(s.p1 for s in sets) p2_points = sum(s.p2 for s in sets) if p1_points == p2_points: raise HTTPException(400, "Absolute tie: Sets and Points are equal.") match.winner_team_id = ( match.p1_team_id if p1_points > p2_points else match.p2_team_id ) match.status = MatchStatus.FINISHED match.sets = [s.model_dump() for s in sets]