Extends SignalR notification system to cover all ProjectManagement CRUD operations:
Domain Events Created:
- EpicUpdatedEvent, EpicDeletedEvent
- StoryCreatedEvent, StoryUpdatedEvent, StoryDeletedEvent
- TaskCreatedEvent, TaskUpdatedEvent, TaskDeletedEvent, TaskAssignedEvent
Event Handlers Added (10 handlers):
- EpicCreatedEventHandler, EpicUpdatedEventHandler, EpicDeletedEventHandler
- StoryCreatedEventHandler, StoryUpdatedEventHandler, StoryDeletedEventHandler
- TaskCreatedEventHandler, TaskUpdatedEventHandler, TaskDeletedEventHandler
- TaskAssignedEventHandler
Infrastructure Extensions:
- Extended IProjectNotificationService with Epic/Story/Task methods
- Extended IRealtimeNotificationService with Epic/Story/Task methods
- Extended RealtimeNotificationService with implementations
- Extended ProjectNotificationServiceAdapter for delegation
Domain Changes:
- Updated EpicCreatedEvent to include TenantId (consistency with other events)
- Added Epic/Story/Task CRUD methods to Project aggregate root
- All operations raise appropriate domain events
Broadcasting Strategy:
- Created events: Broadcast to both project-{projectId} and tenant-{tenantId} groups
- Updated events: Broadcast to project-{projectId} group only
- Deleted events: Broadcast to project-{projectId} group only
- Assigned events: Broadcast to project-{projectId} group with assignment details
Test Results:
- All 192 domain tests passing
- Domain and Application layers compile successfully
- Event handlers auto-registered by MediatR
Files Changed:
- 9 new domain event files
- 10 new event handler files
- 3 service interfaces extended
- 2 service implementations extended
- 1 aggregate updated with event raising logic
- 1 test file updated for new event signature
Status: Complete real-time collaboration for ProjectManagement module
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
229 lines
8.5 KiB
C#
229 lines
8.5 KiB
C#
using Microsoft.AspNetCore.SignalR;
|
|
using ColaFlow.API.Hubs;
|
|
|
|
namespace ColaFlow.API.Services;
|
|
|
|
public class RealtimeNotificationService : IRealtimeNotificationService
|
|
{
|
|
private readonly IHubContext<ProjectHub> _projectHubContext;
|
|
private readonly IHubContext<NotificationHub> _notificationHubContext;
|
|
private readonly ILogger<RealtimeNotificationService> _logger;
|
|
|
|
public RealtimeNotificationService(
|
|
IHubContext<ProjectHub> projectHubContext,
|
|
IHubContext<NotificationHub> notificationHubContext,
|
|
ILogger<RealtimeNotificationService> logger)
|
|
{
|
|
_projectHubContext = projectHubContext;
|
|
_notificationHubContext = notificationHubContext;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task NotifyProjectCreated(Guid tenantId, Guid projectId, object project)
|
|
{
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying tenant {TenantId} of new project {ProjectId}", tenantId, projectId);
|
|
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("ProjectCreated", project);
|
|
}
|
|
|
|
public async Task NotifyProjectUpdated(Guid tenantId, Guid projectId, object project)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying project {ProjectId} updated", projectId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("ProjectUpdated", project);
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("ProjectUpdated", project);
|
|
}
|
|
|
|
public async Task NotifyProjectArchived(Guid tenantId, Guid projectId)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying project {ProjectId} archived", projectId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("ProjectArchived", new { ProjectId = projectId });
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("ProjectArchived", new { ProjectId = projectId });
|
|
}
|
|
|
|
public async Task NotifyProjectUpdate(Guid tenantId, Guid projectId, object data)
|
|
{
|
|
var groupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Sending project update to group {GroupName}", groupName);
|
|
|
|
await _projectHubContext.Clients.Group(groupName).SendAsync("ProjectUpdated", data);
|
|
}
|
|
|
|
// Epic notifications
|
|
public async Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying epic {EpicId} created in project {ProjectId}", epicId, projectId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicCreated", epic);
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("EpicCreated", epic);
|
|
}
|
|
|
|
public async Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying epic {EpicId} updated", epicId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicUpdated", epic);
|
|
}
|
|
|
|
public async Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying epic {EpicId} deleted", epicId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicDeleted", new { EpicId = epicId });
|
|
}
|
|
|
|
// Story notifications
|
|
public async Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying story {StoryId} created in epic {EpicId}", storyId, epicId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryCreated", story);
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("StoryCreated", story);
|
|
}
|
|
|
|
public async Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying story {StoryId} updated", storyId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryUpdated", story);
|
|
}
|
|
|
|
public async Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying story {StoryId} deleted", storyId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryDeleted", new { StoryId = storyId });
|
|
}
|
|
|
|
// Task notifications
|
|
public async Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
var tenantGroupName = $"tenant-{tenantId}";
|
|
|
|
_logger.LogInformation("Notifying task {TaskId} created in story {StoryId}", taskId, storyId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskCreated", task);
|
|
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("TaskCreated", task);
|
|
}
|
|
|
|
public async Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying task {TaskId} updated", taskId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskUpdated", task);
|
|
}
|
|
|
|
public async Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying task {TaskId} deleted", taskId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskDeleted", new { TaskId = taskId });
|
|
}
|
|
|
|
public async Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId)
|
|
{
|
|
var projectGroupName = $"project-{projectId}";
|
|
|
|
_logger.LogInformation("Notifying task {TaskId} assigned to {AssigneeId}", taskId, assigneeId);
|
|
|
|
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskAssigned", new
|
|
{
|
|
TaskId = taskId,
|
|
AssigneeId = assigneeId,
|
|
AssignedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
public async Task NotifyIssueCreated(Guid tenantId, Guid projectId, object issue)
|
|
{
|
|
var groupName = $"project-{projectId}";
|
|
|
|
await _projectHubContext.Clients.Group(groupName).SendAsync("IssueCreated", issue);
|
|
}
|
|
|
|
public async Task NotifyIssueUpdated(Guid tenantId, Guid projectId, object issue)
|
|
{
|
|
var groupName = $"project-{projectId}";
|
|
|
|
await _projectHubContext.Clients.Group(groupName).SendAsync("IssueUpdated", issue);
|
|
}
|
|
|
|
public async Task NotifyIssueDeleted(Guid tenantId, Guid projectId, Guid issueId)
|
|
{
|
|
var groupName = $"project-{projectId}";
|
|
|
|
await _projectHubContext.Clients.Group(groupName).SendAsync("IssueDeleted", new { IssueId = issueId });
|
|
}
|
|
|
|
public async Task NotifyIssueStatusChanged(
|
|
Guid tenantId,
|
|
Guid projectId,
|
|
Guid issueId,
|
|
string oldStatus,
|
|
string newStatus)
|
|
{
|
|
var groupName = $"project-{projectId}";
|
|
|
|
await _projectHubContext.Clients.Group(groupName).SendAsync("IssueStatusChanged", new
|
|
{
|
|
IssueId = issueId,
|
|
OldStatus = oldStatus,
|
|
NewStatus = newStatus,
|
|
ChangedAt = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
public async Task NotifyUser(Guid userId, string message, string type = "info")
|
|
{
|
|
var userConnectionId = $"user-{userId}";
|
|
|
|
await _notificationHubContext.Clients.User(userId.ToString()).SendAsync("Notification", new
|
|
{
|
|
Message = message,
|
|
Type = type,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
public async Task NotifyUsersInTenant(Guid tenantId, string message, string type = "info")
|
|
{
|
|
var groupName = $"tenant-{tenantId}";
|
|
|
|
await _notificationHubContext.Clients.Group(groupName).SendAsync("Notification", new
|
|
{
|
|
Message = message,
|
|
Type = type,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|