--- task_id: sprint_2_story_2_task_1 story: sprint_2_story_2 status: in_progress estimated_hours: 6 created_date: 2025-11-05 start_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(); 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 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 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