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,191 @@
---
task_id: sprint_2_story_2_task_1
story: sprint_2_story_2
status: not_started
estimated_hours: 6
created_date: 2025-11-05
assignee: Backend Team
---
# Task 1: Implement Changed Fields Detection (JSON Diff)
**Story**: Story 2 - Audit Log Core Features (Phase 2)
**Estimated**: 6 hours
## Description
Enhance the audit logging to detect and store only the changed fields (JSON diff) instead of full entity snapshots. This optimizes storage and makes audit logs more readable.
## Acceptance Criteria
- [ ] JSON diff algorithm implemented
- [ ] Only changed fields stored in OldValues/NewValues
- [ ] Nested object changes detected
- [ ] Unit tests for diff algorithm
- [ ] Storage size reduced by 50-70%
## Implementation Details
**Files to Update**:
1. **Diff Service**: `colaflow-api/src/ColaFlow.Infrastructure/Services/JsonDiffService.cs`
```csharp
public class JsonDiffService : IJsonDiffService
{
public ChangedFields GetChangedFields(EntityEntry entry)
{
var changedFields = new Dictionary<string, FieldChange>();
foreach (var property in entry.Properties.Where(p => p.IsModified && !p.Metadata.IsPrimaryKey()))
{
changedFields[property.Metadata.Name] = new FieldChange
{
OldValue = property.OriginalValue,
NewValue = property.CurrentValue
};
}
return new ChangedFields { Fields = changedFields };
}
public string SerializeChangedFields(ChangedFields changes)
{
return JsonSerializer.Serialize(changes, new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
}
}
public class ChangedFields
{
public Dictionary<string, FieldChange> Fields { get; set; } = new();
}
public class FieldChange
{
public object? OldValue { get; set; }
public object? NewValue { get; set; }
}
```
2. **Update Interceptor**: `colaflow-api/src/ColaFlow.Infrastructure/Interceptors/AuditLogInterceptor.cs`
```csharp
public class AuditLogInterceptor : SaveChangesInterceptor
{
private readonly IJsonDiffService _diffService;
public AuditLogInterceptor(
ITenantContext tenantContext,
IHttpContextAccessor httpContextAccessor,
IJsonDiffService diffService)
{
_tenantContext = tenantContext;
_httpContextAccessor = httpContextAccessor;
_diffService = diffService;
}
private List<AuditLog> CreateAuditEntries(DbContext context)
{
// ... existing code ...
foreach (var entry in entries)
{
string? oldValues = null;
string? newValues = null;
if (entry.State == EntityState.Modified)
{
// Use diff service to get only changed fields
var changes = _diffService.GetChangedFields(entry);
var diffJson = _diffService.SerializeChangedFields(changes);
// Store diff in both OldValues and NewValues
// OldValues: { "Title": { "OldValue": "Old", "NewValue": "New" } }
oldValues = diffJson;
newValues = diffJson;
}
else if (entry.State == EntityState.Added)
{
// For Create, store all current values
newValues = SerializeAllFields(entry);
}
else if (entry.State == EntityState.Deleted)
{
// For Delete, store all original values
oldValues = SerializeAllFields(entry);
}
var auditLog = new AuditLog
{
// ... existing fields ...
OldValues = oldValues,
NewValues = newValues
};
auditLogs.Add(auditLog);
}
return auditLogs;
}
private string SerializeAllFields(EntityEntry entry)
{
var allFields = entry.Properties
.Where(p => !p.Metadata.IsPrimaryKey())
.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue);
return JsonSerializer.Serialize(allFields);
}
}
```
**Example Output**:
```json
// Before (Full Snapshot - 500 bytes):
{
"OldValues": {"Id":"abc","Title":"Old Title","Description":"Long description...","Status":"InProgress","Priority":1},
"NewValues": {"Id":"abc","Title":"New Title","Description":"Long description...","Status":"InProgress","Priority":1}
}
// After (Diff Only - 80 bytes, 84% reduction):
{
"OldValues": {"Title":{"OldValue":"Old Title","NewValue":"New Title"}},
"NewValues": {"Title":{"OldValue":"Old Title","NewValue":"New Title"}}
}
```
## Technical Notes
- Use System.Text.Json for performance
- Store diff in both OldValues and NewValues for query flexibility
- Consider nested object changes (e.g., Address.City)
- Ignore computed properties and navigation properties
## Testing
**Unit Tests**:
```csharp
[Fact]
public void GetChangedFields_ShouldReturnOnlyModifiedFields()
{
// Arrange
var entry = CreateMockEntry(
original: new { Title = "Old", Status = "InProgress" },
current: new { Title = "New", Status = "InProgress" }
);
// Act
var changes = _diffService.GetChangedFields(entry);
// Assert
Assert.Single(changes.Fields);
Assert.True(changes.Fields.ContainsKey("Title"));
Assert.Equal("Old", changes.Fields["Title"].OldValue);
Assert.Equal("New", changes.Fields["Title"].NewValue);
}
```
---
**Created**: 2025-11-05 by Backend Agent