< Summary

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

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_UserName()100%210%
get_ConnectedAt()100%210%
get_CurrentPage()100%210%

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    {
 014        public string UserName { get; set; } = null!;
 015        public DateTime ConnectedAt { get; set; }
 016        public string CurrentPage { get; set; } = null!;
 17    }
 18
 19    public class NotificationHub : Hub
 20    {
 21        private static readonly ConcurrentDictionary<string, UserSessionInfo> ConnectedUsers = new();
 22        private static readonly ConcurrentDictionary<string, (List<object> Result, DateTime ExpiresAt)> UsersWithRolesCa
 23        private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(60);
 24
 25        private readonly UserManager<CustomIdentityUser> _userManager;
 26
 27        public NotificationHub(UserManager<CustomIdentityUser> userManager)
 28        {
 29            _userManager = userManager;
 30        }
 31
 32        public override async Task OnConnectedAsync()
 33        {
 34            var connectionId = Context.ConnectionId;
 35            var userName = Context.User?.Identity?.Name ?? "Anonymous";
 36            ConnectedUsers.TryAdd(connectionId, new UserSessionInfo
 37            {
 38                UserName = userName,
 39                ConnectedAt = DateTime.UtcNow,
 40                CurrentPage = "Неизвестно"
 41            });
 42            await base.OnConnectedAsync();
 43            // Отправляем обновленный список пользователей всем клиентам
 44            var usersList = await GetUsersWithRoles();
 45            await Clients.All.SendAsync("UserConnected", ConnectedUsers.Count, usersList);
 46        }
 47
 48        public override async Task OnDisconnectedAsync(Exception? exception)
 49        {
 50            var connectionId = Context.ConnectionId;
 51            ConnectedUsers.TryRemove(connectionId, out _);
 52            await base.OnDisconnectedAsync(exception);
 53            // Отправляем обновленный список пользователей всем клиентам
 54            var usersList = await GetUsersWithRoles();
 55            await Clients.All.SendAsync("UserDisconnected", ConnectedUsers.Count, usersList);
 56        }
 57
 58        // Метод для обновления текущей страницы пользователя
 59        public async Task UpdateCurrentPage(string pagePath)
 60        {
 61            var connectionId = Context.ConnectionId;
 62            if (ConnectedUsers.TryGetValue(connectionId, out var sessionInfo))
 63            {
 64                sessionInfo.CurrentPage = pagePath ?? "Неизвестно";
 65                // Отправляем обновленный список всем клиентам
 66                var usersList = await GetUsersWithRoles();
 67                await Clients.All.SendAsync("UsersListUpdated", usersList);
 68            }
 69        }
 70
 71        public Task<int> GetConnectedUsersCount()
 72        {
 73            return Task.FromResult(ConnectedUsers.Count);
 74        }
 75
 76        public Task<IEnumerable<string>> GetConnectedUsers()
 77        {
 78            // Возвращаем только уникальные имена пользователей (без анонимных)
 79            return Task.FromResult(ConnectedUsers.Values
 80                .Where(u => !string.IsNullOrEmpty(u.UserName) && u.UserName != "Anonymous")
 81                .Select(u => u.UserName)
 82                .Distinct()
 83                .AsEnumerable());
 84        }
 85
 86        public async Task<IEnumerable<object>> GetConnectedUsersWithRoles()
 87        {
 88            // Проверяем, является ли текущий пользователь админом
 89            var isAdmin = false;
 90            if (Context.User?.Identity?.IsAuthenticated == true)
 91            {
 92                var currentUser = await _userManager.GetUserAsync(Context.User);
 93                if (currentUser != null)
 94                {
 95                    var roles = await _userManager.GetRolesAsync(currentUser);
 96                    isAdmin = roles.Contains("Admin");
 97                }
 98            }
 99            return await GetUsersWithRoles(isAdmin);
 100        }
 101
 102        private async Task<List<object>> GetUsersWithRoles(bool includeSessionInfo = false)
 103        {
 104            // Группируем по имени пользователя, берем последнюю сессию
 105            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())
 109                .ToList();
 110
 111            // Кэш только для includeSessionInfo=false (роли не меняются часто; session info меняется при UpdateCurrentP
 112            if (!includeSessionInfo && uniqueUserSessions.Count > 0)
 113            {
 114                var cacheKey = string.Join(",", uniqueUserSessions.OrderBy(u => u.UserName).Select(u => u.UserName));
 115                if (UsersWithRolesCache.TryGetValue(cacheKey, out var cached) && cached.ExpiresAt > DateTime.UtcNow)
 116                {
 117                    return cached.Result;
 118                }
 119            }
 120
 121            var usersWithRoles = new List<object>();
 122
 123            foreach (var sessionInfo in uniqueUserSessions)
 124            {
 125                try
 126                {
 127                    var user = await _userManager.FindByNameAsync(sessionInfo.UserName);
 128                    if (user != null)
 129                    {
 130                        var roles = await _userManager.GetRolesAsync(user);
 131                        var role = roles.Contains("Admin") ? "Admin" : (roles.Contains("User") ? "User" : "None");
 132
 133                        var userInfo = new {
 134                            name = sessionInfo.UserName,
 135                            role = role
 136                        };
 137
 138                        // Добавляем информацию о сеансе только для админов
 139                        if (includeSessionInfo)
 140                        {
 141                            var sessionDuration = DateTime.UtcNow - sessionInfo.ConnectedAt;
 142                            var durationString = FormatDuration(sessionDuration);
 143
 144                            // Используем dynamic для добавления свойств
 145                            var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 146                            dynamicUserInfo["name"] = sessionInfo.UserName;
 147                            dynamicUserInfo["role"] = role;
 148                            dynamicUserInfo["sessionDuration"] = durationString;
 149                            dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 150                            dynamicUserInfo["connectedAt"] = sessionInfo.ConnectedAt.ToString("yyyy-MM-dd HH:mm:ss");
 151
 152                            usersWithRoles.Add(dynamicUserInfo);
 153                        }
 154                        else
 155                        {
 156                            usersWithRoles.Add(userInfo);
 157                        }
 158                    }
 159                    else
 160                    {
 161                        if (includeSessionInfo)
 162                        {
 163                            var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 164                            dynamicUserInfo["name"] = sessionInfo.UserName;
 165                            dynamicUserInfo["role"] = "None";
 166                            dynamicUserInfo["sessionDuration"] = FormatDuration(DateTime.UtcNow - sessionInfo.ConnectedA
 167                            dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 168                            usersWithRoles.Add(dynamicUserInfo);
 169                        }
 170                        else
 171                        {
 172                            usersWithRoles.Add(new { name = sessionInfo.UserName, role = "None" });
 173                        }
 174                    }
 175                }
 176                catch
 177                {
 178                    if (includeSessionInfo)
 179                    {
 180                        var dynamicUserInfo = new System.Dynamic.ExpandoObject() as IDictionary<string, object>;
 181                        dynamicUserInfo["name"] = sessionInfo.UserName;
 182                        dynamicUserInfo["role"] = "None";
 183                        dynamicUserInfo["sessionDuration"] = FormatDuration(DateTime.UtcNow - sessionInfo.ConnectedAt);
 184                        dynamicUserInfo["currentPage"] = sessionInfo.CurrentPage;
 185                        usersWithRoles.Add(dynamicUserInfo);
 186                    }
 187                    else
 188                    {
 189                        usersWithRoles.Add(new { name = sessionInfo.UserName, role = "None" });
 190                    }
 191                }
 192            }
 193
 194            if (!includeSessionInfo && uniqueUserSessions.Count > 0)
 195            {
 196                var cacheKey = string.Join(",", uniqueUserSessions.OrderBy(u => u.UserName).Select(u => u.UserName));
 197                UsersWithRolesCache.AddOrUpdate(cacheKey,
 198                    _ => (usersWithRoles, DateTime.UtcNow + CacheTtl),
 199                    (_, _) => (usersWithRoles, DateTime.UtcNow + CacheTtl));
 200                // Удаляем устаревшие записи, чтобы не накапливать кэш
 201                var now = DateTime.UtcNow;
 202                foreach (var kvp in UsersWithRolesCache)
 203                {
 204                    if (kvp.Value.ExpiresAt < now)
 205                        UsersWithRolesCache.TryRemove(kvp.Key, out _);
 206                }
 207            }
 208
 209            return usersWithRoles;
 210        }
 211
 212        private string FormatDuration(TimeSpan duration)
 213        {
 214            if (duration.TotalDays >= 1)
 215            {
 216                return $"{(int)duration.TotalDays}д {duration.Hours}ч {duration.Minutes}м";
 217            }
 218            else if (duration.TotalHours >= 1)
 219            {
 220                return $"{duration.Hours}ч {duration.Minutes}м";
 221            }
 222            else
 223            {
 224                return $"{duration.Minutes}м {duration.Seconds}с";
 225            }
 226        }
 227    }
 228}