Files
enigFM/backend/app/services/sync.py
mamonov.ep 0fb16f791d Add user ping system and room deletion functionality
Backend changes:
- Fix track deletion foreign key constraint (tracks.py)
  * Clear current_track_id from rooms before deleting track
  * Prevent deletion errors when track is currently playing

- Implement user ping/keepalive system (sync.py, websocket.py, ping_task.py, main.py)
  * Track last pong timestamp for each user
  * Background task sends ping every 30s, disconnects users after 60s timeout
  * Auto-pause playback when room becomes empty
  * Remove disconnected users from room_participants

- Enhance room deletion (rooms.py)
  * Broadcast room_deleted event to all connected users
  * Close all WebSocket connections before deletion
  * Cascade delete participants, queue, and messages

Frontend changes:
- Add ping/pong WebSocket handling (activeRoom.js)
  * Auto-respond to server pings
  * Handle room_deleted event with redirect to home

- Add room deletion UI (RoomView.vue, HomeView.vue, RoomCard.vue)
  * Delete button visible only to room owner
  * Confirmation dialog with warning
  * Delete button on room cards (shows on hover)
  * Redirect to home page after deletion

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 20:46:00 +03:00

89 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Dict, Set
from fastapi import WebSocket
from uuid import UUID
import json
import time
import asyncio
class ConnectionManager:
def __init__(self):
# room_id -> set of (websocket, user_id)
self.active_connections: Dict[UUID, Set[tuple[WebSocket, UUID]]] = {}
# (room_id, user_id) -> last pong timestamp
self.last_pong: Dict[tuple[UUID, UUID], float] = {}
# Ping interval and timeout in seconds
self.ping_interval = 30 # Отправлять ping каждые 30 секунд
self.ping_timeout = 60 # Отключать если нет ответа 60 секунд
async def connect(self, websocket: WebSocket, room_id: UUID, user_id: UUID):
await websocket.accept()
if room_id not in self.active_connections:
self.active_connections[room_id] = set()
self.active_connections[room_id].add((websocket, user_id))
# Инициализируем время последнего pong
self.last_pong[(room_id, user_id)] = time.time()
def disconnect(self, websocket: WebSocket, room_id: UUID, user_id: UUID):
if room_id in self.active_connections:
self.active_connections[room_id].discard((websocket, user_id))
if not self.active_connections[room_id]:
del self.active_connections[room_id]
# Удаляем запись о последнем pong
self.last_pong.pop((room_id, user_id), None)
def update_pong(self, room_id: UUID, user_id: UUID):
"""Обновить время последнего pong от пользователя"""
self.last_pong[(room_id, user_id)] = time.time()
async def broadcast_to_room(self, room_id: UUID, message: dict, exclude_user: UUID = None):
if room_id not in self.active_connections:
return
message_json = json.dumps(message, default=str)
disconnected = []
for websocket, user_id in self.active_connections[room_id]:
if exclude_user and user_id == exclude_user:
continue
try:
await websocket.send_text(message_json)
except Exception:
disconnected.append((websocket, user_id))
for conn in disconnected:
self.active_connections[room_id].discard(conn)
async def send_to_user(self, room_id: UUID, user_id: UUID, message: dict):
"""Отправить сообщение конкретному пользователю в комнате"""
if room_id not in self.active_connections:
return False
message_json = json.dumps(message, default=str)
for websocket, uid in self.active_connections[room_id]:
if uid == user_id:
try:
await websocket.send_text(message_json)
return True
except Exception:
return False
return False
def get_room_user_count(self, room_id: UUID) -> int:
if room_id not in self.active_connections:
return 0
return len(self.active_connections[room_id])
def get_room_users(self, room_id: UUID) -> Set[UUID]:
"""Получить список user_id в комнате"""
if room_id not in self.active_connections:
return set()
return {user_id for _, user_id in self.active_connections[room_id]}
def get_all_connections(self) -> Dict[UUID, Set[tuple[WebSocket, UUID]]]:
"""Получить все активные соединения (для фоновой задачи пинга)"""
return self.active_connections
manager = ConnectionManager()