using System; using System.Collections.Concurrent; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace FinlyticBackend.Hubs; /// /// SignalR Hub streaming real-time stock prices & daily % growth updates for favorite assets every 10 seconds. /// [Authorize] public class FavoritesPriceHub : Hub { private readonly ILogger _logger; // Speichert thread-sicher, wie viele aktive Verbindungen ein User hat (UserId -> ConnectionCount) private static readonly ConcurrentDictionary ActiveUserConnections = new(); public FavoritesPriceHub(ILogger logger) { _logger = logger; } /// /// Liefert eine Übersicht aller aktuell mit dem Hub verbundenen User-IDs. /// public static string[] GetActiveUserIds() => [.. ActiveUserConnections.Keys]; public override async Task OnConnectedAsync() { var userId = Context.UserIdentifier; if (!string.IsNullOrEmpty(userId)) { // Füge die Verbindung der benutzerspezifischen Gruppe hinzu await Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName(userId)); ActiveUserConnections.AddOrUpdate(userId, 1, (_, count) => count + 1); _logger.LogInformation("[FavoritesPriceHub] User '{UserId}' connected (ConnectionId={ConnectionId}). Active connections for user: {Count}", userId, Context.ConnectionId, ActiveUserConnections[userId]); } else { _logger.LogWarning("[FavoritesPriceHub] Anonymous SignalR client connected without UserIdentifier: ConnectionId={ConnectionId}", Context.ConnectionId); } await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { var userId = Context.UserIdentifier; if (!string.IsNullOrEmpty(userId)) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName(userId)); ActiveUserConnections.AddOrUpdate(userId, 0, (_, count) => { var newCount = count - 1; return newCount < 0 ? 0 : newCount; }); // Wenn keine aktiven Verbindungen mehr bestehen, aus Dictionary entfernen if (ActiveUserConnections.TryGetValue(userId, out var remainingCount) && remainingCount <= 0) { ActiveUserConnections.TryRemove(userId, out _); } _logger.LogInformation("[FavoritesPriceHub] User '{UserId}' disconnected (ConnectionId={ConnectionId})", userId, Context.ConnectionId); } await base.OnDisconnectedAsync(exception); } public static string GetGroupName(string userId) => $"User_{userId.Trim()}"; }