🏓 Initial ServeSync demo build
- 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
This commit is contained in:
94
backend/app/api/players.py
Normal file
94
backend/app/api/players.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.models.player import Player, MembershipTier
|
||||
from app.services.elo import get_tier_from_elo
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
|
||||
router = APIRouter(prefix="/players", tags=["players"])
|
||||
|
||||
|
||||
class PlayerCreate(BaseModel):
|
||||
name: str
|
||||
email: str
|
||||
phone: Optional[str] = None
|
||||
|
||||
|
||||
class PlayerUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
|
||||
|
||||
class PlayerResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
email: str
|
||||
phone: Optional[str]
|
||||
elo_rating: float
|
||||
membership_tier: str
|
||||
wins: int
|
||||
losses: int
|
||||
total_matches: int
|
||||
win_rate: float
|
||||
avatar_color: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, player: Player):
|
||||
return cls(
|
||||
id=player.id,
|
||||
name=player.name,
|
||||
email=player.email,
|
||||
phone=player.phone,
|
||||
elo_rating=round(player.elo_rating, 1),
|
||||
membership_tier=player.membership_tier.value,
|
||||
wins=player.wins,
|
||||
losses=player.losses,
|
||||
total_matches=player.total_matches,
|
||||
win_rate=player.win_rate,
|
||||
avatar_color=player.avatar_color,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", response_model=List[PlayerResponse])
|
||||
def get_players(db: Session = Depends(get_db)):
|
||||
players = db.query(Player).filter(Player.is_active == True).order_by(Player.elo_rating.desc()).all()
|
||||
return [PlayerResponse.from_orm(p) for p in players]
|
||||
|
||||
|
||||
@router.get("/leaderboard", response_model=List[PlayerResponse])
|
||||
def get_leaderboard(limit: int = 10, db: Session = Depends(get_db)):
|
||||
players = db.query(Player).filter(Player.is_active == True).order_by(Player.elo_rating.desc()).limit(limit).all()
|
||||
return [PlayerResponse.from_orm(p) for p in players]
|
||||
|
||||
|
||||
@router.get("/{player_id}", response_model=PlayerResponse)
|
||||
def get_player(player_id: int, db: Session = Depends(get_db)):
|
||||
player = db.query(Player).filter(Player.id == player_id).first()
|
||||
if not player:
|
||||
raise HTTPException(status_code=404, detail="Player not found")
|
||||
return PlayerResponse.from_orm(player)
|
||||
|
||||
|
||||
@router.post("/", response_model=PlayerResponse)
|
||||
def create_player(player_data: PlayerCreate, db: Session = Depends(get_db)):
|
||||
existing = db.query(Player).filter(Player.email == player_data.email).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="Email already registered")
|
||||
|
||||
colors = ["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"]
|
||||
color = colors[db.query(Player).count() % len(colors)]
|
||||
|
||||
player = Player(
|
||||
name=player_data.name,
|
||||
email=player_data.email,
|
||||
phone=player_data.phone,
|
||||
avatar_color=color,
|
||||
)
|
||||
db.add(player)
|
||||
db.commit()
|
||||
db.refresh(player)
|
||||
return PlayerResponse.from_orm(player)
|
||||
Reference in New Issue
Block a user