diff --git a/backend/app/api/admin.py b/backend/app/api/admin.py new file mode 100644 index 0000000..458b214 --- /dev/null +++ b/backend/app/api/admin.py @@ -0,0 +1,227 @@ +"""Admin/seed endpoints for demo""" +from fastapi import APIRouter, Depends, BackgroundTasks +from sqlalchemy.orm import Session +from app.database import get_db +from app.models.player import Player, MembershipTier +from app.models.court import Court +from app.models.booking import Booking, BookingStatus +from app.models.match import Match, MatchPlayer, MatchStage, MatchStatus, MatchType +from app.models.tournament import Tournament, TournamentEntry, TournamentMatch, TournamentStatus, BracketType, TournamentMatchStatus +from datetime import datetime, timedelta +import random + +router = APIRouter(prefix="/admin", tags=["admin"]) + + +@router.post("/seed") +def run_seed(db: Session = Depends(get_db)): + """Run demo seed data""" + try: + import sys + sys.path.insert(0, '/app') + from seed import main + main() + return {"status": "ok", "message": "Seed complete"} + except Exception as e: + return {"status": "error", "message": str(e)} + + +@router.post("/update-elos") +def update_elos(db: Session = Depends(get_db)): + """Update demo player ELO ratings to proper values""" + elo_map = { + "marco@demo.com": (1850, MembershipTier.ELITE, 45, 8, "#EF4444"), + "sofia@demo.com": (1720, MembershipTier.ELITE, 38, 12, "#EC4899"), + "carlos@demo.com": (1630, MembershipTier.PLATINUM, 32, 15, "#8B5CF6"), + "ana@demo.com": (1580, MembershipTier.PLATINUM, 28, 18, "#06B6D4"), + "juan@demo.com": (1450, MembershipTier.GOLD, 22, 20, "#F59E0B"), + "maria@demo.com": (1380, MembershipTier.GOLD, 18, 22, "#10B981"), + "pedro@demo.com": (1250, MembershipTier.SILVER, 14, 19, "#3B82F6"), + "rosa@demo.com": (1190, MembershipTier.SILVER, 11, 21, "#F97316"), + "diego@demo.com": (1100, MembershipTier.SILVER, 9, 18, "#84CC16"), + "lucia@demo.com": (980, MembershipTier.BRONZE, 6, 20, "#14B8A6"), + "miguel@demo.com": (920, MembershipTier.BRONZE, 4, 15, "#6366F1"), + "isabella@demo.com": (850, MembershipTier.BRONZE, 2, 12, "#D946EF"), + } + + updated = [] + for email, (elo, tier, wins, losses, color) in elo_map.items(): + p = db.query(Player).filter(Player.email == email).first() + if p: + p.elo_rating = elo + p.membership_tier = tier + p.wins = wins + p.losses = losses + p.total_matches = wins + losses + p.avatar_color = color + updated.append(p.name) + + db.commit() + + # Create courts if not exist + if db.query(Court).count() == 0: + courts_data = [ + ("Court 1", 1, 200.0, "Sport Court", "LED Lighting, Pro Net System"), + ("Court 2", 2, 200.0, "Sport Court", "LED Lighting, Spectator Seating"), + ("Court 3", 3, 250.0, "Cushioned", "Tournament Grade, Scoreboards"), + ("Court 4", 4, 250.0, "Cushioned", "Tournament Grade, VIP Lounge Access"), + ] + for name, num, rate, surface, features in courts_data: + c = Court(name=name, court_number=num, hourly_rate=rate, surface_type=surface, features=features) + db.add(c) + db.commit() + + # Create active matches + courts = db.query(Court).order_by(Court.court_number).all() + players = db.query(Player).order_by(Player.elo_rating.desc()).all() + + if db.query(Match).count() == 0 and len(players) >= 4 and len(courts) >= 2: + # Active match 1 + m1 = Match( + court_id=courts[0].id, + stage=MatchStage.OPEN, + match_type=MatchType.DOUBLES, + status=MatchStatus.IN_PROGRESS, + title="Open Doubles - Court 1", + max_players=4, + team1_score=7, + team2_score=5, + started_at=datetime.utcnow() - timedelta(minutes=25), + ) + db.add(m1) + db.flush() + for pid, team in [(players[0].id, 1), (players[1].id, 1), (players[2].id, 2), (players[3].id, 2)]: + db.add(MatchPlayer(match_id=m1.id, player_id=pid, team=team, elo_before=db.query(Player).filter(Player.id == pid).first().elo_rating)) + + # Active match 2 (skill-based) + m2 = Match( + court_id=courts[1].id, + stage=MatchStage.SKILL_BASED, + match_type=MatchType.DOUBLES, + status=MatchStatus.IN_PROGRESS, + title="Skill Match - Gold League", + max_players=4, + min_elo=1200, + max_elo=1600, + team1_score=3, + team2_score=6, + started_at=datetime.utcnow() - timedelta(minutes=15), + ) + db.add(m2) + db.flush() + for pid, team in [(players[4].id, 1), (players[5].id, 1), (players[6].id, 2), (players[7].id, 2)]: + db.add(MatchPlayer(match_id=m2.id, player_id=pid, team=team, elo_before=db.query(Player).filter(Player.id == pid).first().elo_rating)) + + # Lobby matches + m3 = Match(stage=MatchStage.OPEN, match_type=MatchType.DOUBLES, status=MatchStatus.LOBBY, + title="Evening Open Game", max_players=4) + db.add(m3) + db.flush() + db.add(MatchPlayer(match_id=m3.id, player_id=players[8].id, team=1, elo_before=players[8].elo_rating)) + + m4 = Match(stage=MatchStage.SKILL_BASED, match_type=MatchType.DOUBLES, status=MatchStatus.LOBBY, + title="Gold Tier Challenge", max_players=4, min_elo=1200, max_elo=1600) + db.add(m4) + db.flush() + db.add(MatchPlayer(match_id=m4.id, player_id=players[4].id, team=1, elo_before=players[4].elo_rating)) + db.commit() + + # Create tournament + if db.query(Tournament).count() == 0 and len(players) >= 8 and len(courts) >= 3: + t = Tournament( + name="ServeSync Grand Prix - March 2024", + status=TournamentStatus.IN_PROGRESS, + max_participants=8, + current_round=2, + started_at=datetime.utcnow() - timedelta(hours=2), + ) + db.add(t) + db.flush() + + tp = players[:8] + entries = [] + for i, player in enumerate(tp): + entry = TournamentEntry(tournament_id=t.id, player_id=player.id, seed=i+1, wins=0, losses=0) + db.add(entry) + entries.append(entry) + db.flush() + + r1 = [(tp[0].id, tp[7].id, 11, 5), (tp[1].id, tp[6].id, 11, 7), + (tp[2].id, tp[5].id, 8, 11), (tp[3].id, tp[4].id, 11, 9)] + + winners = [] + losers = [] + for i, (p1, p2, s1, s2) in enumerate(r1): + tm = TournamentMatch(tournament_id=t.id, round_number=1, match_number=i+1, + bracket_type=BracketType.WINNERS, status=TournamentMatchStatus.COMPLETED, + player1_id=p1, player2_id=p2, team1_score=s1, team2_score=s2, + winner_team=1 if s1>s2 else 2, + completed_at=datetime.utcnow() - timedelta(hours=1, minutes=30)) + db.add(tm) + w = p1 if s1>s2 else p2 + l = p2 if s1>s2 else p1 + winners.append(w) + losers.append(l) + for e in entries: + if e.player_id == w: e.wins += 1 + elif e.player_id == l: + e.losses += 1 + e.is_in_losers = True + + # R2 winners - 1 completed, 1 active + wm1 = TournamentMatch(tournament_id=t.id, round_number=2, match_number=1, + bracket_type=BracketType.WINNERS, status=TournamentMatchStatus.COMPLETED, + player1_id=winners[0], player2_id=winners[1], team1_score=11, team2_score=8, + winner_team=1, completed_at=datetime.utcnow() - timedelta(minutes=45)) + db.add(wm1) + for e in entries: + if e.player_id == winners[0]: e.wins += 1 + elif e.player_id == winners[1]: + e.losses += 1 + e.is_in_losers = True + + wm2 = TournamentMatch(tournament_id=t.id, round_number=2, match_number=2, + bracket_type=BracketType.WINNERS, status=TournamentMatchStatus.IN_PROGRESS, + player1_id=winners[2], player2_id=winners[3], team1_score=6, team2_score=7) + db.add(wm2) + + # Losers bracket + lb1 = TournamentMatch(tournament_id=t.id, round_number=1, match_number=1, + bracket_type=BracketType.LOSERS, status=TournamentMatchStatus.COMPLETED, + player1_id=losers[0], player2_id=losers[1], team1_score=9, team2_score=11, + winner_team=2, completed_at=datetime.utcnow() - timedelta(minutes=90)) + db.add(lb1) + lb2 = TournamentMatch(tournament_id=t.id, round_number=1, match_number=2, + bracket_type=BracketType.LOSERS, status=TournamentMatchStatus.COMPLETED, + player1_id=losers[2], player2_id=losers[3], team1_score=11, team2_score=6, + winner_team=1, completed_at=datetime.utcnow() - timedelta(minutes=60)) + db.add(lb2) + + for e in entries: + if e.player_id == losers[0]: + e.losses += 1 + e.is_eliminated = True + e.final_rank = 8 + elif e.player_id == losers[3]: + e.losses += 1 + e.is_eliminated = True + e.final_rank = 7 + + # Tournament match on court 3 + p_name_2 = db.query(Player).filter(Player.id == winners[2]).first().name + p_name_3 = db.query(Player).filter(Player.id == winners[3]).first().name + m_tour = Match( + court_id=courts[2].id, stage=MatchStage.TOURNAMENT, match_type=MatchType.SINGLES, + status=MatchStatus.IN_PROGRESS, + title=f"Tournament R2 - {p_name_2} vs {p_name_3}", + max_players=2, team1_score=6, team2_score=7, + started_at=datetime.utcnow() - timedelta(minutes=20), + ) + db.add(m_tour) + db.flush() + for pid, team in [(winners[2], 1), (winners[3], 2)]: + db.add(MatchPlayer(match_id=m_tour.id, player_id=pid, team=team, + elo_before=db.query(Player).filter(Player.id == pid).first().elo_rating)) + db.commit() + + return {"status": "ok", "updated": updated, "players": len(updated)} diff --git a/backend/app/main.py b/backend/app/main.py index 6d35305..0c12a4c 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -15,7 +15,7 @@ booking.Base.metadata.create_all(bind=engine) match.Base.metadata.create_all(bind=engine) tournament.Base.metadata.create_all(bind=engine) -from app.api import players, courts, matches, tournaments, screen +from app.api import players, courts, matches, tournaments, screen, admin app = FastAPI( title="ServeSync API", @@ -37,6 +37,7 @@ app.include_router(courts.router, prefix="/api") app.include_router(matches.router, prefix="/api") app.include_router(tournaments.router, prefix="/api") app.include_router(screen.router, prefix="/api") +app.include_router(admin.router, prefix="/api") # WebSocket connection manager