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:
163
docs/plans/sprint_2_story_1_task_3.md
Normal file
163
docs/plans/sprint_2_story_1_task_3.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
task_id: sprint_2_story_1_task_3
|
||||
story: sprint_2_story_1
|
||||
status: not_started
|
||||
estimated_hours: 6
|
||||
created_date: 2025-11-05
|
||||
assignee: Backend Team
|
||||
---
|
||||
|
||||
# Task 3: Implement EF Core SaveChangesInterceptor
|
||||
|
||||
**Story**: Story 1 - Audit Log Foundation (Phase 1)
|
||||
**Estimated**: 6 hours
|
||||
|
||||
## Description
|
||||
|
||||
Implement EF Core SaveChangesInterceptor to automatically capture and log all Create/Update/Delete operations on auditable entities. This is the core mechanism for transparent audit logging.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AuditLogInterceptor class created
|
||||
- [ ] Automatic detection of Create/Update/Delete operations
|
||||
- [ ] Audit log entries created for each operation
|
||||
- [ ] TenantId and UserId automatically captured
|
||||
- [ ] Interceptor registered in DI container
|
||||
- [ ] Performance overhead < 5ms per SaveChanges
|
||||
|
||||
## Implementation Details
|
||||
|
||||
**Files to Create**:
|
||||
|
||||
1. **Interceptor**: `colaflow-api/src/ColaFlow.Infrastructure/Interceptors/AuditLogInterceptor.cs`
|
||||
```csharp
|
||||
public class AuditLogInterceptor : SaveChangesInterceptor
|
||||
{
|
||||
private readonly ITenantContext _tenantContext;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AuditLogInterceptor(ITenantContext tenantContext, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_tenantContext = tenantContext;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
|
||||
DbContextEventData eventData,
|
||||
InterceptionResult<int> result,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (eventData.Context is null) return result;
|
||||
|
||||
var auditEntries = CreateAuditEntries(eventData.Context);
|
||||
|
||||
// Add audit logs to context
|
||||
foreach (var auditEntry in auditEntries)
|
||||
{
|
||||
eventData.Context.Add(auditEntry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<AuditLog> CreateAuditEntries(DbContext context)
|
||||
{
|
||||
var auditLogs = new List<AuditLog>();
|
||||
var entries = context.ChangeTracker.Entries()
|
||||
.Where(e => e.State == EntityState.Added ||
|
||||
e.State == EntityState.Modified ||
|
||||
e.State == EntityState.Deleted)
|
||||
.Where(e => IsAuditable(e.Entity))
|
||||
.ToList();
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var auditLog = new AuditLog
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantContext.TenantId,
|
||||
EntityType = entry.Entity.GetType().Name,
|
||||
EntityId = GetEntityId(entry),
|
||||
Action = entry.State switch
|
||||
{
|
||||
EntityState.Added => AuditAction.Create,
|
||||
EntityState.Modified => AuditAction.Update,
|
||||
EntityState.Deleted => AuditAction.Delete,
|
||||
_ => throw new InvalidOperationException()
|
||||
},
|
||||
UserId = GetCurrentUserId(),
|
||||
Timestamp = DateTime.UtcNow,
|
||||
OldValues = entry.State == EntityState.Modified ? SerializeOldValues(entry) : null,
|
||||
NewValues = entry.State != EntityState.Deleted ? SerializeNewValues(entry) : null
|
||||
};
|
||||
|
||||
auditLogs.Add(auditLog);
|
||||
}
|
||||
|
||||
return auditLogs;
|
||||
}
|
||||
|
||||
private bool IsAuditable(object entity)
|
||||
{
|
||||
// Check if entity implements IAuditable interface or is in auditable types list
|
||||
return entity is Project || entity is Epic || entity is Story || entity is WorkTask;
|
||||
}
|
||||
|
||||
private Guid GetEntityId(EntityEntry entry)
|
||||
{
|
||||
var keyValue = entry.Properties.FirstOrDefault(p => p.Metadata.IsPrimaryKey())?.CurrentValue;
|
||||
return keyValue is Guid id ? id : Guid.Empty;
|
||||
}
|
||||
|
||||
private Guid? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = _httpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier);
|
||||
return userIdClaim != null ? Guid.Parse(userIdClaim.Value) : null;
|
||||
}
|
||||
|
||||
private string? SerializeOldValues(EntityEntry entry)
|
||||
{
|
||||
var oldValues = entry.Properties
|
||||
.Where(p => p.IsModified)
|
||||
.ToDictionary(p => p.Metadata.Name, p => p.OriginalValue);
|
||||
return JsonSerializer.Serialize(oldValues);
|
||||
}
|
||||
|
||||
private string? SerializeNewValues(EntityEntry entry)
|
||||
{
|
||||
var newValues = entry.Properties
|
||||
.Where(p => !p.Metadata.IsPrimaryKey())
|
||||
.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue);
|
||||
return JsonSerializer.Serialize(newValues);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **DI Registration**: Update `colaflow-api/src/ColaFlow.Infrastructure/DependencyInjection.cs`
|
||||
```csharp
|
||||
services.AddDbContext<ColaFlowDbContext>((serviceProvider, options) =>
|
||||
{
|
||||
options.UseNpgsql(connectionString);
|
||||
options.AddInterceptors(serviceProvider.GetRequiredService<AuditLogInterceptor>());
|
||||
});
|
||||
|
||||
services.AddScoped<AuditLogInterceptor>();
|
||||
```
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Phase 1 captures basic Create/Update/Delete operations
|
||||
- Changed Fields tracking (old vs new values diff) will be enhanced in Phase 2
|
||||
- Performance optimization: Consider async operations and batching
|
||||
- Use System.Text.Json for serialization (faster than Newtonsoft.Json)
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit tests for interceptor logic
|
||||
- Integration tests for automatic audit logging
|
||||
- Performance benchmark: < 5ms overhead
|
||||
- Verify TenantId and UserId are captured correctly
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025-11-05 by Backend Agent
|
||||
Reference in New Issue
Block a user