feat(backend): Create Sprint 2 backend Stories and Tasks

Created detailed implementation plans for Sprint 2 backend work:

Story 1: Audit Log Foundation (Phase 1)
- Task 1: Design AuditLog database schema and create migration
- Task 2: Create AuditLog entity and Repository
- Task 3: Implement EF Core SaveChangesInterceptor
- Task 4: Write unit tests for audit logging
- Task 5: Integrate with ProjectManagement Module

Story 2: Audit Log Core Features (Phase 2)
- Task 1: Implement Changed Fields Detection (JSON Diff)
- Task 2: Integrate User Context Tracking
- Task 3: Add Multi-Tenant Isolation
- Task 4: Implement Audit Query API
- Task 5: Write Integration Tests

Story 3: Sprint Management Module
- Task 1: Create Sprint Aggregate Root and Domain Events
- Task 2: Implement Sprint Repository and EF Core Configuration
- Task 3: Create CQRS Commands and Queries
- Task 4: Implement Burndown Chart Calculation
- Task 5: Add SignalR Real-Time Notifications
- Task 6: Write Integration Tests

Total: 3 Stories, 16 Tasks, 24 Story Points (8+8+8)
Estimated Duration: 10-12 days

All tasks include:
- Detailed technical implementation guidance
- Code examples and file paths
- Testing requirements (>= 90% coverage)
- Performance benchmarks (< 5ms audit overhead)
- Multi-tenant security validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2025-11-04 22:56:31 +01:00
parent d6cf86a4da
commit ebb56cc9f8
19 changed files with 4030 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
---
task_id: sprint_2_story_3_task_5
story: sprint_2_story_3
status: not_started
estimated_hours: 3
created_date: 2025-11-05
assignee: Backend Team
---
# Task 5: Add SignalR Real-Time Notifications
**Story**: Story 3 - Sprint Management Module
**Estimated**: 3 hours
## Description
Integrate SignalR to broadcast real-time notifications for Sprint events (Created, Updated, Started, Completed, Deleted) to connected clients.
## Acceptance Criteria
- [ ] Domain event handlers created for 5 Sprint events
- [ ] IRealtimeNotificationService extended with Sprint methods
- [ ] SignalR notifications sent to project groups
- [ ] Integration tests verify notifications are sent
- [ ] Frontend can receive Sprint notifications
## Implementation Details
**Files to Create**:
1. **Extend Notification Service**: `colaflow-api/src/ColaFlow.Application/Common/Interfaces/IRealtimeNotificationService.cs`
```csharp
public interface IRealtimeNotificationService
{
// ... existing methods ...
// Sprint notifications
Task NotifySprintCreatedAsync(Guid sprintId, string sprintName, Guid projectId);
Task NotifySprintUpdatedAsync(Guid sprintId, string sprintName, Guid projectId);
Task NotifySprintStartedAsync(Guid sprintId, string sprintName, Guid projectId);
Task NotifySprintCompletedAsync(Guid sprintId, string sprintName, Guid projectId);
Task NotifySprintDeletedAsync(Guid sprintId, string sprintName, Guid projectId);
}
```
2. **Implement Service**: `colaflow-api/src/ColaFlow.Infrastructure/SignalR/RealtimeNotificationService.cs`
```csharp
public class RealtimeNotificationService : IRealtimeNotificationService
{
private readonly IHubContext<ProjectHub> _projectHubContext;
// ... existing code ...
public async Task NotifySprintCreatedAsync(Guid sprintId, string sprintName, Guid projectId)
{
await _projectHubContext.Clients
.Group($"project-{projectId}")
.SendAsync("SprintCreated", new
{
SprintId = sprintId,
SprintName = sprintName,
ProjectId = projectId,
Timestamp = DateTime.UtcNow
});
}
public async Task NotifySprintUpdatedAsync(Guid sprintId, string sprintName, Guid projectId)
{
await _projectHubContext.Clients
.Group($"project-{projectId}")
.SendAsync("SprintUpdated", new
{
SprintId = sprintId,
SprintName = sprintName,
ProjectId = projectId,
Timestamp = DateTime.UtcNow
});
}
public async Task NotifySprintStartedAsync(Guid sprintId, string sprintName, Guid projectId)
{
await _projectHubContext.Clients
.Group($"project-{projectId}")
.SendAsync("SprintStarted", new
{
SprintId = sprintId,
SprintName = sprintName,
ProjectId = projectId,
Timestamp = DateTime.UtcNow
});
}
public async Task NotifySprintCompletedAsync(Guid sprintId, string sprintName, Guid projectId)
{
await _projectHubContext.Clients
.Group($"project-{projectId}")
.SendAsync("SprintCompleted", new
{
SprintId = sprintId,
SprintName = sprintName,
ProjectId = projectId,
Timestamp = DateTime.UtcNow
});
}
public async Task NotifySprintDeletedAsync(Guid sprintId, string sprintName, Guid projectId)
{
await _projectHubContext.Clients
.Group($"project-{projectId}")
.SendAsync("SprintDeleted", new
{
SprintId = sprintId,
SprintName = sprintName,
ProjectId = projectId,
Timestamp = DateTime.UtcNow
});
}
}
```
3. **Domain Event Handlers**: `colaflow-api/src/ColaFlow.Application/Sprints/EventHandlers/`
```csharp
// SprintCreatedEventHandler.cs
public class SprintCreatedEventHandler : INotificationHandler<SprintCreatedEvent>
{
private readonly IRealtimeNotificationService _notificationService;
private readonly ILogger<SprintCreatedEventHandler> _logger;
public SprintCreatedEventHandler(
IRealtimeNotificationService notificationService,
ILogger<SprintCreatedEventHandler> logger)
{
_notificationService = notificationService;
_logger = logger;
}
public async Task Handle(SprintCreatedEvent notification, CancellationToken cancellationToken)
{
try
{
await _notificationService.NotifySprintCreatedAsync(
notification.SprintId,
notification.SprintName,
notification.ProjectId
);
_logger.LogInformation(
"Real-time notification sent for Sprint created: {SprintId}",
notification.SprintId
);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to send real-time notification for Sprint created: {SprintId}",
notification.SprintId
);
}
}
}
// SprintUpdatedEventHandler.cs
public class SprintUpdatedEventHandler : INotificationHandler<SprintUpdatedEvent>
{
private readonly IRealtimeNotificationService _notificationService;
public async Task Handle(SprintUpdatedEvent notification, CancellationToken cancellationToken)
{
// Get ProjectId from repository
await _notificationService.NotifySprintUpdatedAsync(
notification.SprintId,
notification.SprintName,
projectId // Need to get from Sprint
);
}
}
// SprintStartedEventHandler.cs
public class SprintStartedEventHandler : INotificationHandler<SprintStartedEvent>
{
// Similar implementation
}
// SprintCompletedEventHandler.cs
public class SprintCompletedEventHandler : INotificationHandler<SprintCompletedEvent>
{
// Similar implementation
}
// SprintDeletedEventHandler.cs
public class SprintDeletedEventHandler : INotificationHandler<SprintDeletedEvent>
{
// Similar implementation
}
```
4. **Publish Domain Events**: Update Command Handlers to publish domain events
```csharp
// In CreateSprintCommandHandler
public async Task<Guid> Handle(CreateSprintCommand request, CancellationToken cancellationToken)
{
var sprint = Sprint.Create(...);
await _repository.AddAsync(sprint, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
// Publish domain events
foreach (var domainEvent in sprint.DomainEvents)
{
await _mediator.Publish(domainEvent, cancellationToken);
}
sprint.ClearDomainEvents();
return sprint.Id;
}
```
**SignalR Event Types**:
1. `SprintCreated` - New sprint created
2. `SprintUpdated` - Sprint details updated
3. `SprintStarted` - Sprint status changed to Active
4. `SprintCompleted` - Sprint status changed to Completed
5. `SprintDeleted` - Sprint deleted
**Frontend Integration**:
```typescript
// Frontend receives notifications
connection.on('SprintCreated', (data) => {
console.log('Sprint created:', data);
// Update UI, refresh sprint list
});
connection.on('SprintStarted', (data) => {
console.log('Sprint started:', data);
// Update sprint status in UI
});
```
## Technical Notes
- Use MediatR to publish domain events after SaveChanges
- Event handlers are fire-and-forget (don't block command execution)
- Log errors if SignalR notification fails
- Use project groups for targeted notifications (`project-{projectId}`)
- Include timestamp in all notifications
## Testing
**Integration Tests**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintSignalRTests.cs`
```csharp
public class SprintSignalRTests : IntegrationTestBase
{
[Fact]
public async Task CreateSprint_ShouldSendSprintCreatedNotification()
{
// Arrange
var notificationService = GetMock<IRealtimeNotificationService>();
var command = new CreateSprintCommand
{
ProjectId = TestProjectId,
Name = "Sprint 1",
StartDate = DateTime.UtcNow,
EndDate = DateTime.UtcNow.AddDays(14)
};
// Act
var sprintId = await Mediator.Send(command);
// Assert
notificationService.Verify(
s => s.NotifySprintCreatedAsync(sprintId, "Sprint 1", TestProjectId),
Times.Once
);
}
[Fact]
public async Task StartSprint_ShouldSendSprintStartedNotification()
{
// Arrange
var sprintId = await CreateTestSprint();
var notificationService = GetMock<IRealtimeNotificationService>();
// Act
await Mediator.Send(new StartSprintCommand(sprintId));
// Assert
notificationService.Verify(
s => s.NotifySprintStartedAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<Guid>()),
Times.Once
);
}
}
```
---
**Created**: 2025-11-05 by Backend Agent