Skip to content

Commit

Permalink
feat: audit trail
Browse files Browse the repository at this point in the history
  • Loading branch information
Amitpnk committed May 6, 2021
1 parent f8ed746 commit ba8f7f5
Show file tree
Hide file tree
Showing 21 changed files with 352 additions and 1,496 deletions.
4 changes: 4 additions & 0 deletions src/CleanArch.Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public void ConfigureServices(IServiceCollection services)

services.AddApplicationServices();

services.AddScoped<ILoggedInUserService, LoggedInUserService>();

Check failure on line 50 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'ILoggedInUserService' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 50 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'LoggedInUserService' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 50 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'ILoggedInUserService' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 50 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'LoggedInUserService' could not be found (are you missing a using directive or an assembly reference?)

services.AddSingleton<IDateTimeProvider, DateTimeProvider>();

Check failure on line 52 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'IDateTimeProvider' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 52 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'DateTimeProvider' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 52 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'IDateTimeProvider' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 52 in src/CleanArch.Api/Startup.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'DateTimeProvider' could not be found (are you missing a using directive or an assembly reference?)

services.AddInfrastructureServices(Configuration);

services.AddPersistenceServices(Configuration);
Expand Down
7 changes: 0 additions & 7 deletions src/CleanArch.Application/Contracts/ILoggedInUserService.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.CrossCuttingConcerns.Identity
{
public interface ILoggedInUserService
{
bool IsAuthenticated { get; }
string UserId { get; }
}
}
19 changes: 19 additions & 0 deletions src/CleanArch.CrossCuttingConcerns/OS/IDateTimeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.CrossCuttingConcerns.OS
{
public interface IDateTimeProvider
{
DateTime Now { get; }

DateTime UtcNow { get; }

DateTimeOffset OffsetNow { get; }

DateTimeOffset OffsetUtcNow { get; }
}
}
22 changes: 22 additions & 0 deletions src/CleanArch.Domain/Entities/AuditTrail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using CleanArch.Domain.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.Domain.Entities
{
public class AuditTrail : IHasKey<int>
{
public int Id { get; set; }
public string UserId { get; set; }
public string Type { get; set; }
public string TableName { get; set; }
public DateTime DateTime { get; set; }
public string OldValues { get; set; }
public string NewValues { get; set; }
public string AffectedColumns { get; set; }
public string PrimaryKey { get; set; }
}
}
16 changes: 16 additions & 0 deletions src/CleanArch.Domain/Enum/AuditType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.Domain.Enum
{
public enum AuditType
{
None = 0,
Create = 1,
Update = 2,
Delete = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

<ItemGroup>
<ProjectReference Include="..\CleanArch.Application\CleanArch.Application.csproj" />
<ProjectReference Include="..\CleanArch.CrossCuttingConcerns\CleanArch.CrossCuttingConcerns.csproj" />
<ProjectReference Include="..\CleanArch.Domain\CleanArch.Domain.csproj" />
</ItemGroup>

Expand Down
53 changes: 53 additions & 0 deletions src/CleanArch.Infrastructure/Identity/LoggedInUserService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using CleanArch.CrossCuttingConcerns.Identity;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.Infrastructure.Identity
{

// refer CurrentWebUser
public class LoggedInUserService : ILoggedInUserService
{
private readonly IHttpContextAccessor _context;
public LoggedInUserService(IHttpContextAccessor httpContextAccessor)
{
_context = httpContextAccessor;
//UserId = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
//Claims = httpContextAccessor.HttpContext?.User?.Claims.AsEnumerable().Select(item => new KeyValuePair<string, string>(item.Type, item.Value)).ToList();
}
public bool IsAuthenticated
{
get
{
return _context.HttpContext.User.Identity.IsAuthenticated;
}
}

public string UserId
{
get
{
var userId = _context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? _context.HttpContext.User.FindFirst("sub")?.Value;

return userId;
}
}

public List<KeyValuePair<string, string>> Claims
{
get
{
return _context.HttpContext?.User?.Claims.AsEnumerable().Select(item => new KeyValuePair<string, string>(
item.Type,
item.Value)).ToList();
}
}

}
}
20 changes: 20 additions & 0 deletions src/CleanArch.Infrastructure/OS/DateTimeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CleanArch.CrossCuttingConcerns.OS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.Infrastructure.OS
{
public class DateTimeProvider: IDateTimeProvider
{
public DateTime Now => DateTime.Now;

public DateTime UtcNow => DateTime.UtcNow;

public DateTimeOffset OffsetNow => DateTimeOffset.Now;

public DateTimeOffset OffsetUtcNow => DateTimeOffset.UtcNow;
}
}
44 changes: 44 additions & 0 deletions src/CleanArch.Persistence/AuditEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using CleanArch.Domain.Entities;
using CleanArch.Domain.Enum;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArch.Persistence
{
public class AuditEntry
{
public AuditEntry(EntityEntry entry)
{
Entry = entry;
}
public EntityEntry Entry { get; }
public string UserId { get; set; }
public string TableName { get; set; }
public Dictionary<string, object> KeyValues { get; } = new Dictionary<string, object>();
public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>();
public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>();
public List<PropertyEntry> TemporaryProperties { get; } = new List<PropertyEntry>();
public AuditType AuditType { get; set; }
public List<string> ChangedColumns { get; } = new List<string>();
public bool HasTemporaryProperties => TemporaryProperties.Any();

public AuditTrail ToAudit()
{
var audit = new AuditTrail();
audit.UserId = UserId;
audit.Type = AuditType.ToString();
audit.TableName = TableName;
audit.DateTime = DateTime.UtcNow;
audit.PrimaryKey = JsonConvert.SerializeObject(KeyValues);
audit.OldValues = OldValues.Count == 0 ? null : JsonConvert.SerializeObject(OldValues);
audit.NewValues = NewValues.Count == 0 ? null : JsonConvert.SerializeObject(NewValues);
audit.AffectedColumns = ChangedColumns.Count == 0 ? null : JsonConvert.SerializeObject(ChangedColumns);
return audit;
}
}
}
1 change: 1 addition & 0 deletions src/CleanArch.Persistence/CleanArch.Persistence.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<ItemGroup>
<ProjectReference Include="..\CleanArch.Application\CleanArch.Application.csproj" />
<ProjectReference Include="..\CleanArch.CrossCuttingConcerns\CleanArch.CrossCuttingConcerns.csproj" />
</ItemGroup>

</Project>
46 changes: 36 additions & 10 deletions src/CleanArch.Persistence/Context/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,71 @@
using CleanArch.Domain.Common;
using CleanArch.CrossCuttingConcerns.Identity;
using CleanArch.CrossCuttingConcerns.OS;
using CleanArch.Domain.Common;
using CleanArch.Domain.Entities;
using CleanArch.Persistence.Context.Seeds.Application;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CleanArch.Persistence.Context
{
public class ApplicationDbContext : DbContext
public class ApplicationDbContext : AuditableContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
private readonly ILoggedInUserService _loggedInUserService;
private readonly IDateTimeProvider _dateTimeProvider;

public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options,
ILoggedInUserService loggedInUserService,
IDateTimeProvider dateTimeProvider)
: base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
_loggedInUserService = loggedInUserService;
_dateTimeProvider = dateTimeProvider;
}

public DbSet<Event> Events { get; set; }
public DbSet<Category> Categories { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
protected override void OnModelCreating(ModelBuilder builder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
modelBuilder.ApplicationSeed();
foreach (var property in builder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
property.SetColumnType("decimal(18,2)");
}

builder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
builder.ApplicationSeed();
}

public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedDate = DateTime.Now;
entry.Entity.CreatedBy = _loggedInUserService.UserId;
entry.Entity.CreatedDate = _dateTimeProvider.UtcNow;
break;
case EntityState.Modified:
entry.Entity.LastModifiedDate = DateTime.Now;
entry.Entity.LastModifiedBy = _loggedInUserService.UserId;
entry.Entity.LastModifiedDate = _dateTimeProvider.UtcNow;
break;
}
}
return base.SaveChangesAsync(cancellationToken);
if (_loggedInUserService.UserId == null)
{
return await base.SaveChangesAsync(cancellationToken);
}
else
{
return await base.SaveChangesAsync(_loggedInUserService.UserId);
}
}
}
}
Loading

0 comments on commit ba8f7f5

Please sign in to comment.