From b53521775c7fd48a36ffc532fc2b1dc2bd041c4e Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Tue, 4 Nov 2025 20:56:08 +0100 Subject: [PATCH] feat(signalr): Add real-time notifications for Epic/Story/Task operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Services/IRealtimeNotificationService.cs | 16 ++ .../ProjectNotificationServiceAdapter.cs | 54 +++++++ .../Services/RealtimeNotificationService.cs | 104 +++++++++++++ .../EventHandlers/EpicCreatedEventHandler.cs | 44 ++++++ .../EventHandlers/EpicDeletedEventHandler.cs | 35 +++++ .../EventHandlers/EpicUpdatedEventHandler.cs | 44 ++++++ .../EventHandlers/StoryCreatedEventHandler.cs | 46 ++++++ .../EventHandlers/StoryDeletedEventHandler.cs | 36 +++++ .../EventHandlers/StoryUpdatedEventHandler.cs | 46 ++++++ .../EventHandlers/TaskAssignedEventHandler.cs | 37 +++++ .../EventHandlers/TaskCreatedEventHandler.cs | 46 ++++++ .../EventHandlers/TaskDeletedEventHandler.cs | 36 +++++ .../EventHandlers/TaskUpdatedEventHandler.cs | 46 ++++++ .../Services/IProjectNotificationService.cs | 17 ++ .../Aggregates/ProjectAggregate/Project.cs | 145 +++++++++++++++++- .../Events/EpicCreatedEvent.cs | 5 +- .../Events/EpicDeletedEvent.cs | 13 ++ .../Events/EpicUpdatedEvent.cs | 14 ++ .../Events/StoryCreatedEvent.cs | 15 ++ .../Events/StoryDeletedEvent.cs | 14 ++ .../Events/StoryUpdatedEvent.cs | 15 ++ .../Events/TaskAssignedEvent.cs | 15 ++ .../Events/TaskCreatedEvent.cs | 15 ++ .../Events/TaskDeletedEvent.cs | 14 ++ .../Events/TaskUpdatedEvent.cs | 15 ++ .../Events/DomainEventsTests.cs | 20 ++- 26 files changed, 896 insertions(+), 11 deletions(-) create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicCreatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicDeletedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicUpdatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryCreatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryDeletedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryUpdatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskAssignedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskCreatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskDeletedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskUpdatedEventHandler.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicDeletedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicUpdatedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryCreatedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryDeletedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryUpdatedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskAssignedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskCreatedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskDeletedEvent.cs create mode 100644 colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskUpdatedEvent.cs diff --git a/colaflow-api/src/ColaFlow.API/Services/IRealtimeNotificationService.cs b/colaflow-api/src/ColaFlow.API/Services/IRealtimeNotificationService.cs index d2b1011..c016422 100644 --- a/colaflow-api/src/ColaFlow.API/Services/IRealtimeNotificationService.cs +++ b/colaflow-api/src/ColaFlow.API/Services/IRealtimeNotificationService.cs @@ -8,6 +8,22 @@ public interface IRealtimeNotificationService Task NotifyProjectArchived(Guid tenantId, Guid projectId); Task NotifyProjectUpdate(Guid tenantId, Guid projectId, object data); + // Epic notifications + Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic); + Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic); + Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId); + + // Story notifications + Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story); + Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story); + Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId); + + // Task notifications + Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task); + Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task); + Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId); + Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId); + // Issue notifications Task NotifyIssueCreated(Guid tenantId, Guid projectId, object issue); Task NotifyIssueUpdated(Guid tenantId, Guid projectId, object issue); diff --git a/colaflow-api/src/ColaFlow.API/Services/ProjectNotificationServiceAdapter.cs b/colaflow-api/src/ColaFlow.API/Services/ProjectNotificationServiceAdapter.cs index 0ea74bf..fa8b92b 100644 --- a/colaflow-api/src/ColaFlow.API/Services/ProjectNotificationServiceAdapter.cs +++ b/colaflow-api/src/ColaFlow.API/Services/ProjectNotificationServiceAdapter.cs @@ -15,6 +15,7 @@ public class ProjectNotificationServiceAdapter : IProjectNotificationService _realtimeService = realtimeService; } + // Project notifications public Task NotifyProjectCreated(Guid tenantId, Guid projectId, object project) { return _realtimeService.NotifyProjectCreated(tenantId, projectId, project); @@ -29,4 +30,57 @@ public class ProjectNotificationServiceAdapter : IProjectNotificationService { return _realtimeService.NotifyProjectArchived(tenantId, projectId); } + + // Epic notifications + public Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic) + { + return _realtimeService.NotifyEpicCreated(tenantId, projectId, epicId, epic); + } + + public Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic) + { + return _realtimeService.NotifyEpicUpdated(tenantId, projectId, epicId, epic); + } + + public Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId) + { + return _realtimeService.NotifyEpicDeleted(tenantId, projectId, epicId); + } + + // Story notifications + public Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story) + { + return _realtimeService.NotifyStoryCreated(tenantId, projectId, epicId, storyId, story); + } + + public Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story) + { + return _realtimeService.NotifyStoryUpdated(tenantId, projectId, epicId, storyId, story); + } + + public Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId) + { + return _realtimeService.NotifyStoryDeleted(tenantId, projectId, epicId, storyId); + } + + // Task notifications + public Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task) + { + return _realtimeService.NotifyTaskCreated(tenantId, projectId, storyId, taskId, task); + } + + public Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task) + { + return _realtimeService.NotifyTaskUpdated(tenantId, projectId, storyId, taskId, task); + } + + public Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId) + { + return _realtimeService.NotifyTaskDeleted(tenantId, projectId, storyId, taskId); + } + + public Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId) + { + return _realtimeService.NotifyTaskAssigned(tenantId, projectId, taskId, assigneeId); + } } diff --git a/colaflow-api/src/ColaFlow.API/Services/RealtimeNotificationService.cs b/colaflow-api/src/ColaFlow.API/Services/RealtimeNotificationService.cs index e72dbaf..9a72106 100644 --- a/colaflow-api/src/ColaFlow.API/Services/RealtimeNotificationService.cs +++ b/colaflow-api/src/ColaFlow.API/Services/RealtimeNotificationService.cs @@ -59,6 +59,110 @@ public class RealtimeNotificationService : IRealtimeNotificationService 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}"; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicCreatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicCreatedEventHandler.cs new file mode 100644 index 0000000..d75041a --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicCreatedEventHandler.cs @@ -0,0 +1,44 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for EpicCreatedEvent - sends SignalR notification +/// +public class EpicCreatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public EpicCreatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(EpicCreatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling EpicCreatedEvent for epic {EpicId}", notification.EpicId); + + var epicData = new + { + Id = notification.EpicId.Value, + ProjectId = notification.ProjectId.Value, + Name = notification.EpicName, + CreatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyEpicCreated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value, + epicData); + + _logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicDeletedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicDeletedEventHandler.cs new file mode 100644 index 0000000..9dfb0ff --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicDeletedEventHandler.cs @@ -0,0 +1,35 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for EpicDeletedEvent - sends SignalR notification +/// +public class EpicDeletedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public EpicDeletedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(EpicDeletedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling EpicDeletedEvent for epic {EpicId}", notification.EpicId); + + await _notificationService.NotifyEpicDeleted( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value); + + _logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicUpdatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicUpdatedEventHandler.cs new file mode 100644 index 0000000..6ea94d4 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/EpicUpdatedEventHandler.cs @@ -0,0 +1,44 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for EpicUpdatedEvent - sends SignalR notification +/// +public class EpicUpdatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public EpicUpdatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(EpicUpdatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling EpicUpdatedEvent for epic {EpicId}", notification.EpicId); + + var epicData = new + { + Id = notification.EpicId.Value, + ProjectId = notification.ProjectId.Value, + Name = notification.EpicName, + UpdatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyEpicUpdated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value, + epicData); + + _logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryCreatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryCreatedEventHandler.cs new file mode 100644 index 0000000..8755643 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryCreatedEventHandler.cs @@ -0,0 +1,46 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for StoryCreatedEvent - sends SignalR notification +/// +public class StoryCreatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public StoryCreatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(StoryCreatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling StoryCreatedEvent for story {StoryId}", notification.StoryId); + + var storyData = new + { + Id = notification.StoryId.Value, + ProjectId = notification.ProjectId.Value, + EpicId = notification.EpicId.Value, + Title = notification.StoryTitle, + CreatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyStoryCreated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value, + notification.StoryId.Value, + storyData); + + _logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryDeletedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryDeletedEventHandler.cs new file mode 100644 index 0000000..8130b3d --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryDeletedEventHandler.cs @@ -0,0 +1,36 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for StoryDeletedEvent - sends SignalR notification +/// +public class StoryDeletedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public StoryDeletedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(StoryDeletedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling StoryDeletedEvent for story {StoryId}", notification.StoryId); + + await _notificationService.NotifyStoryDeleted( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value, + notification.StoryId.Value); + + _logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryUpdatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryUpdatedEventHandler.cs new file mode 100644 index 0000000..f2317c6 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/StoryUpdatedEventHandler.cs @@ -0,0 +1,46 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for StoryUpdatedEvent - sends SignalR notification +/// +public class StoryUpdatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public StoryUpdatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(StoryUpdatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling StoryUpdatedEvent for story {StoryId}", notification.StoryId); + + var storyData = new + { + Id = notification.StoryId.Value, + ProjectId = notification.ProjectId.Value, + EpicId = notification.EpicId.Value, + Title = notification.StoryTitle, + UpdatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyStoryUpdated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.EpicId.Value, + notification.StoryId.Value, + storyData); + + _logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskAssignedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskAssignedEventHandler.cs new file mode 100644 index 0000000..f670175 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskAssignedEventHandler.cs @@ -0,0 +1,37 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for TaskAssignedEvent - sends SignalR notification +/// +public class TaskAssignedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public TaskAssignedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(TaskAssignedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling TaskAssignedEvent for task {TaskId}", notification.TaskId); + + await _notificationService.NotifyTaskAssigned( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.TaskId.Value, + notification.AssigneeId.Value); + + _logger.LogInformation("SignalR notification sent for task {TaskId} assigned to {AssigneeId}", + notification.TaskId, notification.AssigneeId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskCreatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskCreatedEventHandler.cs new file mode 100644 index 0000000..4694bee --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskCreatedEventHandler.cs @@ -0,0 +1,46 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for TaskCreatedEvent - sends SignalR notification +/// +public class TaskCreatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public TaskCreatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(TaskCreatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling TaskCreatedEvent for task {TaskId}", notification.TaskId); + + var taskData = new + { + Id = notification.TaskId.Value, + ProjectId = notification.ProjectId.Value, + StoryId = notification.StoryId.Value, + Title = notification.TaskTitle, + CreatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyTaskCreated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.StoryId.Value, + notification.TaskId.Value, + taskData); + + _logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskDeletedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskDeletedEventHandler.cs new file mode 100644 index 0000000..0a4be8d --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskDeletedEventHandler.cs @@ -0,0 +1,36 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for TaskDeletedEvent - sends SignalR notification +/// +public class TaskDeletedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public TaskDeletedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(TaskDeletedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling TaskDeletedEvent for task {TaskId}", notification.TaskId); + + await _notificationService.NotifyTaskDeleted( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.StoryId.Value, + notification.TaskId.Value); + + _logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskUpdatedEventHandler.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskUpdatedEventHandler.cs new file mode 100644 index 0000000..4b05cec --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/EventHandlers/TaskUpdatedEventHandler.cs @@ -0,0 +1,46 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using ColaFlow.Modules.ProjectManagement.Domain.Events; +using ColaFlow.Modules.ProjectManagement.Application.Services; + +namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers; + +/// +/// Handler for TaskUpdatedEvent - sends SignalR notification +/// +public class TaskUpdatedEventHandler : INotificationHandler +{ + private readonly IProjectNotificationService _notificationService; + private readonly ILogger _logger; + + public TaskUpdatedEventHandler( + IProjectNotificationService notificationService, + ILogger logger) + { + _notificationService = notificationService; + _logger = logger; + } + + public async Task Handle(TaskUpdatedEvent notification, CancellationToken cancellationToken) + { + _logger.LogInformation("Handling TaskUpdatedEvent for task {TaskId}", notification.TaskId); + + var taskData = new + { + Id = notification.TaskId.Value, + ProjectId = notification.ProjectId.Value, + StoryId = notification.StoryId.Value, + Title = notification.TaskTitle, + UpdatedAt = DateTime.UtcNow + }; + + await _notificationService.NotifyTaskUpdated( + notification.TenantId.Value, + notification.ProjectId.Value, + notification.StoryId.Value, + notification.TaskId.Value, + taskData); + + _logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId); + } +} diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectNotificationService.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectNotificationService.cs index 78cb719..c3fe685 100644 --- a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectNotificationService.cs +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Application/Services/IProjectNotificationService.cs @@ -5,7 +5,24 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Services; /// public interface IProjectNotificationService { + // Project notifications Task NotifyProjectCreated(Guid tenantId, Guid projectId, object project); Task NotifyProjectUpdated(Guid tenantId, Guid projectId, object project); Task NotifyProjectArchived(Guid tenantId, Guid projectId); + + // Epic notifications + Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic); + Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic); + Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId); + + // Story notifications + Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story); + Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story); + Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId); + + // Task notifications + Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task); + Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task); + Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId); + Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId); } diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Aggregates/ProjectAggregate/Project.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Aggregates/ProjectAggregate/Project.cs index 98cfebe..fd3de75 100644 --- a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Aggregates/ProjectAggregate/Project.cs +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Aggregates/ProjectAggregate/Project.cs @@ -90,11 +90,154 @@ public class Project : AggregateRoot var epic = Epic.Create(this.TenantId, name, description, this.Id, createdBy); _epics.Add(epic); - AddDomainEvent(new EpicCreatedEvent(epic.Id, epic.Name, this.Id)); + AddDomainEvent(new EpicCreatedEvent(epic.Id, this.TenantId, this.Id, epic.Name)); return epic; } + public void UpdateEpic(EpicId epicId, string name, string description) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + epic.UpdateDetails(name, description); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new EpicUpdatedEvent(epic.Id, this.TenantId, this.Id, epic.Name)); + } + + public void DeleteEpic(EpicId epicId) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + if (epic.Stories.Any()) + throw new DomainException($"Cannot delete epic with ID {epicId.Value}. The epic has {epic.Stories.Count} associated story/stories. Please delete or reassign the stories first."); + + _epics.Remove(epic); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new EpicDeletedEvent(epicId, this.TenantId, this.Id)); + } + + public Story CreateStory(EpicId epicId, string title, string description, TaskPriority priority, UserId createdBy) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.CreateStory(title, description, priority, createdBy); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new StoryCreatedEvent(story.Id, this.TenantId, this.Id, epicId, story.Title)); + + return story; + } + + public void UpdateStory(EpicId epicId, StoryId storyId, string title, string description) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.Stories.FirstOrDefault(s => s.Id == storyId); + if (story == null) + throw new DomainException($"Story with ID {storyId.Value} not found in epic"); + + story.UpdateDetails(title, description); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new StoryUpdatedEvent(storyId, this.TenantId, this.Id, epicId, story.Title)); + } + + public void DeleteStory(EpicId epicId, StoryId storyId) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + epic.RemoveStory(storyId); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new StoryDeletedEvent(storyId, this.TenantId, this.Id, epicId)); + } + + public WorkTask CreateTask(EpicId epicId, StoryId storyId, string title, string description, TaskPriority priority, UserId createdBy) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.Stories.FirstOrDefault(s => s.Id == storyId); + if (story == null) + throw new DomainException($"Story with ID {storyId.Value} not found in epic"); + + var task = story.CreateTask(title, description, priority, createdBy); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new TaskCreatedEvent(task.Id, this.TenantId, this.Id, storyId, task.Title)); + + return task; + } + + public void UpdateTask(EpicId epicId, StoryId storyId, TaskId taskId, string title, string description) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.Stories.FirstOrDefault(s => s.Id == storyId); + if (story == null) + throw new DomainException($"Story with ID {storyId.Value} not found in epic"); + + var task = story.Tasks.FirstOrDefault(t => t.Id == taskId); + if (task == null) + throw new DomainException($"Task with ID {taskId.Value} not found in story"); + + task.UpdateDetails(title, description); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new TaskUpdatedEvent(taskId, this.TenantId, this.Id, storyId, task.Title)); + } + + public void DeleteTask(EpicId epicId, StoryId storyId, TaskId taskId) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.Stories.FirstOrDefault(s => s.Id == storyId); + if (story == null) + throw new DomainException($"Story with ID {storyId.Value} not found in epic"); + + story.RemoveTask(taskId); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new TaskDeletedEvent(taskId, this.TenantId, this.Id, storyId)); + } + + public void AssignTask(EpicId epicId, StoryId storyId, TaskId taskId, UserId assigneeId) + { + var epic = _epics.FirstOrDefault(e => e.Id == epicId); + if (epic == null) + throw new DomainException($"Epic with ID {epicId.Value} not found"); + + var story = epic.Stories.FirstOrDefault(s => s.Id == storyId); + if (story == null) + throw new DomainException($"Story with ID {storyId.Value} not found in epic"); + + var task = story.Tasks.FirstOrDefault(t => t.Id == taskId); + if (task == null) + throw new DomainException($"Task with ID {taskId.Value} not found in story"); + + task.AssignTo(assigneeId); + UpdatedAt = DateTime.UtcNow; + + AddDomainEvent(new TaskAssignedEvent(taskId, this.TenantId, this.Id, storyId, assigneeId)); + } + public void Archive() { if (Status == ProjectStatus.Archived) diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicCreatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicCreatedEvent.cs index 21bc67a..e7068f2 100644 --- a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicCreatedEvent.cs +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicCreatedEvent.cs @@ -8,6 +8,7 @@ namespace ColaFlow.Modules.ProjectManagement.Domain.Events; /// public sealed record EpicCreatedEvent( EpicId EpicId, - string EpicName, - ProjectId ProjectId + TenantId TenantId, + ProjectId ProjectId, + string EpicName ) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicDeletedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicDeletedEvent.cs new file mode 100644 index 0000000..7c4edeb --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicDeletedEvent.cs @@ -0,0 +1,13 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when an epic is deleted +/// +public sealed record EpicDeletedEvent( + EpicId EpicId, + TenantId TenantId, + ProjectId ProjectId +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicUpdatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicUpdatedEvent.cs new file mode 100644 index 0000000..a79e3aa --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/EpicUpdatedEvent.cs @@ -0,0 +1,14 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when an epic is updated +/// +public sealed record EpicUpdatedEvent( + EpicId EpicId, + TenantId TenantId, + ProjectId ProjectId, + string EpicName +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryCreatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryCreatedEvent.cs new file mode 100644 index 0000000..261f607 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryCreatedEvent.cs @@ -0,0 +1,15 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a story is created +/// +public sealed record StoryCreatedEvent( + StoryId StoryId, + TenantId TenantId, + ProjectId ProjectId, + EpicId EpicId, + string StoryTitle +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryDeletedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryDeletedEvent.cs new file mode 100644 index 0000000..6378241 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryDeletedEvent.cs @@ -0,0 +1,14 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a story is deleted +/// +public sealed record StoryDeletedEvent( + StoryId StoryId, + TenantId TenantId, + ProjectId ProjectId, + EpicId EpicId +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryUpdatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryUpdatedEvent.cs new file mode 100644 index 0000000..1554bba --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/StoryUpdatedEvent.cs @@ -0,0 +1,15 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a story is updated +/// +public sealed record StoryUpdatedEvent( + StoryId StoryId, + TenantId TenantId, + ProjectId ProjectId, + EpicId EpicId, + string StoryTitle +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskAssignedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskAssignedEvent.cs new file mode 100644 index 0000000..7c166a6 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskAssignedEvent.cs @@ -0,0 +1,15 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a task is assigned to a user +/// +public sealed record TaskAssignedEvent( + TaskId TaskId, + TenantId TenantId, + ProjectId ProjectId, + StoryId StoryId, + UserId AssigneeId +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskCreatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskCreatedEvent.cs new file mode 100644 index 0000000..9ba4eb7 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskCreatedEvent.cs @@ -0,0 +1,15 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a task is created +/// +public sealed record TaskCreatedEvent( + TaskId TaskId, + TenantId TenantId, + ProjectId ProjectId, + StoryId StoryId, + string TaskTitle +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskDeletedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskDeletedEvent.cs new file mode 100644 index 0000000..375477a --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskDeletedEvent.cs @@ -0,0 +1,14 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a task is deleted +/// +public sealed record TaskDeletedEvent( + TaskId TaskId, + TenantId TenantId, + ProjectId ProjectId, + StoryId StoryId +) : DomainEvent; diff --git a/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskUpdatedEvent.cs b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskUpdatedEvent.cs new file mode 100644 index 0000000..cf54be0 --- /dev/null +++ b/colaflow-api/src/Modules/ProjectManagement/ColaFlow.Modules.ProjectManagement.Domain/Events/TaskUpdatedEvent.cs @@ -0,0 +1,15 @@ +using ColaFlow.Shared.Kernel.Events; +using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects; + +namespace ColaFlow.Modules.ProjectManagement.Domain.Events; + +/// +/// Event raised when a task is updated +/// +public sealed record TaskUpdatedEvent( + TaskId TaskId, + TenantId TenantId, + ProjectId ProjectId, + StoryId StoryId, + string TaskTitle +) : DomainEvent; diff --git a/colaflow-api/tests/ColaFlow.Domain.Tests/Events/DomainEventsTests.cs b/colaflow-api/tests/ColaFlow.Domain.Tests/Events/DomainEventsTests.cs index 3111930..d23cf86 100644 --- a/colaflow-api/tests/ColaFlow.Domain.Tests/Events/DomainEventsTests.cs +++ b/colaflow-api/tests/ColaFlow.Domain.Tests/Events/DomainEventsTests.cs @@ -125,16 +125,18 @@ public class DomainEventsTests { // Arrange var epicId = EpicId.Create(); - var epicName = "Epic 1"; + var tenantId = TenantId.Create(Guid.NewGuid()); var projectId = ProjectId.Create(); + var epicName = "Epic 1"; // Act - var @event = new EpicCreatedEvent(epicId, epicName, projectId); + var @event = new EpicCreatedEvent(epicId, tenantId, projectId, epicName); // Assert @event.EpicId.Should().Be(epicId); - @event.EpicName.Should().Be(epicName); + @event.TenantId.Should().Be(tenantId); @event.ProjectId.Should().Be(projectId); + @event.EpicName.Should().Be(epicName); @event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5)); } @@ -143,17 +145,19 @@ public class DomainEventsTests { // Arrange var epicId = EpicId.Create(); - var epicName = "Epic 1"; + var tenantId = TenantId.Create(Guid.NewGuid()); var projectId = ProjectId.Create(); + var epicName = "Epic 1"; // Act - var event1 = new EpicCreatedEvent(epicId, epicName, projectId); - var event2 = new EpicCreatedEvent(epicId, epicName, projectId); + var event1 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName); + var event2 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName); // Assert - Records with same values should be equal event1.EpicId.Should().Be(event2.EpicId); - event1.EpicName.Should().Be(event2.EpicName); + event1.TenantId.Should().Be(event2.TenantId); event1.ProjectId.Should().Be(event2.ProjectId); + event1.EpicName.Should().Be(event2.EpicName); } #endregion @@ -167,7 +171,7 @@ public class DomainEventsTests var projectCreatedEvent = new ProjectCreatedEvent(ProjectId.Create(), TenantId.Create(Guid.NewGuid()), "Test", UserId.Create()); var projectUpdatedEvent = new ProjectUpdatedEvent(ProjectId.Create(), "Test", "Desc"); var projectArchivedEvent = new ProjectArchivedEvent(ProjectId.Create()); - var epicCreatedEvent = new EpicCreatedEvent(EpicId.Create(), "Epic", ProjectId.Create()); + var epicCreatedEvent = new EpicCreatedEvent(EpicId.Create(), TenantId.Create(Guid.NewGuid()), ProjectId.Create(), "Epic"); // Assert projectCreatedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);