feat: add Logs

This commit is contained in:
2026-09-20 16:21:15 +07:00
parent 637ca51f47
commit d3c3ecd5e6
62 changed files with 1577 additions and 66 deletions
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using mws.backend.dotnet.domain.Audit;
using mws.backend.dotnet.domain.Documents;
using mws.backend.dotnet.domain.MasterData;
using mws.backend.dotnet.domain.Projects;
@@ -20,6 +21,8 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
public DbSet<Role> Roles => Set<Role>();
public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
public DbSet<MasterDataEntry> MasterData => Set<MasterDataEntry>();
public DbSet<ActionLog> ActionLogs => Set<ActionLog>();
public DbSet<LoginLog> LoginLogs => Set<LoginLog>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using mws.backend.dotnet.domain.Audit;
namespace mws.backend.dotnet.infrastructure.Persistence.Configuration;
public class ActionLogConfiguration : IEntityTypeConfiguration<ActionLog>
{
public void Configure(EntityTypeBuilder<ActionLog> e)
{
e.ToTable("action_logs");
e.HasKey(x => x.Id);
e.Property(x => x.ActorUsername).HasMaxLength(100).IsRequired();
e.Property(x => x.Action).HasMaxLength(100).IsRequired();
e.Property(x => x.EntityType).HasMaxLength(50).IsRequired();
e.Property(x => x.EntityName).HasMaxLength(255);
e.Property(x => x.IpAddress).HasMaxLength(45);
e.Property(x => x.UserAgent).HasMaxLength(512);
e.HasIndex(x => x.CreatedAt);
e.HasIndex(x => x.ActorUserId);
e.HasIndex(x => x.Action);
e.HasIndex(x => x.EntityType);
}
}
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using mws.backend.dotnet.domain.Audit;
namespace mws.backend.dotnet.infrastructure.Persistence.Configuration;
public class LoginLogConfiguration : IEntityTypeConfiguration<LoginLog>
{
public void Configure(EntityTypeBuilder<LoginLog> e)
{
e.ToTable("login_logs");
e.HasKey(x => x.Id);
e.Property(x => x.Username).HasMaxLength(100).IsRequired();
e.Property(x => x.FailureReason).HasMaxLength(100);
e.Property(x => x.IpAddress).HasMaxLength(45);
e.Property(x => x.UserAgent).HasMaxLength(512);
e.HasIndex(x => x.CreatedAt);
e.HasIndex(x => x.Username);
e.HasIndex(x => x.Success);
}
}
+1 -1
View File
@@ -141,7 +141,7 @@ public static class DbSeeder
"Member",
screen => screen.Key switch
{
"users" or "permissions" or "masterdata" => (View: false, Create: false, Edit: false, Delete: false),
"users" or "permissions" or "masterdata" or "actionlogs" or "loginlogs" => (View: false, Create: false, Edit: false, Delete: false),
"projects" => (View: true, Create: false, Edit: false, Delete: false),
_ => (View: true, Create: true, Edit: true, Delete: false),
},
@@ -0,0 +1,35 @@
using mws.backend.dotnet.application.Audit;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Audit;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class ActionLogRepository(AppDbContext db) : RepositoryBase<ActionLog>(db), IActionLogRepository
{
public Task<PagedResult<ActionLog>> SearchAsync(ActionLogFilter filter, int page, int pageSize, CancellationToken ct = default)
{
var query = Set.AsQueryable();
if (filter.From is { } from) query = query.Where(x => x.CreatedAt >= from);
if (filter.To is { } to) query = query.Where(x => x.CreatedAt <= to);
if (!string.IsNullOrWhiteSpace(filter.Actor))
{
var lower = filter.Actor.Trim().ToLower();
query = query.Where(x => x.ActorUsername.ToLower().Contains(lower));
}
if (!string.IsNullOrWhiteSpace(filter.Action))
{
var lower = filter.Action.Trim().ToLower();
query = query.Where(x => x.Action.ToLower().Contains(lower));
}
if (!string.IsNullOrWhiteSpace(filter.EntityType))
{
var lower = filter.EntityType.Trim().ToLower();
query = query.Where(x => x.EntityType.ToLower().Contains(lower));
}
if (filter.EntityId is { } entityId) query = query.Where(x => x.EntityId == entityId);
return query.OrderByDescending(x => x.CreatedAt).ToPagedResultAsync(page, pageSize, ct);
}
}
@@ -0,0 +1,25 @@
using mws.backend.dotnet.application.Audit;
using mws.backend.dotnet.application.Common;
using mws.backend.dotnet.application.Common.Repositories;
using mws.backend.dotnet.domain.Audit;
namespace mws.backend.dotnet.infrastructure.Persistence.Repositories;
public class LoginLogRepository(AppDbContext db) : RepositoryBase<LoginLog>(db), ILoginLogRepository
{
public Task<PagedResult<LoginLog>> SearchAsync(LoginLogFilter filter, int page, int pageSize, CancellationToken ct = default)
{
var query = Set.AsQueryable();
if (filter.From is { } from) query = query.Where(x => x.CreatedAt >= from);
if (filter.To is { } to) query = query.Where(x => x.CreatedAt <= to);
if (!string.IsNullOrWhiteSpace(filter.Username))
{
var lower = filter.Username.Trim().ToLower();
query = query.Where(x => x.Username.ToLower().Contains(lower));
}
if (filter.Success is { } success) query = query.Where(x => x.Success == success);
return query.OrderByDescending(x => x.CreatedAt).ToPagedResultAsync(page, pageSize, ct);
}
}
+4
View File
@@ -17,6 +17,8 @@ public class UnitOfWork : IUnitOfWork
Documents = new DocumentRepository(db);
Roles = new RoleRepository(db);
MasterData = new MasterDataRepository(db);
ActionLogs = new ActionLogRepository(db);
LoginLogs = new LoginLogRepository(db);
}
public IUserRepository Users { get; }
@@ -25,6 +27,8 @@ public class UnitOfWork : IUnitOfWork
public IDocumentRepository Documents { get; }
public IRoleRepository Roles { get; }
public IMasterDataRepository MasterData { get; }
public IActionLogRepository ActionLogs { get; }
public ILoginLogRepository LoginLogs { get; }
public Task<int> SaveChangesAsync(CancellationToken ct = default) => _db.SaveChangesAsync(ct);
}