using System.Collections.Concurrent; using GozareshgirProgramManager.Application._Common.Constants; using GozareshgirProgramManager.Application.Interfaces; using GozareshgirProgramManager.Domain.ProjectAgg.Entities; using GozareshgirProgramManager.Domain.ProjectAgg.Enums; using Microsoft.AspNetCore.SignalR; using ServiceHost.Hubs.ProgramManager; namespace ServiceHost.Notifications.ProgramManager; public class SignalRBoardNotificationPublisher : IBoardNotificationPublisher { private readonly IHubContext _hubContext; public SignalRBoardNotificationPublisher(IHubContext hubContext) { _hubContext = hubContext; } public Task NotifyTaskStatusChanged(long userId, TaskSectionStatus oldStatus, TaskSectionStatus newStatus, Guid sectionId) { var payload = new { UserId = userId, OldStatus = oldStatus, NewStatus = newStatus, SectionId = sectionId }; // گروه task-specific (برای همه assigneeهای واقعی) var taskGroup = $"pm.task:{sectionId}"; // گروه permission-based (مثلاً برای Admin ها) var permissionGroup = $"pm.perm:{ProgramManagerPermissionCode.Board.All.ViewAll}"; // ارسال به هر دو گروه؛ SignalR خودش duplicate connection رو هندل می‌کنه return _hubContext.Clients.Groups(taskGroup, permissionGroup) .SendAsync("ReceiveProjectStatusChanged", payload); } public async Task NotifyTaskCreated(TaskSection taskSection) { var sectionId = taskSection.Id; var userId = taskSection.CurrentAssignedUserId; var taskGroup = $"pm.task:{taskSection.Id}"; var adminGroup = $"pm.perm:{ProgramManagerPermissionCode.Board.All.ViewAll}"; // Add assignee connections to task group automatically await AddAssigneeConnectionsToTaskGroup(userId, sectionId); var payload = new { TaskId = sectionId, AssignedTo = userId, }; await _hubContext.Clients.Groups(taskGroup, adminGroup) .SendAsync("TaskCreated", payload); } public Task NotifyTaskAssigned(long userId, Guid sectionId) { throw new NotImplementedException(); } public Task NotifyTaskRemoved(long userId, Guid sectionId) { throw new NotImplementedException(); } private async Task AddAssigneeConnectionsToTaskGroup(long userId, Guid taskId) { // Retrieve all connectionIds that have this claim var connections = ConnectionMapping.GetConnectionsByClaim("pm.userId", userId.ToString()); var taskGroup = $"pm.task:{taskId}"; var joinTasks = connections .Select(connId => _hubContext.Groups.AddToGroupAsync(connId, taskGroup)); await Task.WhenAll(joinTasks); } } public static class ConnectionMapping { private static readonly ConcurrentDictionary> _map = new(); public static void Add(string claimType, string claimValue, string connectionId) { var key = $"{claimType}:{claimValue}"; _map.AddOrUpdate(key, _ => new HashSet { connectionId }, (_, set) => { set.Add(connectionId); return set; }); } public static void Remove(string claimType, string claimValue, string connectionId) { var key = $"{claimType}:{claimValue}"; if (_map.TryGetValue(key, out var set)) { set.Remove(connectionId); if (!set.Any()) _map.TryRemove(key, out _); } } public static IEnumerable GetConnectionsByClaim(string claimType, string claimValue) { var key = $"{claimType}:{claimValue}"; return _map.TryGetValue(key, out var set) ? set : Enumerable.Empty(); } }