- FastAPI backend with PostgreSQL + Redis - 4 core features: Registration, Courts, Matchmaking, Tournament - Double elimination tournament with bracket visualization - ELO-based matchmaking (Stage 1 Open, Stage 2 Skill-Based) - Real-time WebSocket updates - TV Screen display for courts - Vue 3 + Tailwind CSS frontend - Seed data: 12 players, 4 courts, active matches, tournament in progress - Docker compose stack with Nginx reverse proxy
83 lines
3.0 KiB
Python
83 lines
3.0 KiB
Python
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
from app.database import get_db
|
|
from app.models.tournament import Tournament, TournamentEntry, TournamentMatch, TournamentStatus
|
|
from app.services import tournament as tournament_service
|
|
from pydantic import BaseModel
|
|
from typing import Optional, List
|
|
|
|
router = APIRouter(prefix="/tournaments", tags=["tournaments"])
|
|
|
|
|
|
class CreateTournamentRequest(BaseModel):
|
|
name: str
|
|
max_participants: int = 8
|
|
|
|
|
|
class RegisterPlayerRequest(BaseModel):
|
|
player_id: int
|
|
|
|
|
|
class MatchScoreRequest(BaseModel):
|
|
team1_score: int
|
|
team2_score: int
|
|
|
|
|
|
@router.get("/")
|
|
def get_tournaments(db: Session = Depends(get_db)):
|
|
tournaments = db.query(Tournament).order_by(Tournament.created_at.desc()).all()
|
|
return [
|
|
{
|
|
"id": t.id,
|
|
"name": t.name,
|
|
"status": t.status.value,
|
|
"max_participants": t.max_participants,
|
|
"current_participants": len(t.entries),
|
|
"started_at": t.started_at.isoformat() if t.started_at else None,
|
|
}
|
|
for t in tournaments
|
|
]
|
|
|
|
|
|
@router.get("/{tournament_id}")
|
|
def get_tournament(tournament_id: int, db: Session = Depends(get_db)):
|
|
return tournament_service.get_bracket_data(db, tournament_id)
|
|
|
|
|
|
@router.post("/")
|
|
def create_tournament(req: CreateTournamentRequest, db: Session = Depends(get_db)):
|
|
t = tournament_service.create_tournament(db, req.name, req.max_participants)
|
|
return {"id": t.id, "name": t.name, "status": t.status.value}
|
|
|
|
|
|
@router.post("/{tournament_id}/register")
|
|
def register_player(tournament_id: int, req: RegisterPlayerRequest, db: Session = Depends(get_db)):
|
|
entry = tournament_service.register_player(db, tournament_id, req.player_id)
|
|
if not entry:
|
|
raise HTTPException(status_code=400, detail="Cannot register player (tournament full or already registered)")
|
|
return {"message": "Registered successfully", "player_id": req.player_id}
|
|
|
|
|
|
@router.post("/{tournament_id}/start")
|
|
def start_tournament(tournament_id: int, db: Session = Depends(get_db)):
|
|
t = tournament_service.start_tournament(db, tournament_id)
|
|
if not t:
|
|
raise HTTPException(status_code=400, detail="Cannot start tournament (need at least 4 players)")
|
|
return tournament_service.get_bracket_data(db, tournament_id)
|
|
|
|
|
|
@router.post("/{tournament_id}/matches/{match_id}/score")
|
|
def complete_tournament_match(
|
|
tournament_id: int, match_id: int, req: MatchScoreRequest, db: Session = Depends(get_db)
|
|
):
|
|
tm = tournament_service.complete_tournament_match(db, match_id, req.team1_score, req.team2_score)
|
|
if not tm:
|
|
raise HTTPException(status_code=400, detail="Cannot complete match")
|
|
return tournament_service.get_bracket_data(db, tournament_id)
|
|
|
|
|
|
@router.get("/{tournament_id}/leaderboard")
|
|
def get_leaderboard(tournament_id: int, db: Session = Depends(get_db)):
|
|
data = tournament_service.get_bracket_data(db, tournament_id)
|
|
return data.get("leaderboard", [])
|