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>
5.4 KiB
5.4 KiB
task_id, story, status, estimated_hours, created_date, assignee
| task_id | story | status | estimated_hours | created_date | assignee |
|---|---|---|---|---|---|
| sprint_2_story_2_task_1 | sprint_2_story_2 | not_started | 6 | 2025-11-05 | 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:
- Diff Service:
colaflow-api/src/ColaFlow.Infrastructure/Services/JsonDiffService.cs
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; }
}
- Update Interceptor:
colaflow-api/src/ColaFlow.Infrastructure/Interceptors/AuditLogInterceptor.cs
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:
// 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:
[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