142 lines
3.8 KiB
Python
142 lines
3.8 KiB
Python
# backend/app/routes/tournaments/report.py
|
|
from typing import List, 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, id, match_id)
|
|
if not match:
|
|
raise HTTPException(404, "Match not found")
|
|
|
|
if not report.sets:
|
|
raise HTTPException(400, "No sets submitted")
|
|
|
|
_apply_score(match, report.sets)
|
|
flag_modified(match, "sets")
|
|
db.commit()
|
|
logic.advance_flow(db, t)
|
|
|
|
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),
|
|
):
|
|
"""
|
|
Allows correcting a score.
|
|
Note: If the winner changes, 'advance_flow' might need to handle
|
|
undoing previous advancements, but for now we just re-run the flow.
|
|
"""
|
|
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, id, match_id)
|
|
if not match:
|
|
raise HTTPException(404, "Match not found")
|
|
|
|
if report.sets:
|
|
_apply_score(match, report.sets)
|
|
|
|
flag_modified(match, "sets")
|
|
db.commit()
|
|
logic.advance_flow(db, t)
|
|
|
|
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, id, match_id)
|
|
if not match:
|
|
raise HTTPException(404, "Match not found")
|
|
|
|
match.winner_team_id = None
|
|
match.status = MatchStatus.PENDING
|
|
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]
|