🏓 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:
141
backend/app/main.py
Normal file
141
backend/app/main.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
import asyncio
|
||||
import json
|
||||
from typing import List, Dict
|
||||
|
||||
from app.database import engine, SessionLocal
|
||||
from app.models import player, court, booking, match, tournament
|
||||
|
||||
# Create all tables
|
||||
player.Base.metadata.create_all(bind=engine)
|
||||
court.Base.metadata.create_all(bind=engine)
|
||||
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
|
||||
|
||||
app = FastAPI(
|
||||
title="ServeSync API",
|
||||
description="Pickleball Court Management System",
|
||||
version="1.0.0",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(players.router, prefix="/api")
|
||||
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")
|
||||
|
||||
|
||||
# WebSocket connection manager
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: Dict[str, List[WebSocket]] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, channel: str):
|
||||
await websocket.accept()
|
||||
if channel not in self.active_connections:
|
||||
self.active_connections[channel] = []
|
||||
self.active_connections[channel].append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket, channel: str):
|
||||
if channel in self.active_connections:
|
||||
self.active_connections[channel].remove(websocket)
|
||||
|
||||
async def broadcast(self, channel: str, data: dict):
|
||||
if channel in self.active_connections:
|
||||
disconnected = []
|
||||
for connection in self.active_connections[channel]:
|
||||
try:
|
||||
await connection.send_text(json.dumps(data))
|
||||
except Exception:
|
||||
disconnected.append(connection)
|
||||
for conn in disconnected:
|
||||
self.active_connections[channel].remove(conn)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
@app.websocket("/ws/court/{court_id}")
|
||||
async def websocket_court(websocket: WebSocket, court_id: int):
|
||||
"""WebSocket for real-time court display updates"""
|
||||
channel = f"court_{court_id}"
|
||||
await manager.connect(websocket, channel)
|
||||
try:
|
||||
while True:
|
||||
# Send court data every 3 seconds
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from app.api.screen import get_court_display
|
||||
data = get_court_display(court_id, db)
|
||||
await websocket.send_text(json.dumps(data))
|
||||
finally:
|
||||
db.close()
|
||||
await asyncio.sleep(3)
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, channel)
|
||||
|
||||
|
||||
@app.websocket("/ws/overview")
|
||||
async def websocket_overview(websocket: WebSocket):
|
||||
"""WebSocket for real-time overview display"""
|
||||
channel = "overview"
|
||||
await manager.connect(websocket, channel)
|
||||
try:
|
||||
while True:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from app.api.screen import get_overview
|
||||
data = get_overview(db)
|
||||
await websocket.send_text(json.dumps(data))
|
||||
finally:
|
||||
db.close()
|
||||
await asyncio.sleep(3)
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, channel)
|
||||
|
||||
|
||||
@app.websocket("/ws/matches")
|
||||
async def websocket_matches(websocket: WebSocket):
|
||||
"""WebSocket for real-time match lobby updates"""
|
||||
channel = "matches"
|
||||
await manager.connect(websocket, channel)
|
||||
try:
|
||||
while True:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from app.api.matches import get_lobby
|
||||
from app.api.matches import get_active_matches
|
||||
data = {
|
||||
"lobby": get_lobby(db),
|
||||
"active": get_active_matches(db),
|
||||
}
|
||||
await websocket.send_text(json.dumps(data))
|
||||
finally:
|
||||
db.close()
|
||||
await asyncio.sleep(2)
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, channel)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "ServeSync API v1.0", "status": "running"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "healthy"}
|
||||
Reference in New Issue
Block a user