feat(processing): persist safe atomic operation history

This commit is contained in:
cesnimda
2026-10-28 15:54:37 +01:00
parent 5f261083f3
commit 1f87301ee9
17 changed files with 501 additions and 32 deletions
@@ -0,0 +1,50 @@
using JobTrackerApi.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace JobTrackerApi.Migrations;
[DbContext(typeof(JobTrackerContext))]
[Migration("20261028090000_AddUserOperationEvents")]
public sealed class AddUserOperationEvents : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (ActiveProvider?.Contains("MySql", StringComparison.OrdinalIgnoreCase) == true)
migrationBuilder.Sql("""
CREATE TABLE `UserOperationEvents` (
`Id` bigint NOT NULL AUTO_INCREMENT,
`OperationId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL,
`OwnerUserId` varchar(255) NOT NULL,
`AtUtc` datetime(6) NOT NULL,
`Kind` varchar(40) NOT NULL,
`Status` varchar(32) NOT NULL,
`Stage` varchar(64) NULL,
`Attempt` int NOT NULL,
`FailureCode` varchar(64) NULL,
CONSTRAINT `PK_UserOperationEvents` PRIMARY KEY (`Id`),
CONSTRAINT `FK_UserOperationEvents_UserOperations_OperationId` FOREIGN KEY (`OperationId`) REFERENCES `UserOperations` (`Id`) ON DELETE CASCADE
) CHARACTER SET=utf8mb4;
""");
else
migrationBuilder.CreateTable("UserOperationEvents", table => new
{
Id = table.Column<long>(type: "INTEGER", nullable: false).Annotation("Sqlite:Autoincrement", true),
OperationId = table.Column<Guid>(type: "TEXT", nullable: false),
OwnerUserId = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
AtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
Kind = table.Column<string>(type: "TEXT", maxLength: 40, nullable: false),
Status = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false),
Stage = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
Attempt = table.Column<int>(type: "INTEGER", nullable: false),
FailureCode = table.Column<string>(type: "TEXT", maxLength: 64, nullable: true),
}, constraints: table =>
{
table.PrimaryKey("PK_UserOperationEvents", x => x.Id);
table.ForeignKey("FK_UserOperationEvents_UserOperations_OperationId", x => x.OperationId, "UserOperations", "Id", onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex("IX_UserOperationEvents_OperationId_Id", "UserOperationEvents", new[] { "OperationId", "Id" });
migrationBuilder.CreateIndex("IX_UserOperationEvents_AtUtc", "UserOperationEvents", "AtUtc");
}
protected override void Down(MigrationBuilder migrationBuilder) => migrationBuilder.DropTable("UserOperationEvents");
}
@@ -2265,6 +2265,24 @@ namespace JobTrackerApi.Migrations
b.ToTable("TwoFactorRecoveryCodes");
});
modelBuilder.Entity("JobTrackerApi.Models.UserOperationEvent", b =>
{
b.Property<long>("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER");
b.Property<Guid>("OperationId").HasColumnType("TEXT");
b.Property<string>("OwnerUserId").IsRequired().HasMaxLength(255).HasColumnType("TEXT");
b.Property<DateTime>("AtUtc").HasColumnType("TEXT");
b.Property<string>("Kind").IsRequired().HasMaxLength(40).HasColumnType("TEXT");
b.Property<string>("Status").IsRequired().HasMaxLength(32).HasColumnType("TEXT");
b.Property<string>("Stage").HasMaxLength(64).HasColumnType("TEXT");
b.Property<int>("Attempt").HasColumnType("INTEGER");
b.Property<string>("FailureCode").HasMaxLength(64).HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("OperationId", "Id");
b.HasIndex("AtUtc");
b.ToTable("UserOperationEvents");
b.HasOne("JobTrackerApi.Models.UserOperation", null).WithMany().HasForeignKey("OperationId").OnDelete(DeleteBehavior.Cascade).IsRequired();
});
modelBuilder.Entity("JobTrackerApi.Models.UserNotification", b =>
{
b.Property<Guid>("Id")