From 989f216b9a16b47fe996b3b40c80a8be2ff70b4d Mon Sep 17 00:00:00 2001 From: mahan Date: Mon, 15 Dec 2025 17:14:16 +0330 Subject: [PATCH] feat: enhance IBoardNotificationPublisher with task notification methods --- .../TaskSectionAddedHandler.cs | 13 +-- .../TaskSectionAssignedHandler.cs | 2 +- .../TaskSectionStatusChangedHandler.cs | 4 +- .../Interfaces/IBoardNotificationPublisher.cs | 14 +++- .../SignalRBoardNotificationPublisher.cs | 83 ++++++++++++++++++- 5 files changed, 103 insertions(+), 13 deletions(-) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/{ProjectSection => TaskSection}/TaskSectionAddedHandler.cs (52%) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/{ProjectSection => TaskSection}/TaskSectionAssignedHandler.cs (96%) rename ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/{ProjectSection => TaskSection}/TaskSectionStatusChangedHandler.cs (83%) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAddedHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAddedHandler.cs similarity index 52% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAddedHandler.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAddedHandler.cs index a7538ef2..cde7df00 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAddedHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAddedHandler.cs @@ -4,20 +4,23 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Events; using MediatR; using Microsoft.Extensions.Logging; -namespace GozareshgirProgramManager.Application.DomainEventHandlers.ProjectSection; +namespace GozareshgirProgramManager.Application.DomainEventHandlers.TaskSection; -public class ProjectSectionAddedHandler:INotificationHandler> +public class ProjectSectionAddedHandler:INotificationHandler> { private readonly ILogger _logger; + private readonly IBoardNotificationPublisher _boardNotificationPublisher; - public ProjectSectionAddedHandler(ILogger logger) + public ProjectSectionAddedHandler(ILogger logger, IBoardNotificationPublisher boardNotificationPublisher) { _logger = logger; + _boardNotificationPublisher = boardNotificationPublisher; } - public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) + public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken) { - + var domainEvent = notification.DomainEvent; + //_boardNotificationPublisher.NotifyTaskCreated() return Task.CompletedTask; } } \ No newline at end of file diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAssignedHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAssignedHandler.cs similarity index 96% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAssignedHandler.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAssignedHandler.cs index 3f86a177..86f5f3ef 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionAssignedHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionAssignedHandler.cs @@ -2,7 +2,7 @@ using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Domain.ProjectAgg.Events; using MediatR; -namespace GozareshgirProgramManager.Application.DomainEventHandlers.ProjectSection; +namespace GozareshgirProgramManager.Application.DomainEventHandlers.TaskSection; public class ProjectSectionAssignedHandler:INotificationHandler> { diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionStatusChangedHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionStatusChangedHandler.cs similarity index 83% rename from ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionStatusChangedHandler.cs rename to ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionStatusChangedHandler.cs index cca24761..dce3b4bc 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/ProjectSection/TaskSectionStatusChangedHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/DomainEventHandlers/TaskSection/TaskSectionStatusChangedHandler.cs @@ -3,7 +3,7 @@ using GozareshgirProgramManager.Application.Interfaces; using GozareshgirProgramManager.Domain.ProjectAgg.Events; using MediatR; -namespace GozareshgirProgramManager.Application.DomainEventHandlers.ProjectSection; +namespace GozareshgirProgramManager.Application.DomainEventHandlers.TaskSection; public class TaskSectionStatusChangedHandler:INotificationHandler> { @@ -17,7 +17,7 @@ public class TaskSectionStatusChangedHandler:INotificationHandler notification, CancellationToken cancellationToken) { var domainEvent = notification.DomainEvent; - _boardNotificationPublisher.SendProjectStatusChanged(domainEvent.UserId,domainEvent.OldStatus,domainEvent.NewStatus,domainEvent.SectionId); + _boardNotificationPublisher.NotifyTaskStatusChanged(domainEvent.UserId,domainEvent.OldStatus,domainEvent.NewStatus,domainEvent.SectionId); return Task.CompletedTask; } } \ No newline at end of file diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Interfaces/IBoardNotificationPublisher.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Interfaces/IBoardNotificationPublisher.cs index 1f4d5f1b..925740a3 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Interfaces/IBoardNotificationPublisher.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Interfaces/IBoardNotificationPublisher.cs @@ -1,9 +1,19 @@ -using GozareshgirProgramManager.Domain.ProjectAgg.Enums; +using GozareshgirProgramManager.Domain.ProjectAgg.Entities; +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; namespace GozareshgirProgramManager.Application.Interfaces; public interface IBoardNotificationPublisher { - Task SendProjectStatusChanged(long userId, TaskSectionStatus oldStatus, + Task NotifyTaskStatusChanged(long userId, TaskSectionStatus oldStatus, TaskSectionStatus newStatus, Guid sectionId); + + Task NotifyTaskCreated(TaskSection taskSection); + + Task NotifyTaskAssigned(long userId, Guid sectionId); + + Task NotifyTaskRemoved(long userId, Guid sectionId); + + + } \ No newline at end of file diff --git a/ServiceHost/Notifications/ProgramManager/SignalRBoardNotificationPublisher.cs b/ServiceHost/Notifications/ProgramManager/SignalRBoardNotificationPublisher.cs index 87a0c539..7dde09b2 100644 --- a/ServiceHost/Notifications/ProgramManager/SignalRBoardNotificationPublisher.cs +++ b/ServiceHost/Notifications/ProgramManager/SignalRBoardNotificationPublisher.cs @@ -1,12 +1,14 @@ +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 +public class SignalRBoardNotificationPublisher : IBoardNotificationPublisher { private readonly IHubContext _hubContext; @@ -15,12 +17,12 @@ public class SignalRBoardNotificationPublisher:IBoardNotificationPublisher _hubContext = hubContext; } - public Task SendProjectStatusChanged(long userId, TaskSectionStatus oldStatus, + public Task NotifyTaskStatusChanged(long userId, TaskSectionStatus oldStatus, TaskSectionStatus newStatus, Guid sectionId) { var payload = new { - UserId= userId, + UserId = userId, OldStatus = oldStatus, NewStatus = newStatus, SectionId = sectionId @@ -36,4 +38,79 @@ public class SignalRBoardNotificationPublisher:IBoardNotificationPublisher 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(); + } } \ No newline at end of file