feat: BreakPilot PWA - Full codebase (clean push without large binaries)
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
This commit is contained in:
143
backend/classroom/routes/websocket_routes.py
Normal file
143
backend/classroom/routes/websocket_routes.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Classroom API - WebSocket Routes
|
||||
|
||||
Real-time WebSocket endpoints for timer updates.
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
|
||||
from ..services.persistence import (
|
||||
sessions,
|
||||
init_db_if_needed,
|
||||
DB_ENABLED,
|
||||
SessionLocal,
|
||||
)
|
||||
from ..websocket_manager import (
|
||||
ws_manager,
|
||||
start_timer_broadcast,
|
||||
build_timer_status,
|
||||
is_timer_broadcast_running,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["WebSocket"])
|
||||
|
||||
|
||||
@router.websocket("/ws/{session_id}")
|
||||
async def websocket_timer(websocket: WebSocket, session_id: str):
|
||||
"""
|
||||
WebSocket-Endpoint fuer Echtzeit-Timer-Updates.
|
||||
|
||||
Features:
|
||||
- Sub-Sekunden Timer-Updates (jede Sekunde)
|
||||
- Phasenwechsel-Benachrichtigungen
|
||||
- Session-Ende-Benachrichtigungen
|
||||
- Multi-Device Support
|
||||
|
||||
Protocol:
|
||||
- Server sendet JSON-Messages mit "type" und "data"
|
||||
- Types: "timer_update", "phase_change", "session_ended", "error", "connected"
|
||||
- Client kann "ping" senden fuer Keepalive
|
||||
"""
|
||||
# Session validieren bevor Connect
|
||||
session = sessions.get(session_id)
|
||||
if not session:
|
||||
# Versuche aus DB zu laden
|
||||
if DB_ENABLED:
|
||||
try:
|
||||
init_db_if_needed()
|
||||
from classroom_engine.repository import SessionRepository
|
||||
db = SessionLocal()
|
||||
repo = SessionRepository(db)
|
||||
db_session = repo.get_by_id(session_id)
|
||||
if db_session:
|
||||
session = repo.to_dataclass(db_session)
|
||||
sessions[session_id] = session
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket: Failed to load session {session_id}: {e}")
|
||||
|
||||
if not session:
|
||||
await websocket.close(code=4004, reason="Session not found")
|
||||
return
|
||||
|
||||
if session.is_ended:
|
||||
await websocket.close(code=4001, reason="Session already ended")
|
||||
return
|
||||
|
||||
# Verbindung akzeptieren und registrieren
|
||||
await ws_manager.connect(websocket, session_id)
|
||||
|
||||
# Timer-Broadcast-Task starten wenn noetig
|
||||
start_timer_broadcast(sessions)
|
||||
|
||||
# Initiale Daten senden
|
||||
try:
|
||||
initial_timer = build_timer_status(session)
|
||||
await websocket.send_json({
|
||||
"type": "connected",
|
||||
"data": {
|
||||
"session_id": session_id,
|
||||
"client_count": ws_manager.get_client_count(session_id),
|
||||
"timer": initial_timer
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket: Failed to send initial data: {e}")
|
||||
|
||||
# Message-Loop
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
message = await websocket.receive_text()
|
||||
data = json.loads(message)
|
||||
|
||||
if data.get("type") == "ping":
|
||||
await websocket.send_json({"type": "pong"})
|
||||
elif data.get("type") == "get_timer":
|
||||
# Client kann manuell Timer-Status anfordern
|
||||
session = sessions.get(session_id)
|
||||
if session and not session.is_ended:
|
||||
timer_data = build_timer_status(session)
|
||||
await websocket.send_json({
|
||||
"type": "timer_update",
|
||||
"data": timer_data
|
||||
})
|
||||
except json.JSONDecodeError:
|
||||
await websocket.send_json({
|
||||
"type": "error",
|
||||
"data": {"message": "Invalid JSON"}
|
||||
})
|
||||
except WebSocketDisconnect:
|
||||
logger.info(f"WebSocket: Client disconnected from session {session_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket error: {e}")
|
||||
finally:
|
||||
await ws_manager.disconnect(websocket)
|
||||
|
||||
|
||||
@router.get("/ws/status")
|
||||
async def websocket_status() -> Dict[str, Any]:
|
||||
"""
|
||||
Status-Endpoint fuer WebSocket-Verbindungen.
|
||||
|
||||
Zeigt aktive Sessions und Verbindungszahlen.
|
||||
"""
|
||||
active_sessions = ws_manager.get_active_sessions()
|
||||
return {
|
||||
"active_sessions": len(active_sessions),
|
||||
"sessions": [
|
||||
{
|
||||
"session_id": sid,
|
||||
"client_count": ws_manager.get_client_count(sid)
|
||||
}
|
||||
for sid in active_sessions
|
||||
],
|
||||
"broadcast_task_running": is_timer_broadcast_running()
|
||||
}
|
||||
Reference in New Issue
Block a user