Implemented centralised audit logging

This commit is contained in:
2026-09-21 16:32:56 +02:00
parent 78b03b6996
commit f4024d563a
41 changed files with 5751 additions and 54 deletions
@@ -0,0 +1,9 @@
namespace PostFundManagement.Domain.Abstractions;
public interface ITenantContextService
{
string? TenantName { get; }
long TenantId { get; }
bool IsAdmin { get; }
bool HasTenantFilter { get; }
}
@@ -0,0 +1,4 @@
namespace PostFundManagement.Domain.Attributes;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class AuditAttribute : Attribute;
+13 -4
View File
@@ -5,16 +5,19 @@ namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<AuditLogConfiguration, AuditLog>]
public class AuditLog : Models.AuditLog
{
public virtual User? Performer { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual User? Performer { get; set; }
}
public sealed class AuditLogConfiguration : IEntityTypeConfiguration<Domain.Entities.AuditLog>
public sealed class AuditLogConfiguration : IEntityTypeConfiguration<AuditLog>
{
public void Configure(EntityTypeBuilder<Domain.Entities.AuditLog> builder)
public void Configure(EntityTypeBuilder<AuditLog> builder)
{
builder.ToTable(nameof(Domain.Entities.AuditLog).Pluralize());
builder.ToTable(nameof(AuditLog).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.TenantId).IsRequired(false);
builder.Property(f => f.EntityName).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.EntityId).IsRequired();
builder.Property(f => f.Action).IsRequired().HasMaxLength(ShortLabelLength);
@@ -22,6 +25,12 @@ public sealed class AuditLogConfiguration : IEntityTypeConfiguration<Domain.Enti
builder.Property(f => f.PerformedBy).IsRequired();
builder.Property(f => f.Timestamp).IsRequired().HasDefaultValueSql("now()");
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired(false)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Performer)
.WithMany()
.HasForeignKey(fk => fk.PerformedBy)
@@ -9,6 +9,8 @@ public class Award : Models.Award
public virtual User? Approver { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; }
public virtual Programme? Programme { get; set; }
@@ -27,6 +29,7 @@ public sealed class AwardConfiguration : IEntityTypeConfiguration<Award>
builder.HasKey(pk => pk.Id);
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.ProjectId).IsRequired(false);
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
@@ -37,6 +40,12 @@ public sealed class AwardConfiguration : IEntityTypeConfiguration<Award>
.HasSentinel(ApprovalStatus.Pending);
builder.Property(f => f.Amount).IsRequired().HasPrecision(18, 2);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
@@ -5,6 +5,8 @@ namespace PostFundManagement.Domain.Entities;
[EntityTypeConfiguration<NotificationConfiguration, Notification>]
public class Notification : Models.Notification
{
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; }
public virtual User? RecipientUser { get; set; }
@@ -22,6 +24,7 @@ public sealed class NotificationConfiguration : IEntityTypeConfiguration<Notific
builder.Property(f => f.SentAt).IsRequired(false);
builder.Property(f => f.Recipient).IsRequired();
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.Platform).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(NotificationPlatform.Email);
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
@@ -31,6 +34,12 @@ public sealed class NotificationConfiguration : IEntityTypeConfiguration<Notific
builder.Property(f => f.Destination).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Error).IsRequired(false);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
@@ -9,6 +9,8 @@ public class Portfolio : Models.Portfolio
public virtual User? Owner { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Organisation? Organisation { get; set; }
public virtual ICollection<Programme> Programmes { get; set; } = [];
@@ -21,6 +23,7 @@ public sealed class PortfolioConfiguration : IEntityTypeConfiguration<Portfolio>
builder.ToTable(nameof(Portfolio).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.OrganisationId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.OwnedBy).IsRequired();
@@ -28,6 +31,12 @@ public sealed class PortfolioConfiguration : IEntityTypeConfiguration<Portfolio>
builder.Property(f => f.Name).IsRequired().HasMaxLength(LabelLength);
builder.Property(f => f.Description).IsRequired(false).HasMaxLength(TextLength);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Organisation)
.WithMany()
.HasForeignKey(fk => fk.OrganisationId)
@@ -8,6 +8,8 @@ public class Programme : Models.Programme
public virtual User? Creator { get; set; }
public virtual User? Owner { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Portfolio? Portfolio { get; set; }
@@ -27,6 +29,7 @@ public sealed class ProgrammeConfiguration : IEntityTypeConfiguration<Programme>
builder.ToTable(nameof(Programme).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.PortfolioId).IsRequired();
builder.Property(f => f.OwnedBy).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
@@ -37,6 +40,12 @@ public sealed class ProgrammeConfiguration : IEntityTypeConfiguration<Programme>
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(f => f.Portfolio)
.WithMany(f => f.Programmes)
.HasForeignKey(fk => fk.PortfolioId)
@@ -9,6 +9,8 @@ public class Project : Models.Project
public virtual User? Updator { get; set; }
public virtual Tenant? Tenant { get; set; }
public virtual Programme? Programme { get; set; }
public virtual ICollection<Award> Awards { get; set; } = [];
@@ -21,6 +23,7 @@ public sealed class ProjectConfiguration : IEntityTypeConfiguration<Project>
builder.ToTable(nameof(Project).Pluralize());
builder.HasKey(pk => pk.Id);
builder.Property(f => f.TenantId).IsRequired();
builder.Property(f => f.ProgrammeId).IsRequired();
builder.Property(f => f.CreatedBy).IsRequired();
builder.Property(f => f.UpdatedBy).IsRequired(false);
@@ -37,6 +40,12 @@ public sealed class ProjectConfiguration : IEntityTypeConfiguration<Project>
builder.Property(f => f.Status).IsRequired().HasConversion<int>().HasDefaultValueSql("1")
.HasSentinel(ApprovalStatus.Pending);
builder.HasOne(f => f.Tenant)
.WithMany()
.HasForeignKey(fk => fk.TenantId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Programme)
.WithMany(f => f.Projects)
.HasForeignKey(fk => fk.ProgrammeId)
@@ -30,6 +30,12 @@ public sealed class TenantUserConfiguration : IEntityTypeConfiguration<TenantUse
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.User)
.WithMany()
.HasForeignKey(fk => fk.UserId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(f => f.Creator)
.WithMany()
.HasForeignKey(fk => fk.CreatedBy)
@@ -4,6 +4,37 @@ namespace PostFundManagement.Domain.Extensions;
public static class Mappers
{
public static TenantUser Map(this Entities.TenantUser entity) => new()
{
Id = entity.Id,
TenantId = entity.TenantId,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
Status = entity.Status,
UserId = entity.UserId
};
public static TenantOrganisation Map(this Entities.TenantOrganisation entity) => new()
{
Id = entity.Id,
Status = entity.Status,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
OrganisationId = entity.OrganisationId,
TenantId = entity.TenantId
};
public static Tenant Map(this Entities.Tenant entity) => new()
{
Id = entity.Id,
Name = entity.Name,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
Status = entity.Status,
UpdatedAt = entity.UpdatedAt,
UpdatedBy = entity.UpdatedBy
};
public static Activity Map(this Entities.Activity entity) => new()
{
Id = entity.Id,
@@ -40,6 +71,7 @@ public static class Mappers
ApprovedBy = entity.ApprovedBy,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
TenantId = entity.TenantId,
OrganisationId = entity.OrganisationId,
ProgrammeId = entity.ProgrammeId,
ProjectId = entity.ProjectId,
@@ -162,7 +194,7 @@ public static class Mappers
AwardId = entity.AwardId,
ContractId = entity.ContractId,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
CreatedBy = entity.CreatedBy,
InvitedOrganisationId = entity.InvitedOrganisationId,
OrganisationId = entity.OrganisationId,
Recipient = entity.Recipient,
@@ -202,7 +234,7 @@ public static class Mappers
public static Organisation Map(this Entities.Organisation entity) => new()
{
Id = entity.Id,
CreatedAt = entity.CreatedAt,
CreatedAt = entity.CreatedAt,
CreatedBy = entity.CreatedBy,
Email = entity.Email,
Name = entity.Name,
@@ -225,6 +257,7 @@ public static class Mappers
{
Id = entity.Id,
CreatedAt = entity.CreatedAt,
TenantId = entity.TenantId,
CreatedBy = entity.CreatedBy,
Description = entity.Description,
Name = entity.Name,
@@ -239,6 +272,7 @@ public static class Mappers
Destination = entity.Destination,
Error = entity.Error,
Message = entity.Message,
TenantId = entity.TenantId,
OrganisationId = entity.OrganisationId,
Platform = entity.Platform,
Recipient = entity.Recipient,
@@ -255,6 +289,7 @@ public static class Mappers
CreatedBy = entity.CreatedBy,
Name = entity.Name,
OwnedBy = entity.OwnedBy,
TenantId = entity.TenantId,
PortfolioId = entity.PortfolioId,
Status = entity.Status,
UpdatedAt = entity.UpdatedAt,
@@ -269,6 +304,7 @@ public static class Mappers
Description = entity.Description,
EndedAt = entity.EndedAt,
Name = entity.Name,
TenantId = entity.TenantId,
ProgrammeId = entity.ProgrammeId,
StartedAt = entity.StartedAt,
Status = entity.Status,
+3 -1
View File
@@ -4,9 +4,11 @@ public class AuditLog
{
public long Id { get; set; }
public long? TenantId { get; set; }
public string? EntityName { get; set; }
public long EntityId { get; set; }
public string? EntityId { get; set; }
public string? Action { get; set; }
@@ -6,6 +6,8 @@ public class Award
public long OrganisationId { get; set; }
public long TenantId { get; set; }
public long ProgrammeId { get; set; }
public long? ProjectId { get; set; }
@@ -4,6 +4,8 @@ public class Notification
{
public long Id { get; set; }
public long TenantId { get; set; }
public long OrganisationId { get; set; }
public Guid Recipient { get; set; }
@@ -4,6 +4,8 @@ public class Portfolio
{
public long Id { get; set; }
public long TenantId { get; set; }
public long OrganisationId { get; set; }
public Guid OwnedBy { get; set; }
@@ -6,6 +6,8 @@ public class Programme
public long PortfolioId { get; set; }
public long TenantId { get; set; }
public Guid OwnedBy { get; set; }
public Guid CreatedBy { get; set; }
@@ -6,6 +6,8 @@ public class Project
public long ProgrammeId { get; set; }
public long TenantId { get; set; }
public Guid CreatedBy { get; set; }
public Guid? UpdatedBy { get; set; }
+2 -2
View File
@@ -8,9 +8,9 @@ public class Tenant
public Guid CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public Guid UpdatedBy { get; set; }
public Guid? UpdatedBy { get; set; }
public string? Name { get; set; }