feat: Rebuild as Court Manager Dashboard v2
- Complete rewrite for single Court Manager user - Feature 1: Player Management (CRUD, ELO, skill level) - Feature 2: Court Reservation (timeline, override) - Feature 3: Match Event Builder (3-step wizard) - Step 1: Event setup with player selection + stage calculator - Step 2: Stage configurator (Open/Skill/RR/Tournament) - Step 3: Auto bracket generation - Feature 4: Live Court View (main dashboard, 2x2 grid) - Event Control: score entry, bracket advancement, court assignment - Screen view: TV display per court (/screen/:id) - Seed: 8 players, 4 courts, 1 completed + 1 active event - Routes: / players courts events events/new events/:id screen/:id
This commit is contained in:
@@ -1,27 +1,15 @@
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from .database import engine
|
||||
from .models import Base
|
||||
from .routers import players, courts, events, matches, dashboard, admin
|
||||
from .websocket_manager import manager
|
||||
import asyncio
|
||||
import json
|
||||
from typing import List, Dict
|
||||
|
||||
from app.database import engine, SessionLocal
|
||||
from app.models import player, court, booking, match, tournament
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# 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, admin
|
||||
|
||||
app = FastAPI(
|
||||
title="ServeSync API",
|
||||
description="Pickleball Court Management System",
|
||||
version="1.0.0",
|
||||
)
|
||||
app = FastAPI(title="ServeSync API", version="2.0.0")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -31,112 +19,37 @@ app.add_middleware(
|
||||
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")
|
||||
app.include_router(admin.router, prefix="/api")
|
||||
app.include_router(players.router)
|
||||
app.include_router(courts.router)
|
||||
app.include_router(events.router)
|
||||
app.include_router(matches.router)
|
||||
app.include_router(dashboard.router)
|
||||
app.include_router(admin.router)
|
||||
|
||||
|
||||
# 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")
|
||||
@app.get("/api/health")
|
||||
def health():
|
||||
return {"status": "healthy"}
|
||||
return {"status": "ok", "version": "2.0.0"}
|
||||
|
||||
|
||||
@app.websocket("/ws/dashboard")
|
||||
async def ws_dashboard(websocket: WebSocket):
|
||||
await manager.connect(websocket, "dashboard")
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(3)
|
||||
await manager.broadcast("dashboard", {"type": "ping"})
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, "dashboard")
|
||||
|
||||
|
||||
@app.websocket("/ws/screen/{court_id}")
|
||||
async def ws_screen(websocket: WebSocket, court_id: int):
|
||||
channel = f"screen_{court_id}"
|
||||
await manager.connect(websocket, channel)
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(5)
|
||||
await manager.broadcast(channel, {"type": "ping", "court_id": court_id})
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, channel)
|
||||
|
||||
Reference in New Issue
Block a user