Add transfer tracking and secure admin access
This commit is contained in:
@@ -23,6 +23,9 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
|
||||
public DbSet<PushMessage> PushMessages => Set<PushMessage>();
|
||||
public DbSet<PushDelivery> PushDeliveries => Set<PushDelivery>();
|
||||
public DbSet<BudgetNotificationReceipt> BudgetNotificationReceipts => Set<BudgetNotificationReceipt>();
|
||||
public DbSet<AdminUser> AdminUsers => Set<AdminUser>();
|
||||
public DbSet<AdminSession> AdminSessions => Set<AdminSession>();
|
||||
public DbSet<AdminAuditLog> AdminAuditLogs => Set<AdminAuditLog>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder b)
|
||||
{
|
||||
@@ -54,8 +57,16 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
|
||||
e.HasIndex(x => new { x.LedgerId, x.OccurredAt });
|
||||
e.HasIndex(x => new { x.UserId, x.IsDeleted });
|
||||
e.Property(x => x.ClientRequestId).HasMaxLength(64);
|
||||
e.HasIndex(x => new { x.UserId, x.ClientRequestId }).IsUnique();
|
||||
e.HasQueryFilter(x => !x.IsDeleted); // 软删除全局过滤
|
||||
e.HasIndex(x => new { x.UserId, x.ClientRequestId }).IsUnique();
|
||||
e.Property(x => x.Counterparty).HasMaxLength(100);
|
||||
e.Property(x => x.Provider).HasMaxLength(24);
|
||||
e.Property(x => x.ProviderTransactionId).HasMaxLength(128);
|
||||
e.Property(x => x.RecognitionOccurrenceId).HasMaxLength(64);
|
||||
e.Property(x => x.EvidenceFingerprint).HasMaxLength(64);
|
||||
e.Property(x => x.RecognitionConfidence).HasMaxLength(24);
|
||||
e.HasIndex(x => new { x.UserId, x.Provider, x.ProviderTransactionId }).IsUnique();
|
||||
e.HasIndex(x => new { x.UserId, x.RecognitionOccurrenceId }).IsUnique();
|
||||
e.HasQueryFilter(x => !x.IsDeleted); // 软删除全局过滤
|
||||
});
|
||||
|
||||
b.Entity<Category>(e => e.Property(x => x.ColorKey).HasMaxLength(32));
|
||||
@@ -142,6 +153,40 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
|
||||
.HasForeignKey(x => x.BudgetId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
b.Entity<AdminUser>(e =>
|
||||
{
|
||||
e.Property(x => x.Username).HasMaxLength(64);
|
||||
e.Property(x => x.PasswordHash).HasMaxLength(128);
|
||||
e.Property(x => x.Role).HasMaxLength(24);
|
||||
e.HasIndex(x => x.Username).IsUnique();
|
||||
e.HasIndex(x => new { x.IsActive, x.Role });
|
||||
});
|
||||
|
||||
b.Entity<AdminSession>(e =>
|
||||
{
|
||||
e.Property(x => x.TokenHash).HasMaxLength(64);
|
||||
e.Property(x => x.CsrfTokenHash).HasMaxLength(64);
|
||||
e.Property(x => x.IpAddress).HasMaxLength(64);
|
||||
e.Property(x => x.UserAgent).HasMaxLength(300);
|
||||
e.HasIndex(x => x.TokenHash).IsUnique();
|
||||
e.HasIndex(x => new { x.AdminUserId, x.RevokedAt, x.ExpiresAt });
|
||||
e.HasOne(x => x.AdminUser).WithMany(x => x.Sessions)
|
||||
.HasForeignKey(x => x.AdminUserId).OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
b.Entity<AdminAuditLog>(e =>
|
||||
{
|
||||
e.Property(x => x.Username).HasMaxLength(64);
|
||||
e.Property(x => x.Action).HasMaxLength(80);
|
||||
e.Property(x => x.Resource).HasMaxLength(120);
|
||||
e.Property(x => x.HttpMethod).HasMaxLength(12);
|
||||
e.Property(x => x.Path).HasMaxLength(300);
|
||||
e.Property(x => x.Detail).HasMaxLength(1000);
|
||||
e.Property(x => x.IpAddress).HasMaxLength(64);
|
||||
e.HasIndex(x => x.CreatedAt);
|
||||
e.HasIndex(x => new { x.AdminUserId, x.CreatedAt });
|
||||
});
|
||||
|
||||
// MySQL DATETIME has no timezone metadata. Preserve stored UTC wall-clock
|
||||
// values and restore DateTimeKind.Utc whenever EF materializes them.
|
||||
var utcDateTimeConverter = new ValueConverter<DateTime, DateTime>(
|
||||
|
||||
Reference in New Issue
Block a user