< Summary

Information
Class: Services.SignalRServices.NotificationHub
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SignalRServices/NotificationHub.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 105
Coverable lines: 105
Total lines: 228
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 54
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%210%
OnConnectedAsync()0%4260%
OnDisconnectedAsync()100%210%
UpdateCurrentPage()0%2040%
GetConnectedUsersCount()100%210%
GetConnectedUsers()0%620%
GetConnectedUsersWithRoles()0%7280%
GetUsersWithRoles()0%930300%
FormatDuration(...)0%2040%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SignalRServices/NotificationHub.cs

#LineLine coverage
 1using Microsoft.AspNetCore.SignalR;
 2using Microsoft.AspNetCore.Identity;
 3using System;
 4using System.Collections.Generic;
 5using System.Threading.Tasks;
 6using System.Collections.Concurrent;
 7using System.Linq;
 8using OzonRepositories.Context.Identity;
 9
 10namespace Services.SignalRServices
 11{
 12    public class UserSessionInfo
 13    {
 14        public string UserName { get; set; } = null!;
 15        public DateTime ConnectedAt { get; set; }
 16        public string CurrentPage { get; set; } = null!;
 17    }
 18
 19    public class NotificationHub : Hub
 20    {
 021        private static readonly ConcurrentDictionary<string, UserSessionInfo> ConnectedUsers = new();
 022        private static readonly ConcurrentDictionary<string, (List<object> Result, DateTime ExpiresAt)> UsersWithRolesCa
 023        private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(60);
 24
 25        private readonly UserManager<CustomIdentityUser> _userManager;
 26
 027        public NotificationHub(UserManager<CustomIdentityUser> userManager)
 28        {
 029            _userManager = userManager;
 030        }
 31
 32        public override async Task OnConnectedAsync()
 33        {
 034            var connectionId = Context.ConnectionId;
 035            var userName = Context.User?.Identity?.Name ?? "Anonymous";
 036            ConnectedUsers.TryAdd(connectionId, new UserSessionInfo
 037            {
 038                UserName = userName,
 039                ConnectedAt = DateTime.UtcNow,
 040                CurrentPage = "Неизвестно"
 041            });
 042            await base.OnConnectedAsync();
 43            // Отправляем обновленный список пользователей всем клиентам
 044            var usersList = await GetUsersWithRoles();
 045            await Clients.All.SendAsync("UserConnected", ConnectedUsers.Count, usersList);
 046        }
 47
 48        public override async Task OnDisconnectedAsync(Exception? exception)
 49        {
 050            var connectionId = Context.ConnectionId;
 051            ConnectedUsers.TryRemove(connectionId, out _);
 052            await base.OnDisconnectedAsync(exception);
 53            // Отправляем обновленный список пользователей всем клиентам
 054            var usersList = await GetUsersWithRoles();
 055            await Clients.All.SendAsync("UserDisconnected", ConnectedUsers.Count, usersList);
 056        }
 57
 58        // Метод для обновления текущей страницы пользователя
 59        public async Task UpdateCurrentPage(string pagePath)
 60        {
 061            var connectionId = Context.ConnectionId;
 062            if (ConnectedUsers.TryGetValue(connectionId, out var sessionInfo))
 63            {
 064                sessionInfo.CurrentPage = pagePath ?? "Неизвестно";
 65                // Отправляем обновленный список всем клиентам
 066                var usersList = await GetUsersWithRoles();
 067                await Clients.All.SendAsync("UsersListUpdated", usersList);
 68            }
 069        }
 70
 71        public Task<int> GetConnectedUsersCount()
 72        {
 073            return Task.FromResult(ConnectedUsers.Count);
 74        }
 75
 76        public Task<IEnumerable<string>> GetConnectedUsers()
 77        {
 78            // Возвращаем только уникальные имена пользователей (без анонимных)
 079            return Task.FromResult(ConnectedUsers.Values
 080                .Where(u => !string.IsNullOrEmpty(u.UserName) && u.UserName != "Anonymous")
 081                .Select(u => u.UserName)
 082                .Distinct()
 083                .AsEnumerable());
 84        }
 85
 86        public async Task<IEnumerable<object>> GetConnectedUsersWithRoles()
 87        {
 88            // Проверяем, является ли текущий пользователь админом
 089            var isAdmin = false;
 090            if (Context.User?.Identity?.IsAuthenticated == true)
 91            {
 092                var currentUser = await _userManager.GetUserAsync(Context.User);
 093                if (currentUser != null)
 94                {
 095                    var roles = await _userManager.GetRolesAsync(currentUser);
 096                    isAdmin = roles.Contains("Admin");
 97                }
 98            }
 099            return await GetUsersWithRoles(isAdmin);
 0100        }
 101
 102        private async Task<List<object>> GetUsersWithRoles(bool includeSessionInfo = false)
 103        {
 104            // Группируем по имени пользователя, берем последнюю сессию
 0105            var uniqueUserSessions = ConnectedUsers.Values
 106                .Where(u => !string.IsNullOrEmpty(u.UserName) && u.UserName != "Anonymous")
 107                .GroupBy(u => u.UserName)
 108                .Select(g => g.OrderByDescending(u => u.ConnectedAt).First())
 0109                .ToList();
 110
 111            // Кэш только для includeSessionInfo=false (роли не меняются часто; session info меняется при UpdateCurrentP
 0112            if (!includeSessionInfo && uniqueUserSessions.Count > 0)
 113            {
 114                var cacheKey = string.Join(",", uniqueUserSessions.OrderBy(u => u.UserName).Select(u => u.UserName));
 0115                if (UsersWithRolesCache.TryGetValue(cacheKey, out var cached) && cached.ExpiresAt > DateTime.UtcNow)
 116                {
 0117                    return cached.Result;
 118                }
 119            }
 120
 0121            var usersWithRoles = new List<object>();
 122
 0123            foreach (var sessionInfo in uniqueUserSessions)
 124            {
 125                try
 126                {
 0127                    var user = await _userManager.FindByNameAsync(sessionInfo.UserName);
 0128                    if (user != null)
 129                    {
 0130                        var roles = await _userManager.GetRolesAsync(user);
 0131                        var role = roles.Contains("Admin") ? "Admin" : (roles.Contains("User") ? "User" : "None");
 132
 0133                        var userInfo = new {
 0134                            name = sessionInfo.UserName,
 0135                            role = role
 0136                        };
 137
 138                        // Добавляем информацию о сеансе только для админов
 0139                        if (includeSessionInfo)
 140                        {
 0141                            var sessionDuration = DateTime.UtcNow - sessionInfo.ConnectedAt;
 0142                            var durationString = FormatDuration(sessionDuration);
 143
 144                            // Используем dynamic для добавления свойств
 0145                            var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 0146                            dynamicUserInfo["name"] = sessionInfo.UserName;
 0147                            dynamicUserInfo["role"] = role;
 0148                            dynamicUserInfo["sessionDuration"] = durationString;
 0149                            dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 0150                            dynamicUserInfo["connectedAt"] = sessionInfo.ConnectedAt.ToString("yyyy-MM-dd HH:mm:ss");
 151
 0152                            usersWithRoles.Add(dynamicUserInfo);
 153                        }
 154                        else
 155                        {
 0156                            usersWithRoles.Add(userInfo);
 157                        }
 158                    }
 159                    else
 160                    {
 0161                        if (includeSessionInfo)
 162                        {
 0163                            var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 0164                            dynamicUserInfo["name"] = sessionInfo.UserName;
 0165                            dynamicUserInfo["role"] = "None";
 0166                            dynamicUserInfo["sessionDuration"] = FormatDuration(DateTime.UtcNow - sessionInfo.ConnectedA
 0167                            dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 0168                            usersWithRoles.Add(dynamicUserInfo);
 169                        }
 170                        else
 171                        {
 0172                            usersWithRoles.Add(new { name = sessionInfo.UserName, role = "None" });
 173                        }
 174                    }
 0175                }
 0176                catch
 177                {
 0178                    if (includeSessionInfo)
 179                    {
 0180                        var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 0181                        dynamicUserInfo["name"] = sessionInfo.UserName;
 0182                        dynamicUserInfo["role"] = "None";
 0183                        dynamicUserInfo["sessionDuration"] = FormatDuration(DateTime.UtcNow - sessionInfo.ConnectedAt);
 0184                        dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 0185                        usersWithRoles.Add(dynamicUserInfo);
 186                    }
 187                    else
 188                    {
 0189                        usersWithRoles.Add(new { name = sessionInfo.UserName, role = "None" });
 190                    }
 0191                }
 0192            }
 193
 0194            if (!includeSessionInfo && uniqueUserSessions.Count > 0)
 195            {
 196                var cacheKey = string.Join(",", uniqueUserSessions.OrderBy(u => u.UserName).Select(u => u.UserName));
 0197                UsersWithRolesCache.AddOrUpdate(cacheKey,
 0198                    _ => (usersWithRoles, DateTime.UtcNow + CacheTtl),
 0199                    (_, _) => (usersWithRoles, DateTime.UtcNow + CacheTtl));
 200                // Удаляем устаревшие записи, чтобы не накапливать кэш
 0201                var now = DateTime.UtcNow;
 0202                foreach (var kvp in UsersWithRolesCache)
 203                {
 0204                    if (kvp.Value.ExpiresAt < now)
 0205                        UsersWithRolesCache.TryRemove(kvp.Key, out _);
 206                }
 207            }
 208
 0209            return usersWithRoles;
 0210        }
 211
 212        private string FormatDuration(TimeSpan duration)
 213        {
 0214            if (duration.TotalDays >= 1)
 215            {
 0216                return $"{(int)duration.TotalDays}д {duration.Hours}ч {duration.Minutes}м";
 217            }
 0218            else if (duration.TotalHours >= 1)
 219            {
 0220                return $"{duration.Hours}ч {duration.Minutes}м";
 221            }
 222            else
 223            {
 0224                return $"{duration.Minutes}м {duration.Seconds}с";
 225            }
 226        }
 227    }
 228}