feat: add Logs
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public class AuditContext : IAuditContext
|
||||
{
|
||||
public Guid? EntityId { get; set; }
|
||||
public string? EntityName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using MediatR;
|
||||
using mws.backend.dotnet.application.Common;
|
||||
using mws.backend.dotnet.domain.Audit;
|
||||
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public class AuditLogBehavior<TRequest, TResponse>(
|
||||
IUnitOfWork uow,
|
||||
ICurrentUser currentUser,
|
||||
IAuditContext auditContext)
|
||||
: IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
|
||||
{
|
||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
|
||||
{
|
||||
if (request is not IAuditableRequest auditable)
|
||||
{
|
||||
return await next();
|
||||
}
|
||||
|
||||
auditContext.EntityId = null;
|
||||
auditContext.EntityName = null;
|
||||
|
||||
var response = await next();
|
||||
|
||||
uow.ActionLogs.Add(new ActionLog
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ActorUserId = currentUser.UserId ?? Guid.Empty,
|
||||
ActorUsername = TruncateRequired(currentUser.Username ?? "unknown", 100),
|
||||
Action = auditable.Action,
|
||||
EntityType = auditable.EntityType,
|
||||
EntityId = auditable.EntityId ?? auditContext.EntityId ?? (response as IHasEntityId)?.EntityId,
|
||||
EntityName = Truncate(auditable.EntityName ?? auditContext.EntityName, 255),
|
||||
IpAddress = currentUser.IpAddress,
|
||||
UserAgent = currentUser.UserAgent,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
});
|
||||
await uow.SaveChangesAsync(ct);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static string TruncateRequired(string value, int max) =>
|
||||
value.Length <= max ? value : value[..max];
|
||||
|
||||
private static string? Truncate(string? value, int max) =>
|
||||
value is null || value.Length <= max ? value : value[..max];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public record ActionLogFilter(
|
||||
DateTimeOffset? From,
|
||||
DateTimeOffset? To,
|
||||
string? Actor,
|
||||
string? Action,
|
||||
string? EntityType,
|
||||
Guid? EntityId);
|
||||
|
||||
public record LoginLogFilter(
|
||||
DateTimeOffset? From,
|
||||
DateTimeOffset? To,
|
||||
string? Username,
|
||||
bool? Success);
|
||||
|
||||
public class ActionLogDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid ActorUserId { get; set; }
|
||||
public string ActorUsername { get; set; } = string.Empty;
|
||||
public string Action { get; set; } = string.Empty;
|
||||
public string EntityType { get; set; } = string.Empty;
|
||||
public Guid? EntityId { get; set; }
|
||||
public string? EntityName { get; set; }
|
||||
public string? IpAddress { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class LoginLogDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public Guid? UserId { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string? FailureReason { get; set; }
|
||||
public string? IpAddress { get; set; }
|
||||
public string? UserAgent { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public interface IAuditContext
|
||||
{
|
||||
Guid? EntityId { get; set; }
|
||||
string? EntityName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public interface IAuditableRequest
|
||||
{
|
||||
string Action { get; }
|
||||
string EntityType { get; }
|
||||
Guid? EntityId => null;
|
||||
string? EntityName => null;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public interface IHasEntityId
|
||||
{
|
||||
Guid EntityId { get; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using MediatR;
|
||||
using mws.backend.dotnet.application.Common;
|
||||
using mws.backend.dotnet.application.Permissions;
|
||||
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public record GetActionLogsQuery(Guid ActorUserId, ActionLogFilter Filter, int Page, int PageSize)
|
||||
: IRequest<PagedResult<ActionLogDto>>;
|
||||
|
||||
public class GetActionLogsHandler(IUnitOfWork uow, IPermissionService permissions)
|
||||
: IRequestHandler<GetActionLogsQuery, PagedResult<ActionLogDto>>
|
||||
{
|
||||
public async Task<PagedResult<ActionLogDto>> Handle(GetActionLogsQuery query, CancellationToken ct)
|
||||
{
|
||||
await permissions.EnsureAsync(query.ActorUserId, "actionlogs", PermissionAction.View, ct);
|
||||
|
||||
var page = await uow.ActionLogs.SearchAsync(query.Filter, query.Page, query.PageSize, ct);
|
||||
return new PagedResult<ActionLogDto>
|
||||
{
|
||||
Items = page.Items.Select(x => new ActionLogDto
|
||||
{
|
||||
Id = x.Id,
|
||||
ActorUserId = x.ActorUserId,
|
||||
ActorUsername = x.ActorUsername,
|
||||
Action = x.Action,
|
||||
EntityType = x.EntityType,
|
||||
EntityId = x.EntityId,
|
||||
EntityName = x.EntityName,
|
||||
IpAddress = x.IpAddress,
|
||||
UserAgent = x.UserAgent,
|
||||
CreatedAt = x.CreatedAt,
|
||||
}).ToList(),
|
||||
TotalCount = page.TotalCount,
|
||||
Page = page.Page,
|
||||
PageSize = page.PageSize,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using mws.backend.dotnet.application.Common;
|
||||
using mws.backend.dotnet.application.Permissions;
|
||||
|
||||
namespace mws.backend.dotnet.application.Audit;
|
||||
|
||||
public record GetLoginLogsQuery(Guid ActorUserId, LoginLogFilter Filter, int Page, int PageSize)
|
||||
: IRequest<PagedResult<LoginLogDto>>;
|
||||
|
||||
public class GetLoginLogsHandler(IUnitOfWork uow, IPermissionService permissions)
|
||||
: IRequestHandler<GetLoginLogsQuery, PagedResult<LoginLogDto>>
|
||||
{
|
||||
public async Task<PagedResult<LoginLogDto>> Handle(GetLoginLogsQuery query, CancellationToken ct)
|
||||
{
|
||||
await permissions.EnsureAsync(query.ActorUserId, "loginlogs", PermissionAction.View, ct);
|
||||
|
||||
var page = await uow.LoginLogs.SearchAsync(query.Filter, query.Page, query.PageSize, ct);
|
||||
return new PagedResult<LoginLogDto>
|
||||
{
|
||||
Items = page.Items.Select(x => new LoginLogDto
|
||||
{
|
||||
Id = x.Id,
|
||||
Username = x.Username,
|
||||
UserId = x.UserId,
|
||||
Success = x.Success,
|
||||
FailureReason = x.FailureReason,
|
||||
IpAddress = x.IpAddress,
|
||||
UserAgent = x.UserAgent,
|
||||
CreatedAt = x.CreatedAt,
|
||||
}).ToList(),
|
||||
TotalCount = page.TotalCount,
|
||||
Page = page.Page,
|
||||
PageSize = page.PageSize,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user