fix: align backend APIs and upload flow

This commit is contained in:
2026-09-15 14:10:55 +08:00
parent 32177a7293
commit 53e6195938
149 changed files with 4791 additions and 435 deletions
+112
View File
@@ -0,0 +1,112 @@
using Microsoft.EntityFrameworkCore;
namespace IM.Admin.Data;
public sealed class AdminAccount
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Account { get; set; } = "";
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public string PasswordHash { get; set; } = "";
public string Role { get; set; } = "reviewer";
public bool Enabled { get; set; } = true;
public string Stamp { get; set; } = Guid.NewGuid().ToString("N");
public int FailedAttempts { get; set; }
public DateTime? LockedUntil { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
public sealed class SystemSetting
{
public string Id { get; set; } = "";
public long Version { get; set; }
public string Value { get; set; } = "{}";
public string? Draft { get; set; }
public long? TestedVersion { get; set; }
public string Secret { get; set; } = "";
public string? DraftSecret { get; set; }
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
public sealed class Report
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid ReporterId { get; set; }
public Guid TargetId { get; set; }
public string TargetName { get; set; } = "";
public string Type { get; set; } = "user";
public string Reason { get; set; } = "";
public string Description { get; set; } = "";
public string Evidence { get; set; } = "[]";
public string Status { get; set; } = "待处理";
public Guid? AssigneeId { get; set; }
public string? Result { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? ClosedAt { get; set; }
public long Version { get; set; }
}
// A durable outbox command: both the request and the pending report update commit together.
public sealed class Operation
{
public Guid Id { get; set; }
public Guid ActorId { get; set; }
public string ActorName { get; set; } = "";
public Guid TargetId { get; set; }
public string Type { get; set; } = "";
public string Action { get; set; } = "";
public string Reason { get; set; } = "";
public Guid? ReportId { get; set; }
public string Status { get; set; } = "pending";
public int Attempts { get; set; }
public string? Error { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime NextAttemptAt { get; set; } = DateTime.UtcNow;
public DateTime? CompletedAt { get; set; }
}
public sealed class AuditRecord
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid ActorId { get; set; }
public string ActorName { get; set; } = "";
public string Action { get; set; } = "";
public string TargetId { get; set; } = "";
public string TargetName { get; set; } = "";
public string Before { get; set; } = "";
public string After { get; set; } = "";
public string Reason { get; set; } = "";
public Guid? ReportId { get; set; }
public Guid? OperationId { get; set; }
public string Result { get; set; } = "成功";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
public sealed class PasswordReset
{
public string Id { get; set; } = "";
public Guid AccountId { get; set; }
public DateTime ExpiresAt { get; set; }
}
public sealed class AdminDb(DbContextOptions<AdminDb> options) : DbContext(options)
{
public DbSet<AdminAccount> Accounts => Set<AdminAccount>();
public DbSet<SystemSetting> Settings => Set<SystemSetting>();
public DbSet<Report> Reports => Set<Report>();
public DbSet<Operation> Operations => Set<Operation>();
public DbSet<AuditRecord> Audit => Set<AuditRecord>();
public DbSet<PasswordReset> Resets => Set<PasswordReset>();
protected override void OnModelCreating(ModelBuilder b)
{
b.Entity<AdminAccount>().ToTable("admin_accounts").HasIndex(x => x.Account).IsUnique();
b.Entity<AdminAccount>().Property(x => x.Account).HasMaxLength(100);
b.Entity<AdminAccount>().Property(x => x.Stamp).IsConcurrencyToken();
b.Entity<SystemSetting>().ToTable("admin_settings").Property(x => x.Id).HasMaxLength(64);
b.Entity<SystemSetting>().Property(x => x.Version).IsConcurrencyToken();
b.Entity<Report>().ToTable("admin_reports").HasIndex(x => new { x.ReporterId, x.CreatedAt });
b.Entity<Report>().HasIndex(x => new { x.Status, x.CreatedAt });
b.Entity<Report>().Property(x => x.Status).HasMaxLength(30);
b.Entity<Report>().Property(x => x.Version).IsConcurrencyToken();
b.Entity<Operation>().ToTable("admin_operations").HasIndex(x => new { x.Status, x.NextAttemptAt });
b.Entity<Operation>().Property(x => x.Status).HasMaxLength(30);
b.Entity<AuditRecord>().ToTable("admin_audit").HasIndex(x => x.CreatedAt);
b.Entity<AuditRecord>().HasIndex(x => x.OperationId).IsUnique();
b.Entity<PasswordReset>().ToTable("admin_password_resets").Property(x => x.Id).HasMaxLength(64);
}
}
+9
View File
@@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace IM.Admin.Data;
public sealed class DesignTimeFactory : IDesignTimeDbContextFactory<AdminDb>
{
public AdminDb CreateDbContext(string[] args) => new(new DbContextOptionsBuilder<AdminDb>()
.UseMySql("Server=localhost;Database=im_admin;User=migration;Password=design-time-only", new MySqlServerVersion(new Version(8, 0, 0))).Options);
}
@@ -0,0 +1,314 @@
// <auto-generated />
using System;
using IM.Admin.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Admin.WebApi.Data.Migrations
{
[DbContext(typeof(AdminDb))]
[Migration("20260915004233_InitialAdmin")]
partial class InitialAdmin
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("IM.Admin.Data.AdminAccount", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Account")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Enabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FailedAttempts")
.HasColumnType("int");
b.Property<DateTime?>("LockedUntil")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stamp")
.IsConcurrencyToken()
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Account")
.IsUnique();
b.ToTable("admin_accounts", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.AuditRecord", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ActorId")
.HasColumnType("char(36)");
b.Property<string>("ActorName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("After")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Before")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("OperationId")
.HasColumnType("char(36)");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid?>("ReportId")
.HasColumnType("char(36)");
b.Property<string>("Result")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TargetId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TargetName")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("OperationId")
.IsUnique();
b.ToTable("admin_audit", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.Operation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ActorId")
.HasColumnType("char(36)");
b.Property<string>("ActorName")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Attempts")
.HasColumnType("int");
b.Property<DateTime?>("CompletedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Error")
.HasColumnType("longtext");
b.Property<DateTime>("NextAttemptAt")
.HasColumnType("datetime(6)");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid?>("ReportId")
.HasColumnType("char(36)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<Guid>("TargetId")
.HasColumnType("char(36)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Status", "NextAttemptAt");
b.ToTable("admin_operations", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.PasswordReset", b =>
{
b.Property<string>("Id")
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<Guid>("AccountId")
.HasColumnType("char(36)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.ToTable("admin_password_resets", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.Report", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid?>("AssigneeId")
.HasColumnType("char(36)");
b.Property<DateTime?>("ClosedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Evidence")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ReporterId")
.HasColumnType("char(36)");
b.Property<string>("Result")
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<Guid>("TargetId")
.HasColumnType("char(36)");
b.Property<string>("TargetName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("ReporterId", "CreatedAt");
b.HasIndex("Status", "CreatedAt");
b.ToTable("admin_reports", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.SystemSetting", b =>
{
b.Property<string>("Id")
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<string>("Draft")
.HasColumnType("longtext");
b.Property<string>("DraftSecret")
.HasColumnType("longtext");
b.Property<string>("Secret")
.IsRequired()
.HasColumnType("longtext");
b.Property<long?>("TestedVersion")
.HasColumnType("bigint");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint");
b.HasKey("Id");
b.ToTable("admin_settings", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,234 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Admin.WebApi.Data.Migrations
{
/// <inheritdoc />
public partial class InitialAdmin : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_accounts",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Account = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Email = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PasswordHash = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Role = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
Stamp = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FailedAttempts = table.Column<int>(type: "int", nullable: false),
LockedUntil = table.Column<DateTime>(type: "datetime(6)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_accounts", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_audit",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ActorId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ActorName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Action = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TargetId = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TargetName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Before = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
After = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Reason = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ReportId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
OperationId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Result = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_audit", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_operations",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ActorId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ActorName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TargetId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Type = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Action = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Reason = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ReportId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Status = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Attempts = table.Column<int>(type: "int", nullable: false),
Error = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
NextAttemptAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
CompletedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_operations", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_password_resets",
columns: table => new
{
Id = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
AccountId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ExpiresAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_password_resets", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_reports",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ReporterId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TargetId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TargetName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Type = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Reason = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Description = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Evidence = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
AssigneeId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Result = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ClosedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
Version = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_reports", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "admin_settings",
columns: table => new
{
Id = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Version = table.Column<long>(type: "bigint", nullable: false),
Value = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Draft = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
TestedVersion = table.Column<long>(type: "bigint", nullable: true),
Secret = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
DraftSecret = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_settings", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_admin_accounts_Account",
table: "admin_accounts",
column: "Account",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_admin_audit_CreatedAt",
table: "admin_audit",
column: "CreatedAt");
migrationBuilder.CreateIndex(
name: "IX_admin_audit_OperationId",
table: "admin_audit",
column: "OperationId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_admin_operations_Status_NextAttemptAt",
table: "admin_operations",
columns: new[] { "Status", "NextAttemptAt" });
migrationBuilder.CreateIndex(
name: "IX_admin_reports_ReporterId_CreatedAt",
table: "admin_reports",
columns: new[] { "ReporterId", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_admin_reports_Status_CreatedAt",
table: "admin_reports",
columns: new[] { "Status", "CreatedAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "admin_accounts");
migrationBuilder.DropTable(
name: "admin_audit");
migrationBuilder.DropTable(
name: "admin_operations");
migrationBuilder.DropTable(
name: "admin_password_resets");
migrationBuilder.DropTable(
name: "admin_reports");
migrationBuilder.DropTable(
name: "admin_settings");
}
}
}
@@ -0,0 +1,311 @@
// <auto-generated />
using System;
using IM.Admin.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Admin.WebApi.Data.Migrations
{
[DbContext(typeof(AdminDb))]
partial class AdminDbModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("IM.Admin.Data.AdminAccount", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Account")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("varchar(100)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Enabled")
.HasColumnType("tinyint(1)");
b.Property<int>("FailedAttempts")
.HasColumnType("int");
b.Property<DateTime?>("LockedUntil")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stamp")
.IsConcurrencyToken()
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Account")
.IsUnique();
b.ToTable("admin_accounts", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.AuditRecord", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ActorId")
.HasColumnType("char(36)");
b.Property<string>("ActorName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("After")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Before")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("OperationId")
.HasColumnType("char(36)");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid?>("ReportId")
.HasColumnType("char(36)");
b.Property<string>("Result")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TargetId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TargetName")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("OperationId")
.IsUnique();
b.ToTable("admin_audit", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.Operation", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ActorId")
.HasColumnType("char(36)");
b.Property<string>("ActorName")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Attempts")
.HasColumnType("int");
b.Property<DateTime?>("CompletedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Error")
.HasColumnType("longtext");
b.Property<DateTime>("NextAttemptAt")
.HasColumnType("datetime(6)");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid?>("ReportId")
.HasColumnType("char(36)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<Guid>("TargetId")
.HasColumnType("char(36)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Status", "NextAttemptAt");
b.ToTable("admin_operations", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.PasswordReset", b =>
{
b.Property<string>("Id")
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<Guid>("AccountId")
.HasColumnType("char(36)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.ToTable("admin_password_resets", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.Report", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid?>("AssigneeId")
.HasColumnType("char(36)");
b.Property<DateTime?>("ClosedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Evidence")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("ReporterId")
.HasColumnType("char(36)");
b.Property<string>("Result")
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(30)
.HasColumnType("varchar(30)");
b.Property<Guid>("TargetId")
.HasColumnType("char(36)");
b.Property<string>("TargetName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("ReporterId", "CreatedAt");
b.HasIndex("Status", "CreatedAt");
b.ToTable("admin_reports", (string)null);
});
modelBuilder.Entity("IM.Admin.Data.SystemSetting", b =>
{
b.Property<string>("Id")
.HasMaxLength(64)
.HasColumnType("varchar(64)");
b.Property<string>("Draft")
.HasColumnType("longtext");
b.Property<string>("DraftSecret")
.HasColumnType("longtext");
b.Property<string>("Secret")
.IsRequired()
.HasColumnType("longtext");
b.Property<long?>("TestedVersion")
.HasColumnType("bigint");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("Version")
.IsConcurrencyToken()
.HasColumnType("bigint");
b.HasKey("Id");
b.ToTable("admin_settings", (string)null);
});
#pragma warning restore 612, 618
}
}
}