diff --git a/Directory.Packages.props b/Directory.Packages.props
index 63833ad..2190654 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,8 +16,9 @@
+
-
+
\ No newline at end of file
diff --git a/Voxen-Server.slnx b/Voxen-Server.slnx
index 9291968..1ed1998 100644
--- a/Voxen-Server.slnx
+++ b/Voxen-Server.slnx
@@ -19,6 +19,7 @@
+
diff --git a/src/bundles/Voxen.Server/Program.cs b/src/bundles/Voxen.Server/Program.cs
index c533fbe..b86b1d5 100644
--- a/src/bundles/Voxen.Server/Program.cs
+++ b/src/bundles/Voxen.Server/Program.cs
@@ -1,5 +1,6 @@
using FastEndpoints;
using FastEndpoints.Swagger;
+using Voxen.Server.Audits.Extensions;
using Voxen.Server.Authentication.Extensions;
using Voxen.Server.Channels.Extensions;
using Voxen.Server.Domain.Extensions;
@@ -14,6 +15,7 @@
var jwtSettings = builder.Configuration.GetSection("Jwt");
builder.Services
+ .AddVoxenAudits()
.AddVoxenApiServices()
.AddVoxenAuthentication(jwtSettings)
.AddVoxenChannels()
diff --git a/src/bundles/Voxen.Server/Voxen.Server.csproj b/src/bundles/Voxen.Server/Voxen.Server.csproj
index 3e7ae42..c351cdd 100644
--- a/src/bundles/Voxen.Server/Voxen.Server.csproj
+++ b/src/bundles/Voxen.Server/Voxen.Server.csproj
@@ -8,6 +8,7 @@
+
diff --git a/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsEndpoint.cs b/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsEndpoint.cs
new file mode 100644
index 0000000..cace929
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsEndpoint.cs
@@ -0,0 +1,46 @@
+using FastEndpoints;
+using Microsoft.EntityFrameworkCore;
+using System.Text.Json;
+using Voxen.Server.Audits.Models;
+using Voxen.Server.Domain;
+using Voxen.Server.Domain.Enums;
+
+namespace Voxen.Server.Audits.Endpoints.GetAuditLogs;
+
+///
+/// An endpoint for retrieving audit logs.
+///
+public sealed class GetAuditLogsEndpoint(VoxenDbContext db) : EndpointWithoutRequest>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/audits");
+ Roles(nameof(ServerRole.Admin));
+ }
+
+ ///
+ public override async Task HandleAsync(CancellationToken ct)
+ {
+ var logs = await db.AuditLogs
+ .AsNoTracking()
+ .Include(a => a.User)
+ .OrderByDescending(a => a.CreatedAt)
+ .Select(a => new GetAuditLogsResponse
+ {
+ Id = a.Id,
+ UserId = a.UserId,
+ UserName = a.User.UserName,
+ Action = a.Action,
+ Category = a.Category,
+ EntityId = a.EntityId,
+ Changes = string.IsNullOrWhiteSpace(a.ChangesJson)
+ ? null
+ : JsonSerializer.Deserialize>(a.ChangesJson),
+ CreatedAt = a.CreatedAt
+ })
+ .ToListAsync(ct);
+
+ await Send.OkAsync(logs, ct);
+ }
+}
diff --git a/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsResponse.cs b/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsResponse.cs
new file mode 100644
index 0000000..a7a36e1
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Endpoints/GetAuditLogs/GetAuditLogsResponse.cs
@@ -0,0 +1,50 @@
+using Voxen.Server.Audits.Models;
+using Voxen.Server.Domain.Enums;
+
+namespace Voxen.Server.Audits.Endpoints.GetAuditLogs;
+
+///
+/// Represents the response structure for audit log data.
+///
+public class GetAuditLogsResponse
+{
+ ///
+ /// Gets or sets the unique identifier for the audit log.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// Gets or sets the identifier of the user associated with the audit log.
+ ///
+ public Guid UserId { get; set; }
+
+ ///
+ /// Gets or sets the name of the user associated with the audit log.
+ ///
+ public string? UserName { get; set; }
+
+ ///
+ /// Gets or sets the type of action performed in the audit log.
+ ///
+ public AuditAction Action { get; set; }
+
+ ///
+ /// Gets or sets the category of the action logged in the audit log.
+ ///
+ public AuditCategory Category { get; set; }
+
+ ///
+ /// Gets or sets the identifier of the entity affected by the audit log.
+ ///
+ public Guid? EntityId { get; set; }
+
+ ///
+ /// Gets or sets the deserialized list of changes (old/new values) for this audit log.
+ ///
+ public List? Changes { get; set; }
+
+ ///
+ /// Gets or sets the timestamp when the audit log was created.
+ ///
+ public DateTime CreatedAt { get; set; }
+}
diff --git a/src/modules/Voxen.Server.Audits/Extensions/ServiceCollectionExtensions.cs b/src/modules/Voxen.Server.Audits/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..bd63775
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,21 @@
+using Microsoft.Extensions.DependencyInjection;
+using Voxen.Server.Audits.Interfaces;
+using Voxen.Server.Audits.Services;
+
+namespace Voxen.Server.Audits.Extensions;
+
+///
+/// Provides extension methods for configuring Voxen Audits services in an .
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds the Voxen Audits services to the specified .
+ ///
+ public static IServiceCollection AddVoxenAudits(this IServiceCollection services)
+ {
+ services.AddScoped();
+
+ return services;
+ }
+}
diff --git a/src/modules/Voxen.Server.Audits/Interfaces/IAuditLogService.cs b/src/modules/Voxen.Server.Audits/Interfaces/IAuditLogService.cs
new file mode 100644
index 0000000..11b2178
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Interfaces/IAuditLogService.cs
@@ -0,0 +1,22 @@
+using Voxen.Server.Audits.Models;
+using Voxen.Server.Domain.Entities;
+using Voxen.Server.Domain.Enums;
+
+namespace Voxen.Server.Audits.Interfaces;
+
+///
+/// Defines the contract for an audit log service that handles logging of audit entries.
+///
+public interface IAuditLogService
+{
+ ///
+ /// Logs an audit entry to the database.
+ ///
+ /// The user who performed the action.
+ /// The type of action being logged.
+ /// The category of the action being logged.
+ /// The unique identifier of the entity associated with the audit log.
+ /// A collection of changes made to the entity, represented as objects.
+ /// A cancellation token used to cancel the asynchronous action if needed.
+ public Task LogAsync(User actor, AuditAction action, AuditCategory category, Guid entityId, IEnumerable changes, CancellationToken ct = default);
+}
diff --git a/src/modules/Voxen.Server.Audits/Models/AuditChange.cs b/src/modules/Voxen.Server.Audits/Models/AuditChange.cs
new file mode 100644
index 0000000..60345e2
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Models/AuditChange.cs
@@ -0,0 +1,22 @@
+namespace Voxen.Server.Audits.Models;
+
+///
+/// Represents a change made to an entity during an audit.
+///
+public class AuditChange
+{
+ ///
+ /// Gets or sets the name of the property that was changed during the audit.
+ ///
+ public string PropertyName { get; set; } = null!;
+
+ ///
+ /// Gets or sets the previous value of the property before the change was made.
+ ///
+ public string? OldValue { get; set; }
+
+ ///
+ /// Gets or sets the new value of the property after the change.
+ ///
+ public string NewValue { get; set; } = null!;
+}
diff --git a/src/modules/Voxen.Server.Audits/Services/AuditLogService.cs b/src/modules/Voxen.Server.Audits/Services/AuditLogService.cs
new file mode 100644
index 0000000..4ac32b4
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Services/AuditLogService.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using Voxen.Server.Audits.Interfaces;
+using Voxen.Server.Audits.Models;
+using Voxen.Server.Domain;
+using Voxen.Server.Domain.Entities;
+using Voxen.Server.Domain.Enums;
+
+namespace Voxen.Server.Audits.Services;
+
+///
+/// Provides functionality for logging audit entries to the database.
+///
+public class AuditLogService(VoxenDbContext db) : IAuditLogService
+{
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = false
+ };
+
+ ///
+ public async Task LogAsync(User actor, AuditAction action, AuditCategory category, Guid entityId, IEnumerable changes, CancellationToken ct = default)
+ {
+ var changeList = changes.ToList();
+ var audit = new Audit
+ {
+ UserId = actor.Id,
+ Action = action,
+ Category = category,
+ EntityId = entityId,
+ ChangesJson = changeList is { Count: > 0 }
+ ? JsonSerializer.Serialize(changeList, JsonOptions)
+ : null,
+ CreatedAt = DateTime.UtcNow
+ };
+
+ db.AuditLogs.Add(audit);
+ await db.SaveChangesAsync(ct);
+ }
+}
diff --git a/src/modules/Voxen.Server.Audits/Voxen.Server.Audits.csproj b/src/modules/Voxen.Server.Audits/Voxen.Server.Audits.csproj
new file mode 100644
index 0000000..c20b020
--- /dev/null
+++ b/src/modules/Voxen.Server.Audits/Voxen.Server.Audits.csproj
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/Voxen.Server.Domain/Entities/Audit.cs b/src/modules/Voxen.Server.Domain/Entities/Audit.cs
new file mode 100644
index 0000000..ccda6fa
--- /dev/null
+++ b/src/modules/Voxen.Server.Domain/Entities/Audit.cs
@@ -0,0 +1,49 @@
+using Voxen.Server.Domain.Enums;
+
+namespace Voxen.Server.Domain.Entities;
+
+///
+/// Represents an audit log entry within the Voxen server.
+///
+public class Audit
+{
+ ///
+ /// Unique identifier for the audit log entry.
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// The user who performed the action.
+ ///
+ public Guid UserId { get; set; }
+
+ ///
+ /// Navigation property for the user.
+ ///
+ public User User { get; set; } = null!;
+
+ ///
+ /// The action that was performed (Create, Update, Delete, etc.).
+ ///
+ public AuditAction Action { get; set; }
+
+ ///
+ /// The category/type of entity affected (Channel, User, Server, etc.).
+ ///
+ public AuditCategory Category { get; set; }
+
+ ///
+ /// The identifier of the affected entity (e.g., ChannelId, UserId).
+ ///
+ public Guid? EntityId { get; set; }
+
+ ///
+ /// Backing field for JSON storage in DB.
+ ///
+ public string? ChangesJson { get; set; }
+
+ ///
+ /// Timestamp when the action occurred (UTC).
+ ///
+ public DateTime CreatedAt { get; set; }
+}
diff --git a/src/modules/Voxen.Server.Domain/Enums/AuditAction.cs b/src/modules/Voxen.Server.Domain/Enums/AuditAction.cs
new file mode 100644
index 0000000..c3660ce
--- /dev/null
+++ b/src/modules/Voxen.Server.Domain/Enums/AuditAction.cs
@@ -0,0 +1,12 @@
+namespace Voxen.Server.Domain.Enums;
+
+///
+/// Represents the type of action performed in an audit log entry.
+///
+public enum AuditAction
+{
+ ///
+ /// Represents the creation of a new entity or record in the audit log.
+ ///
+ Create
+}
diff --git a/src/modules/Voxen.Server.Domain/Enums/AuditCategory.cs b/src/modules/Voxen.Server.Domain/Enums/AuditCategory.cs
new file mode 100644
index 0000000..80ace03
--- /dev/null
+++ b/src/modules/Voxen.Server.Domain/Enums/AuditCategory.cs
@@ -0,0 +1,17 @@
+namespace Voxen.Server.Domain.Enums;
+
+///
+/// Represents the category of an action logged in the audit system.
+///
+public enum AuditCategory
+{
+ ///
+ /// Represents a change to server-related properties logged in the audit system.
+ ///
+ Server,
+
+ ///
+ /// Represents a change to user-related properties logged in the audit system.
+ ///
+ User
+}
diff --git a/src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.Designer.cs b/src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.Designer.cs
similarity index 89%
rename from src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.Designer.cs
rename to src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.Designer.cs
index 0fe7393..8e26b39 100644
--- a/src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.Designer.cs
+++ b/src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.Designer.cs
@@ -11,7 +11,7 @@
namespace Voxen.Server.Domain.Migrations
{
[DbContext(typeof(VoxenDbContext))]
- [Migration("20260316123356_InitialCreate")]
+ [Migration("20260320110513_InitialCreate")]
partial class InitialCreate
{
///
@@ -146,6 +146,39 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.ToTable("AspNetUserTokens", (string)null);
});
+ modelBuilder.Entity("Voxen.Server.Domain.Entities.Audit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Action")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ChangesJson")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("EntityId")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AuditLogs");
+ });
+
modelBuilder.Entity("Voxen.Server.Domain.Entities.Channel", b =>
{
b.Property("Id")
@@ -340,6 +373,17 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
.IsRequired();
});
+ modelBuilder.Entity("Voxen.Server.Domain.Entities.Audit", b =>
+ {
+ b.HasOne("Voxen.Server.Domain.Entities.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
modelBuilder.Entity("Voxen.Server.Domain.Entities.Message", b =>
{
b.HasOne("Voxen.Server.Domain.Entities.Channel", "Channel")
diff --git a/src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.cs b/src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.cs
similarity index 90%
rename from src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.cs
rename to src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.cs
index bfab387..988370b 100644
--- a/src/modules/Voxen.Server.Domain/Migrations/20260316123356_InitialCreate.cs
+++ b/src/modules/Voxen.Server.Domain/Migrations/20260320110513_InitialCreate.cs
@@ -186,6 +186,29 @@ protected override void Up(MigrationBuilder migrationBuilder)
onDelete: ReferentialAction.Cascade);
});
+ migrationBuilder.CreateTable(
+ name: "AuditLogs",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ UserId = table.Column(type: "TEXT", nullable: false),
+ Action = table.Column(type: "TEXT", nullable: false),
+ Category = table.Column(type: "TEXT", nullable: false),
+ EntityId = table.Column(type: "TEXT", nullable: true),
+ ChangesJson = table.Column(type: "TEXT", nullable: true),
+ CreatedAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AuditLogs", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AuditLogs_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
migrationBuilder.CreateTable(
name: "Messages",
columns: table => new
@@ -250,6 +273,11 @@ protected override void Up(MigrationBuilder migrationBuilder)
column: "NormalizedUserName",
unique: true);
+ migrationBuilder.CreateIndex(
+ name: "IX_AuditLogs_UserId",
+ table: "AuditLogs",
+ column: "UserId");
+
migrationBuilder.CreateIndex(
name: "IX_Messages_ChannelId",
table: "Messages",
@@ -279,6 +307,9 @@ protected override void Down(MigrationBuilder migrationBuilder)
migrationBuilder.DropTable(
name: "AspNetUserTokens");
+ migrationBuilder.DropTable(
+ name: "AuditLogs");
+
migrationBuilder.DropTable(
name: "Messages");
diff --git a/src/modules/Voxen.Server.Domain/Migrations/VoxenDbContextModelSnapshot.cs b/src/modules/Voxen.Server.Domain/Migrations/VoxenDbContextModelSnapshot.cs
index e8d6771..e9e92bc 100644
--- a/src/modules/Voxen.Server.Domain/Migrations/VoxenDbContextModelSnapshot.cs
+++ b/src/modules/Voxen.Server.Domain/Migrations/VoxenDbContextModelSnapshot.cs
@@ -143,6 +143,39 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("AspNetUserTokens", (string)null);
});
+ modelBuilder.Entity("Voxen.Server.Domain.Entities.Audit", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Action")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ChangesJson")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("EntityId")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AuditLogs");
+ });
+
modelBuilder.Entity("Voxen.Server.Domain.Entities.Channel", b =>
{
b.Property("Id")
@@ -337,6 +370,17 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.IsRequired();
});
+ modelBuilder.Entity("Voxen.Server.Domain.Entities.Audit", b =>
+ {
+ b.HasOne("Voxen.Server.Domain.Entities.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
modelBuilder.Entity("Voxen.Server.Domain.Entities.Message", b =>
{
b.HasOne("Voxen.Server.Domain.Entities.Channel", "Channel")
diff --git a/src/modules/Voxen.Server.Domain/Services/SeedData.cs b/src/modules/Voxen.Server.Domain/Services/SeedData.cs
index 4d0a393..e25ec60 100644
--- a/src/modules/Voxen.Server.Domain/Services/SeedData.cs
+++ b/src/modules/Voxen.Server.Domain/Services/SeedData.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
+using System.Text.Json;
using Voxen.Server.Domain.Entities;
using Voxen.Server.Domain.Enums;
@@ -13,16 +14,24 @@ public static class SeedData
///
/// Initializes the database with default data asynchronously.
///
- /// The database context used to interact with the database.
+ /// The database context used to interact with the database.
/// The user manager used to create and manage user accounts.
/// A task that represents the asynchronous operation.
- public static async Task InitializeDatabaseAsync(VoxenDbContext context, UserManager userManager)
+ public static async Task InitializeDatabaseAsync(VoxenDbContext db, UserManager userManager)
{
- await context.Database.MigrateAsync();
- if (await context.Server.AnyAsync())
+ await db.Database.MigrateAsync();
+ if (await db.Server.AnyAsync())
return;
+
+ var adminId = await userManager.CreateAdmin(db);
+ db.CreateServer(adminId);
- var defaultServer = new Entities.Server
+ await db.SaveChangesAsync();
+ }
+
+ private static void CreateServer(this VoxenDbContext db, Guid adminId)
+ {
+ var server = new Entities.Server
{
Id = Guid.NewGuid(),
Name = "Voxen Server",
@@ -31,6 +40,28 @@ public static async Task InitializeDatabaseAsync(VoxenDbContext context, UserMan
LogoContentType = null
};
+ db.Server.Add(server);
+ db.AuditLogs.Add(new Audit
+ {
+ UserId = adminId,
+ Action = AuditAction.Create,
+ Category = AuditCategory.Server,
+ EntityId = server.Id,
+ ChangesJson = """
+ [
+ {
+ "PropertyName": "Name",
+ "OldValue": null,
+ "NewValue": "Voxen Server"
+ }
+ ]
+ """,
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
+ private static async Task CreateAdmin(this UserManager userManager, VoxenDbContext db)
+ {
var adminUser = new User
{
Id = Guid.NewGuid(),
@@ -38,7 +69,6 @@ public static async Task InitializeDatabaseAsync(VoxenDbContext context, UserMan
Role = ServerRole.Admin
};
- context.Server.Add(defaultServer);
var result = await userManager.CreateAsync(adminUser, "Password123!");
if (!result.Succeeded)
@@ -47,6 +77,29 @@ public static async Task InitializeDatabaseAsync(VoxenDbContext context, UserMan
$"Failed to create admin user: {string.Join(", ", result.Errors.Select(e => e.Description))}");
}
- await context.SaveChangesAsync();
+ db.AuditLogs.Add(new Audit
+ {
+ UserId = adminUser.Id,
+ Action = AuditAction.Create,
+ Category = AuditCategory.User,
+ EntityId = adminUser.Id,
+ ChangesJson = """
+ [
+ {
+ "PropertyName": "Name",
+ "OldValue": null,
+ "NewValue": "admin"
+ },
+ {
+ "PropertyName": "Role",
+ "OldValue": null,
+ "NewValue": "Admin"
+ }
+ ]
+ """,
+ CreatedAt = DateTime.UtcNow
+ });
+
+ return adminUser.Id;
}
}
diff --git a/src/modules/Voxen.Server.Domain/VoxenDbContext.cs b/src/modules/Voxen.Server.Domain/VoxenDbContext.cs
index e795148..b8d505b 100644
--- a/src/modules/Voxen.Server.Domain/VoxenDbContext.cs
+++ b/src/modules/Voxen.Server.Domain/VoxenDbContext.cs
@@ -32,6 +32,11 @@ public VoxenDbContext(DbContextOptions options) : base(options)
///
public DbSet Messages => Set();
+ ///
+ /// Gets the database set for audit logs.
+ ///
+ public DbSet AuditLogs => Set();
+
///
protected override void OnModelCreating(ModelBuilder builder)
{
@@ -48,5 +53,24 @@ protected override void OnModelCreating(ModelBuilder builder)
.WithMany(u => u.Messages)
.HasForeignKey(m => m.UserId)
.OnDelete(DeleteBehavior.Cascade);
+
+ builder.Entity(entity =>
+ {
+ entity.HasKey(a => a.Id);
+
+ entity.Property(a => a.Action)
+ .HasConversion();
+
+ entity.Property(a => a.Category)
+ .HasConversion();
+
+ entity.Property(a => a.ChangesJson)
+ .HasColumnType("TEXT");
+
+ entity.HasOne(a => a.User)
+ .WithMany()
+ .HasForeignKey(a => a.UserId)
+ .OnDelete(DeleteBehavior.Restrict);
+ });
}
}