🏓 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:
432
backend/seed.py
Normal file
432
backend/seed.py
Normal file
@@ -0,0 +1,432 @@
|
||||
"""Seed demo data for ServeSync"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import engine, SessionLocal
|
||||
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
|
||||
|
||||
|
||||
def clear_data(db: Session):
|
||||
db.query(TournamentMatch).delete()
|
||||
db.query(TournamentEntry).delete()
|
||||
db.query(Tournament).delete()
|
||||
db.query(MatchPlayer).delete()
|
||||
db.query(Match).delete()
|
||||
db.query(Booking).delete()
|
||||
db.query(Court).delete()
|
||||
db.query(Player).delete()
|
||||
db.commit()
|
||||
print("Cleared existing data")
|
||||
|
||||
|
||||
def seed_players(db: Session) -> list:
|
||||
players_data = [
|
||||
{"name": "Marco Santos", "email": "marco@demo.com", "elo": 1850, "tier": MembershipTier.ELITE, "color": "#EF4444", "wins": 45, "losses": 8},
|
||||
{"name": "Sofia Reyes", "email": "sofia@demo.com", "elo": 1720, "tier": MembershipTier.ELITE, "color": "#EC4899", "wins": 38, "losses": 12},
|
||||
{"name": "Carlos Mendez", "email": "carlos@demo.com", "elo": 1630, "tier": MembershipTier.PLATINUM, "color": "#8B5CF6", "wins": 32, "losses": 15},
|
||||
{"name": "Ana Cruz", "email": "ana@demo.com", "elo": 1580, "tier": MembershipTier.PLATINUM, "color": "#06B6D4", "wins": 28, "losses": 18},
|
||||
{"name": "Juan Dela Cruz", "email": "juan@demo.com", "elo": 1450, "tier": MembershipTier.GOLD, "color": "#F59E0B", "wins": 22, "losses": 20},
|
||||
{"name": "Maria Garcia", "email": "maria@demo.com", "elo": 1380, "tier": MembershipTier.GOLD, "color": "#10B981", "wins": 18, "losses": 22},
|
||||
{"name": "Pedro Lopez", "email": "pedro@demo.com", "elo": 1250, "tier": MembershipTier.SILVER, "color": "#3B82F6", "wins": 14, "losses": 19},
|
||||
{"name": "Rosa Martinez", "email": "rosa@demo.com", "elo": 1190, "tier": MembershipTier.SILVER, "color": "#F97316", "wins": 11, "losses": 21},
|
||||
{"name": "Diego Torres", "email": "diego@demo.com", "elo": 1100, "tier": MembershipTier.SILVER, "color": "#84CC16", "wins": 9, "losses": 18},
|
||||
{"name": "Lucia Flores", "email": "lucia@demo.com", "elo": 980, "tier": MembershipTier.BRONZE, "color": "#14B8A6", "wins": 6, "losses": 20},
|
||||
{"name": "Miguel Ortega", "email": "miguel@demo.com", "elo": 920, "tier": MembershipTier.BRONZE, "color": "#6366F1", "wins": 4, "losses": 15},
|
||||
{"name": "Isabella Vega", "email": "isabella@demo.com", "elo": 850, "tier": MembershipTier.BRONZE, "color": "#D946EF", "wins": 2, "losses": 12},
|
||||
]
|
||||
|
||||
players = []
|
||||
for pd in players_data:
|
||||
p = Player(
|
||||
name=pd["name"],
|
||||
email=pd["email"],
|
||||
elo_rating=pd["elo"],
|
||||
membership_tier=pd["tier"],
|
||||
avatar_color=pd["color"],
|
||||
wins=pd["wins"],
|
||||
losses=pd["losses"],
|
||||
total_matches=pd["wins"] + pd["losses"],
|
||||
)
|
||||
db.add(p)
|
||||
players.append(p)
|
||||
|
||||
db.commit()
|
||||
for p in players:
|
||||
db.refresh(p)
|
||||
print(f"Created {len(players)} players")
|
||||
return players
|
||||
|
||||
|
||||
def seed_courts(db: Session) -> list:
|
||||
courts_data = [
|
||||
{"name": "Court 1", "number": 1, "rate": 200.0, "surface": "Sport Court", "features": "LED Lighting, Pro Net System"},
|
||||
{"name": "Court 2", "number": 2, "rate": 200.0, "surface": "Sport Court", "features": "LED Lighting, Spectator Seating"},
|
||||
{"name": "Court 3", "number": 3, "rate": 250.0, "surface": "Cushioned", "features": "Tournament Grade, Scoreboards"},
|
||||
{"name": "Court 4", "number": 4, "rate": 250.0, "surface": "Cushioned", "features": "Tournament Grade, VIP Lounge Access"},
|
||||
]
|
||||
|
||||
courts = []
|
||||
for cd in courts_data:
|
||||
c = Court(
|
||||
name=cd["name"],
|
||||
court_number=cd["number"],
|
||||
hourly_rate=cd["rate"],
|
||||
surface_type=cd["surface"],
|
||||
features=cd["features"],
|
||||
)
|
||||
db.add(c)
|
||||
courts.append(c)
|
||||
|
||||
db.commit()
|
||||
for c in courts:
|
||||
db.refresh(c)
|
||||
print(f"Created {len(courts)} courts")
|
||||
return courts
|
||||
|
||||
|
||||
def seed_bookings(db: Session, players: list, courts: list):
|
||||
now = datetime.utcnow()
|
||||
bookings = []
|
||||
|
||||
# Future bookings
|
||||
for i in range(8):
|
||||
player = players[i % len(players)]
|
||||
court = courts[i % len(courts)]
|
||||
start = now + timedelta(hours=i + 2)
|
||||
end = start + timedelta(hours=1)
|
||||
b = Booking(
|
||||
player_id=player.id,
|
||||
court_id=court.id,
|
||||
start_time=start,
|
||||
end_time=end,
|
||||
duration_hours=1.0,
|
||||
total_cost=court.hourly_rate,
|
||||
status=BookingStatus.CONFIRMED,
|
||||
)
|
||||
db.add(b)
|
||||
bookings.append(b)
|
||||
|
||||
db.commit()
|
||||
print(f"Created {len(bookings)} bookings")
|
||||
|
||||
|
||||
def seed_active_matches(db: Session, players: list, courts: list) -> list:
|
||||
matches = []
|
||||
|
||||
# Match 1: Active doubles on Court 1 (Open match)
|
||||
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 player_id, team in [(players[0].id, 1), (players[1].id, 1), (players[2].id, 2), (players[3].id, 2)]:
|
||||
mp = MatchPlayer(match_id=m1.id, player_id=player_id, team=team, elo_before=db.query(Player).filter(Player.id == player_id).first().elo_rating)
|
||||
db.add(mp)
|
||||
matches.append(m1)
|
||||
|
||||
# Match 2: Active skill-based on Court 2
|
||||
m2 = Match(
|
||||
court_id=courts[1].id,
|
||||
stage=MatchStage.SKILL_BASED,
|
||||
match_type=MatchType.DOUBLES,
|
||||
status=MatchStatus.IN_PROGRESS,
|
||||
title="Skill Match - Platinum League",
|
||||
max_players=4,
|
||||
min_elo=1300,
|
||||
max_elo=1700,
|
||||
team1_score=3,
|
||||
team2_score=6,
|
||||
started_at=datetime.utcnow() - timedelta(minutes=15),
|
||||
)
|
||||
db.add(m2)
|
||||
db.flush()
|
||||
|
||||
for player_id, team in [(players[4].id, 1), (players[5].id, 1), (players[6].id, 2), (players[7].id, 2)]:
|
||||
mp = MatchPlayer(match_id=m2.id, player_id=player_id, team=team, elo_before=db.query(Player).filter(Player.id == player_id).first().elo_rating)
|
||||
db.add(mp)
|
||||
matches.append(m2)
|
||||
|
||||
# Match 3: Lobby (waiting for players)
|
||||
m3 = Match(
|
||||
stage=MatchStage.OPEN,
|
||||
match_type=MatchType.DOUBLES,
|
||||
status=MatchStatus.LOBBY,
|
||||
title="Evening Open Game",
|
||||
max_players=4,
|
||||
)
|
||||
db.add(m3)
|
||||
db.flush()
|
||||
|
||||
for player_id, team in [(players[8].id, 1), (players[9].id, 2)]:
|
||||
mp = MatchPlayer(match_id=m3.id, player_id=player_id, team=team, elo_before=db.query(Player).filter(Player.id == player_id).first().elo_rating)
|
||||
db.add(mp)
|
||||
matches.append(m3)
|
||||
|
||||
# Match 4: Skill-based lobby
|
||||
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()
|
||||
|
||||
mp = MatchPlayer(match_id=m4.id, player_id=players[4].id, team=1, elo_before=players[4].elo_rating)
|
||||
db.add(mp)
|
||||
matches.append(m4)
|
||||
|
||||
db.commit()
|
||||
for m in matches:
|
||||
db.refresh(m)
|
||||
print(f"Created {len(matches)} matches (2 active, 2 lobby)")
|
||||
return matches
|
||||
|
||||
|
||||
def seed_tournament(db: Session, players: list, courts: list):
|
||||
"""Create a sample double elimination tournament in progress"""
|
||||
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()
|
||||
|
||||
# Register 8 players
|
||||
tournament_players = players[:8]
|
||||
entries = []
|
||||
for i, player in enumerate(tournament_players):
|
||||
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()
|
||||
|
||||
# Round 1 Winners Bracket (4 matches) - all completed
|
||||
r1_matches = [
|
||||
(players[0].id, players[7].id, 11, 5), # 1 vs 8
|
||||
(players[1].id, players[6].id, 11, 7), # 2 vs 7
|
||||
(players[2].id, players[5].id, 8, 11), # 3 vs 6 - player 5 wins
|
||||
(players[3].id, players[4].id, 11, 9), # 4 vs 5
|
||||
]
|
||||
|
||||
winners_r1 = [] # winner player IDs after round 1
|
||||
losers_r1 = [] # loser player IDs after round 1
|
||||
|
||||
for i, (p1_id, p2_id, s1, s2) in enumerate(r1_matches):
|
||||
tm = TournamentMatch(
|
||||
tournament_id=t.id,
|
||||
round_number=1,
|
||||
match_number=i + 1,
|
||||
bracket_type=BracketType.WINNERS,
|
||||
status=TournamentMatchStatus.COMPLETED,
|
||||
player1_id=p1_id,
|
||||
player2_id=p2_id,
|
||||
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)
|
||||
|
||||
winner_id = p1_id if s1 > s2 else p2_id
|
||||
loser_id = p2_id if s1 > s2 else p1_id
|
||||
winners_r1.append(winner_id)
|
||||
losers_r1.append(loser_id)
|
||||
|
||||
# Update entry stats
|
||||
for entry in entries:
|
||||
if entry.player_id == winner_id:
|
||||
entry.wins += 1
|
||||
elif entry.player_id == loser_id:
|
||||
entry.losses += 1
|
||||
entry.is_in_losers = True
|
||||
|
||||
db.flush()
|
||||
|
||||
# Round 2 Winners Bracket - 2 matches - 1 completed, 1 in progress
|
||||
# Match 1: players[0] vs players[1]
|
||||
wm1 = TournamentMatch(
|
||||
tournament_id=t.id,
|
||||
round_number=2,
|
||||
match_number=1,
|
||||
bracket_type=BracketType.WINNERS,
|
||||
status=TournamentMatchStatus.COMPLETED,
|
||||
player1_id=winners_r1[0],
|
||||
player2_id=winners_r1[1],
|
||||
team1_score=11,
|
||||
team2_score=8,
|
||||
winner_team=1,
|
||||
completed_at=datetime.utcnow() - timedelta(minutes=45),
|
||||
)
|
||||
db.add(wm1)
|
||||
|
||||
# Update entries
|
||||
for entry in entries:
|
||||
if entry.player_id == winners_r1[0]:
|
||||
entry.wins += 1
|
||||
elif entry.player_id == winners_r1[1]:
|
||||
entry.losses += 1
|
||||
entry.is_in_losers = True
|
||||
|
||||
# Match 2: players[3] (seed4 winner) vs players[5] (seed6 winner)
|
||||
wm2 = TournamentMatch(
|
||||
tournament_id=t.id,
|
||||
round_number=2,
|
||||
match_number=2,
|
||||
bracket_type=BracketType.WINNERS,
|
||||
status=TournamentMatchStatus.IN_PROGRESS,
|
||||
player1_id=winners_r1[2],
|
||||
player2_id=winners_r1[3],
|
||||
team1_score=6,
|
||||
team2_score=7,
|
||||
)
|
||||
db.add(wm2)
|
||||
|
||||
# Assign court 3 for active tournament match
|
||||
m_active = Match(
|
||||
court_id=courts[2].id,
|
||||
stage=MatchStage.TOURNAMENT,
|
||||
match_type=MatchType.SINGLES,
|
||||
status=MatchStatus.IN_PROGRESS,
|
||||
title=f"Tournament R2 - {db.query(Player).filter(Player.id == winners_r1[2]).first().name} vs {db.query(Player).filter(Player.id == winners_r1[3]).first().name}",
|
||||
max_players=2,
|
||||
team1_score=6,
|
||||
team2_score=7,
|
||||
started_at=datetime.utcnow() - timedelta(minutes=20),
|
||||
)
|
||||
db.add(m_active)
|
||||
db.flush()
|
||||
|
||||
for player_id, team in [(winners_r1[2], 1), (winners_r1[3], 2)]:
|
||||
mp = MatchPlayer(match_id=m_active.id, player_id=player_id, team=team,
|
||||
elo_before=db.query(Player).filter(Player.id == player_id).first().elo_rating)
|
||||
db.add(mp)
|
||||
|
||||
# Losers Bracket Round 1 - 2 matches (losers from r1 matched up)
|
||||
lb1 = TournamentMatch(
|
||||
tournament_id=t.id,
|
||||
round_number=1,
|
||||
match_number=1,
|
||||
bracket_type=BracketType.LOSERS,
|
||||
status=TournamentMatchStatus.COMPLETED,
|
||||
player1_id=losers_r1[0],
|
||||
player2_id=losers_r1[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_r1[2],
|
||||
player2_id=losers_r1[3],
|
||||
team1_score=11,
|
||||
team2_score=6,
|
||||
winner_team=1,
|
||||
completed_at=datetime.utcnow() - timedelta(minutes=60),
|
||||
)
|
||||
db.add(lb2)
|
||||
|
||||
# Update loser entries
|
||||
for entry in entries:
|
||||
if entry.player_id == losers_r1[0]:
|
||||
entry.losses += 1
|
||||
entry.is_eliminated = True
|
||||
entry.final_rank = 8
|
||||
elif entry.player_id == losers_r1[3]:
|
||||
entry.losses += 1
|
||||
entry.is_eliminated = True
|
||||
entry.final_rank = 7
|
||||
|
||||
db.commit()
|
||||
db.refresh(t)
|
||||
print(f"Created tournament: {t.name}")
|
||||
return t
|
||||
|
||||
|
||||
def seed_completed_matches(db: Session, players: list, courts: list):
|
||||
"""Create some historical completed matches"""
|
||||
past_matches = []
|
||||
for i in range(6):
|
||||
start = datetime.utcnow() - timedelta(days=i + 1, hours=2)
|
||||
p_list = random.sample(players, 4)
|
||||
m = Match(
|
||||
court_id=courts[i % len(courts)].id,
|
||||
stage=MatchStage.OPEN if i % 2 == 0 else MatchStage.SKILL_BASED,
|
||||
match_type=MatchType.DOUBLES,
|
||||
status=MatchStatus.COMPLETED,
|
||||
title=f"Completed Match #{i + 1}",
|
||||
max_players=4,
|
||||
team1_score=11,
|
||||
team2_score=random.randint(5, 10),
|
||||
started_at=start,
|
||||
ended_at=start + timedelta(hours=1),
|
||||
)
|
||||
db.add(m)
|
||||
db.flush()
|
||||
|
||||
for player, team in zip(p_list, [1, 1, 2, 2]):
|
||||
mp = MatchPlayer(match_id=m.id, player_id=player.id, team=team,
|
||||
elo_before=player.elo_rating, elo_after=player.elo_rating + random.uniform(-10, 10),
|
||||
elo_change=random.uniform(-10, 10), is_winner=(team == 1))
|
||||
db.add(mp)
|
||||
past_matches.append(m)
|
||||
|
||||
db.commit()
|
||||
print(f"Created {len(past_matches)} completed matches")
|
||||
|
||||
|
||||
def main():
|
||||
print("🌱 Seeding ServeSync demo data...")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
clear_data(db)
|
||||
players = seed_players(db)
|
||||
courts = seed_courts(db)
|
||||
seed_bookings(db, players, courts)
|
||||
seed_active_matches(db, players, courts)
|
||||
seed_tournament(db, players, courts)
|
||||
seed_completed_matches(db, players, courts)
|
||||
print("✅ Seed complete!")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user