feat(backend): Add AuditLog repository interface and implementation

Implement repository pattern for AuditLog entity for Sprint 2 Story 1 Task 2.

Changes:
- Created IAuditLogRepository interface with 6 query methods
- Implemented AuditLogRepository with efficient querying
- Registered repository in DI container
- All queries use AsNoTracking for read-only operations

Query Methods:
- GetByIdAsync: Get single audit log by ID
- GetByEntityAsync: Get audit history for specific entity
- GetByUserAsync: Get user activity with pagination
- GetRecentAsync: Get recent audit logs
- AddAsync: Add new audit log
- GetCountAsync: Get total audit log count

Performance:
- All queries automatically filtered by TenantId (Global Query Filter)
- Efficient use of composite indexes
- AsNoTracking for read-only operations

Testing:
- All tests passing (192 domain + 113 identity + 8 arch + 32 app + 12 infra = 357 tests)
- No compilation errors
- Zero test failures

🤖 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 23:14:41 +01:00
parent 599c1aedc6
commit 2466cd4020
4 changed files with 102 additions and 1 deletions

View File

@@ -45,6 +45,7 @@ public static class ModuleExtensions
// Register repositories
services.AddScoped<IProjectRepository, ProjectRepository>();
services.AddScoped<IAuditLogRepository, ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories.AuditLogRepository>();
services.AddScoped<IUnitOfWork, ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence.UnitOfWork>();
// Register services

View File

@@ -0,0 +1,27 @@
using ColaFlow.Modules.ProjectManagement.Domain.Entities;
namespace ColaFlow.Modules.ProjectManagement.Domain.Repositories;
public interface IAuditLogRepository
{
Task<AuditLog?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<AuditLog>> GetByEntityAsync(
string entityType,
Guid entityId,
CancellationToken cancellationToken = default);
Task<IReadOnlyList<AuditLog>> GetByUserAsync(
Guid userId,
int pageNumber = 1,
int pageSize = 50,
CancellationToken cancellationToken = default);
Task<IReadOnlyList<AuditLog>> GetRecentAsync(
int count = 100,
CancellationToken cancellationToken = default);
Task AddAsync(AuditLog auditLog, CancellationToken cancellationToken = default);
Task<int> GetCountAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,72 @@
using ColaFlow.Modules.ProjectManagement.Domain.Entities;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
namespace ColaFlow.Modules.ProjectManagement.Infrastructure.Repositories;
public class AuditLogRepository : IAuditLogRepository
{
private readonly PMDbContext _context;
public AuditLogRepository(PMDbContext context)
{
_context = context;
}
public async Task<AuditLog?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _context.AuditLogs
.AsNoTracking()
.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
}
public async Task<IReadOnlyList<AuditLog>> GetByEntityAsync(
string entityType,
Guid entityId,
CancellationToken cancellationToken = default)
{
return await _context.AuditLogs
.AsNoTracking()
.Where(a => a.EntityType == entityType && a.EntityId == entityId)
.OrderByDescending(a => a.Timestamp)
.ToListAsync(cancellationToken);
}
public async Task<IReadOnlyList<AuditLog>> GetByUserAsync(
Guid userId,
int pageNumber = 1,
int pageSize = 50,
CancellationToken cancellationToken = default)
{
return await _context.AuditLogs
.AsNoTracking()
.Where(a => a.UserId == userId)
.OrderByDescending(a => a.Timestamp)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
}
public async Task<IReadOnlyList<AuditLog>> GetRecentAsync(
int count = 100,
CancellationToken cancellationToken = default)
{
return await _context.AuditLogs
.AsNoTracking()
.OrderByDescending(a => a.Timestamp)
.Take(count)
.ToListAsync(cancellationToken);
}
public async Task AddAsync(AuditLog auditLog, CancellationToken cancellationToken = default)
{
await _context.AuditLogs.AddAsync(auditLog, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task<int> GetCountAsync(CancellationToken cancellationToken = default)
{
return await _context.AuditLogs.CountAsync(cancellationToken);
}
}

View File

@@ -1,9 +1,10 @@
---
task_id: sprint_2_story_1_task_2
story: sprint_2_story_1
status: not_started
status: in_progress
estimated_hours: 4
created_date: 2025-11-05
start_date: 2025-11-05
assignee: Backend Team
---