Label fixes, Websocket reconnects and more detailed court view cards.

This commit is contained in:
2026-06-14 20:57:19 +02:00 Verified
parent b904879326
commit 55a13934ef
8 changed files with 222 additions and 76 deletions
+11 -14
View File
@@ -45,17 +45,14 @@ def create_tournament(db: Session, data: schemas.TournamentCreate):
def get_tournaments(db: Session):
return db.query(models.Tournament).all()
def get_tournament(db: Session, tournament_id: str):
return (
db.query(models.Tournament)
.filter(models.Tournament.id == tournament_id)
.first()
)
def get_tournament(db: Session, tournament_id: str, lock: bool = False):
query = db.query(models.Tournament).filter(models.Tournament.id == tournament_id)
if lock:
query = query.with_for_update()
return query.first()
def delete_tournament(db: Session, tournament_id: str) -> bool:
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return False
db.delete(t)
@@ -66,7 +63,7 @@ def delete_tournament(db: Session, tournament_id: str) -> bool:
def update_tournament_details(
db: Session, tournament_id: str, data: schemas.TournamentUpdate
):
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return None
@@ -94,7 +91,7 @@ def get_teams(db: Session, tournament_id: str):
def create_team(db: Session, tournament_id: str, team_data: schemas.TeamCreate):
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return None
@@ -110,7 +107,7 @@ def create_team(db: Session, tournament_id: str, team_data: schemas.TeamCreate):
def update_tournament_teams(db: Session, tournament_id: str, new_team_names: list[str]):
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return None
@@ -125,7 +122,7 @@ def update_tournament_teams(db: Session, tournament_id: str, new_team_names: lis
def delete_team(db: Session, tournament_id: str, team_id: int):
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return None
team = db.get(models.Team, team_id)
@@ -182,7 +179,7 @@ def delete_court(db: Session, court_id: int):
def update_tournament_courts(db: Session, tournament_id: str, new_court_ids: list[int]):
t = get_tournament(db, tournament_id)
t = get_tournament(db, tournament_id, lock=True)
if not t:
return None
+7 -10
View File
@@ -95,6 +95,7 @@ def update_schedule_times(db: Session, t: models.Tournament):
models.Tournament.timestamp >= start_of_day,
models.Tournament.timestamp < end_of_day,
)
.with_for_update()
.all()
)
@@ -296,6 +297,8 @@ def update_schedule_times(db: Session, t: models.Tournament):
best_score = float("inf")
for prev_m in all_matches:
if prev_m.tournament_id != m.tournament_id:
continue
if not prev_m.start_time or prev_m.start_time >= m_start:
continue
prev_end = prev_m.start_time + timedelta(minutes=prev_m.tournament.duration)
@@ -307,10 +310,7 @@ def update_schedule_times(db: Session, t: models.Tournament):
next_start = match_outcome_next_start.get(out_key)
if next_start and next_start < m_end:
continue
role_str = "Winner" if outcome == "W" else "Loser"
identifier = f"{role_str} of #{prev_m.match_number}"
identifier = f"{outcome}:{prev_m.id}"
if is_ref_busy(identifier, m_start, m_end):
continue
@@ -363,10 +363,10 @@ def advance_winner(db: Session, match: models.Match, winner_id: int):
loser_id = match.p1_team_id if match.p1_team_id != winner_id else match.p2_team_id
for m in match.tournament.matches:
if m.ref_label == f"Loser of #{match.match_number}":
if m.ref_label == f"L:{match.id}":
m.ref_team_id = loser_id
db.add(m)
elif m.ref_label == f"Winner of #{match.match_number}":
elif m.ref_label == f"W:{match.id}":
m.ref_team_id = winner_id
db.add(m)
@@ -435,10 +435,7 @@ def undo_advancement(db: Session, match: models.Match):
return
for m in match.tournament.matches:
if (
m.ref_label == f"Loser of #{match.match_number}"
or m.ref_label == f"Winner of #{match.match_number}"
):
if m.ref_label in [f"L:{match.id}", f"W:{match.id}"]:
m.ref_team_id = None
db.add(m)
+40 -9
View File
@@ -10,13 +10,11 @@ from ..database import get_db
router = APIRouter(prefix="/courts", tags=["Courts"])
@router.get("", response_model=list[schemas.CourtSchema])
def get_all_courts(db: Session = Depends(get_db)):
"""Public route to list all global courts"""
return db.query(models.Court).all()
@router.post("", response_model=schemas.CourtSchema)
def create_global_court(
data: schemas.CourtCreate,
@@ -26,7 +24,6 @@ def create_global_court(
"""Admin route to register a new physical court"""
return crud.create_court(db, data)
@router.delete("/{court_id}")
def delete_global_court(
court_id: int, db: Session = Depends(get_db), user: dict = Depends(get_admin_user)
@@ -36,7 +33,6 @@ def delete_global_court(
raise HTTPException(404, "Court not found")
return SUCCESS
@router.get("/{court_id}/schedule")
def get_court_schedule(court_id: int, db: Session = Depends(get_db)):
court = db.query(models.Court).filter(models.Court.id == court_id).first()
@@ -59,6 +55,39 @@ def get_court_schedule(court_id: int, db: Session = Depends(get_db)):
.all()
)
t_ids = {m.tournament_id for m in matches}
if t_ids:
all_t_matches = db.query(models.Match).filter(models.Match.tournament_id.in_(t_ids)).all()
else:
all_t_matches = []
match_by_id = {str(tm.id).lower(): tm for tm in all_t_matches}
parent_map = {}
for tm in all_t_matches:
if tm.winner_next_match_id:
parent_map[(str(tm.winner_next_match_id).lower(), tm.winner_next_match_slot)] = f"Winner of #{tm.match_number}"
if tm.loser_next_match_id:
parent_map[(str(tm.loser_next_match_id).lower(), tm.loser_next_match_slot)] = f"Loser of #{tm.match_number}"
def resolve_ref(m: models.Match):
if m.ref_team:
return m.ref_team.name
if m.ref_label and ":" in m.ref_label:
outcome, ref_id = m.ref_label.split(":")
ref_match = match_by_id.get(ref_id.strip().lower())
if ref_match:
role = "Winner" if outcome.upper() == "W" else "Loser"
return f"{role} of #{ref_match.match_number}"
return m.ref_label or "TBD"
def get_team_label(m: models.Match, slot: int, team):
if team:
return team.name
return parent_map.get((str(m.id).lower(), slot), "TBD")
return {
"court": court.name,
"matches": [
@@ -68,15 +97,17 @@ def get_court_schedule(court_id: int, db: Session = Depends(get_db)):
"tournament_name": m.tournament.name,
"duration": m.tournament.duration,
"time": m.start_time.strftime("%H:%M") if m.start_time else None,
"status": m.status.value,
"status": m.status.value if hasattr(m.status, 'value') else m.status,
"match_number": m.match_number,
"p1": m.p1_team.name if m.p1_team else "TBD",
"p2": m.p2_team.name if m.p2_team else "TBD",
"p1": get_team_label(m, 0, m.p1_team),
"p2": get_team_label(m, 1, m.p2_team),
"p1_is_real": bool(m.p1_team),
"p2_is_real": bool(m.p2_team),
"p1_sets": len([s for s in m.sets if s.get("p1", 0) > s.get("p2", 0)]),
"p2_sets": len([s for s in m.sets if s.get("p2", 0) > s.get("p1", 0)]),
"ref_name": m.ref_team.name if m.ref_team else m.ref_label,
"ref_name": resolve_ref(m),
}
for m in matches
if m.start_time
],
}
}
+3 -5
View File
@@ -19,8 +19,6 @@ def _check_auth(t: models.Tournament, user: Optional[str], code: Optional[str]):
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,
@@ -29,7 +27,7 @@ async def report_score(
db: Session = Depends(get_db),
user: Optional[str] = Depends(get_optional_user),
):
t = crud.get_tournament(db, id)
t = crud.get_tournament(db, id, lock=True)
if not t:
raise HTTPException(404, "Tournament not found")
@@ -65,7 +63,7 @@ async def edit_score(
db: Session = Depends(get_db),
user: Optional[str] = Depends(get_optional_user),
):
t = crud.get_tournament(db, id)
t = crud.get_tournament(db, id, lock=True)
if not t:
raise HTTPException(404, "Tournament not found")
@@ -99,7 +97,7 @@ async def clear_score(
db: Session = Depends(get_db),
user: Optional[str] = Depends(get_optional_user),
):
t = crud.get_tournament(db, id)
t = crud.get_tournament(db, id, lock=True)
if not t:
raise HTTPException(404, "Tournament not found")