🏓 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:
Forge
2026-03-19 00:58:17 +08:00
commit 46fd2953c3
45 changed files with 4383 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
from sqlalchemy import Column, Integer, ForeignKey, DateTime, Float, String, Enum, Boolean, JSON
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
import enum
class MatchStage(str, enum.Enum):
OPEN = "open"
SKILL_BASED = "skill_based"
TOURNAMENT = "tournament"
class MatchStatus(str, enum.Enum):
LOBBY = "lobby"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
CANCELLED = "cancelled"
class MatchType(str, enum.Enum):
SINGLES = "singles"
DOUBLES = "doubles"
class Match(Base):
__tablename__ = "matches"
id = Column(Integer, primary_key=True, index=True)
court_id = Column(Integer, ForeignKey("courts.id"), nullable=True)
stage = Column(Enum(MatchStage), default=MatchStage.OPEN)
match_type = Column(Enum(MatchType), default=MatchType.DOUBLES)
status = Column(Enum(MatchStatus), default=MatchStatus.LOBBY)
# Scores
team1_score = Column(Integer, default=0)
team2_score = Column(Integer, default=0)
team1_games = Column(Integer, default=0)
team2_games = Column(Integer, default=0)
# Game details
max_players = Column(Integer, default=4)
min_elo = Column(Float, nullable=True)
max_elo = Column(Float, nullable=True)
# Match metadata
title = Column(String(200))
started_at = Column(DateTime(timezone=True), nullable=True)
ended_at = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships
court = relationship("Court", back_populates="matches")
match_players = relationship("MatchPlayer", back_populates="match", cascade="all, delete-orphan")
class MatchPlayer(Base):
__tablename__ = "match_players"
id = Column(Integer, primary_key=True, index=True)
match_id = Column(Integer, ForeignKey("matches.id"), nullable=False)
player_id = Column(Integer, ForeignKey("players.id"), nullable=False)
team = Column(Integer, nullable=False) # 1 or 2
elo_before = Column(Float, nullable=True)
elo_after = Column(Float, nullable=True)
elo_change = Column(Float, default=0.0)
is_winner = Column(Boolean, nullable=True)
joined_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
match = relationship("Match", back_populates="match_players")
player = relationship("Player", back_populates="match_players")