190 lines
12 KiB
C#
190 lines
12 KiB
C#
using ElectionSystem.Api.Infrastructure.Auditing;
|
|
using ElectionSystem.Api.Modules.Identity.Domain;
|
|
using ElectionSystem.Api.Modules.Organization.Domain;
|
|
using ElectionSystem.Api.Modules.Voters.Domain;
|
|
using ElectionSystem.Api.Modules.Candidates.Domain;
|
|
using ElectionSystem.Api.Modules.Election.Domain;
|
|
using ElectionSystem.Api.Modules.OfflineCounting.Domain;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Identity;
|
|
|
|
namespace ElectionSystem.Api.Infrastructure.Persistence;
|
|
|
|
public sealed class ElectionDbContext(DbContextOptions<ElectionDbContext> options) : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>(options)
|
|
{
|
|
public DbSet<OrganizationUnit> OrganizationUnits => Set<OrganizationUnit>();
|
|
public DbSet<AdministrativeRegion> AdministrativeRegions => Set<AdministrativeRegion>();
|
|
public DbSet<UserRegionalRoleAssignment> UserRegionalRoleAssignments => Set<UserRegionalRoleAssignment>();
|
|
public DbSet<CountingStation> CountingStations => Set<CountingStation>();
|
|
public DbSet<UserCountingStationAssignment> UserCountingStationAssignments => Set<UserCountingStationAssignment>();
|
|
public DbSet<VoterProfile> Voters => Set<VoterProfile>();
|
|
public DbSet<VoterRegistrationAggregate> VoterRegistrationAggregates => Set<VoterRegistrationAggregate>();
|
|
public DbSet<CandidateProfile> Candidates => Set<CandidateProfile>();
|
|
public DbSet<ElectionActivity> ElectionActivities => Set<ElectionActivity>();
|
|
public DbSet<ElectionContest> ElectionContests => Set<ElectionContest>();
|
|
public DbSet<ElectionOption> ElectionOptions => Set<ElectionOption>();
|
|
public DbSet<ElectionCountingStation> ElectionCountingStations => Set<ElectionCountingStation>();
|
|
public DbSet<OfflineCountingSession> OfflineCountingSessions => Set<OfflineCountingSession>();
|
|
public DbSet<OfflineCountSubmission> OfflineCountSubmissions => Set<OfflineCountSubmission>();
|
|
public DbSet<OfflineCountTally> OfflineCountTallies => Set<OfflineCountTally>();
|
|
public DbSet<AuditEntry> AuditEntries => Set<AuditEntry>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
modelBuilder.Entity<ApplicationUser>(entity =>
|
|
{
|
|
entity.ToTable("identity_users");
|
|
entity.Property(x => x.UserName).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.DisplayName).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.ExternalSubject).HasMaxLength(256);
|
|
entity.HasIndex(x => x.UserName).IsUnique();
|
|
entity.HasIndex(x => x.ExternalSubject).IsUnique();
|
|
});
|
|
modelBuilder.Entity<ApplicationRole>().ToTable("identity_roles");
|
|
modelBuilder.Entity<IdentityUserRole<Guid>>().ToTable("identity_user_roles");
|
|
modelBuilder.Entity<IdentityUserClaim<Guid>>().ToTable("identity_user_claims");
|
|
modelBuilder.Entity<IdentityUserLogin<Guid>>().ToTable("identity_user_logins");
|
|
modelBuilder.Entity<IdentityUserToken<Guid>>().ToTable("identity_user_tokens");
|
|
modelBuilder.Entity<IdentityRoleClaim<Guid>>().ToTable("identity_role_claims");
|
|
modelBuilder.Entity<AdministrativeRegion>(entity =>
|
|
{
|
|
entity.ToTable("administrative_regions");
|
|
entity.Property(x => x.Code).HasMaxLength(32).IsRequired();
|
|
entity.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
|
entity.Property(x => x.Source).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.SourceVersion).HasMaxLength(64).IsRequired();
|
|
entity.HasIndex(x => new { x.Code, x.SourceVersion }).IsUnique();
|
|
entity.HasIndex(x => new { x.ParentId, x.Level, x.IsActive });
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.ParentId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<UserRegionalRoleAssignment>(entity =>
|
|
{
|
|
entity.ToTable("identity_user_regional_roles");
|
|
entity.HasIndex(x => new { x.UserId, x.Role, x.RegionId }).IsUnique();
|
|
entity.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<CountingStation>(entity =>
|
|
{
|
|
entity.ToTable("counting_stations");
|
|
entity.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
|
entity.Property(x => x.Code).HasMaxLength(64).IsRequired();
|
|
entity.HasIndex(x => x.Code).IsUnique();
|
|
entity.HasIndex(x => x.RegionId);
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<UserCountingStationAssignment>(entity =>
|
|
{
|
|
entity.ToTable("identity_user_counting_stations");
|
|
entity.HasIndex(x => new { x.UserId, x.CountingStationId }).IsUnique();
|
|
entity.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade);
|
|
entity.HasOne<CountingStation>().WithMany().HasForeignKey(x => x.CountingStationId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
modelBuilder.Entity<VoterProfile>(entity =>
|
|
{
|
|
entity.ToTable("voter_profiles");
|
|
entity.Property(x => x.VoterNumber).HasMaxLength(64).IsRequired();
|
|
entity.Property(x => x.DisplayName).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.DisabledReason).HasMaxLength(500);
|
|
entity.HasIndex(x => x.VoterNumber).IsUnique();
|
|
entity.HasIndex(x => new { x.RegionId, x.Status, x.DisplayName });
|
|
entity.HasIndex(x => x.UserId).IsUnique();
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
entity.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<VoterRegistrationAggregate>(entity =>
|
|
{
|
|
entity.ToTable("voter_registration_aggregates");
|
|
entity.Property(x => x.Reason).HasMaxLength(500).IsRequired();
|
|
entity.HasIndex(x => x.RegionId).IsUnique();
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
entity.HasOne<ApplicationUser>().WithMany().HasForeignKey(x => x.SetByUserId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<CandidateProfile>(entity =>
|
|
{
|
|
entity.ToTable("candidate_profiles");
|
|
entity.Property(x => x.DisplayName).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.NominationSummary).HasMaxLength(4000);
|
|
entity.Property(x => x.StatusReason).HasMaxLength(500);
|
|
entity.HasIndex(x => new { x.RegionId, x.Status, x.DisplayName });
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<OrganizationUnit>(entity =>
|
|
{
|
|
entity.ToTable("organization_units");
|
|
entity.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
|
entity.Property(x => x.Code).HasMaxLength(64).IsRequired();
|
|
entity.HasIndex(x => x.Code).IsUnique();
|
|
entity.HasOne<OrganizationUnit>().WithMany().HasForeignKey(x => x.ParentId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<ElectionActivity>(entity =>
|
|
{
|
|
entity.ToTable("election_activities");
|
|
entity.Property(x => x.Name).HasMaxLength(200).IsRequired();
|
|
entity.Property(x => x.Description).HasMaxLength(4000);
|
|
entity.HasIndex(x => new { x.RegionId, x.Status, x.VotingEndsAtUtc });
|
|
entity.HasOne<AdministrativeRegion>().WithMany().HasForeignKey(x => x.RegionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<ElectionContest>(entity =>
|
|
{
|
|
entity.ToTable("election_contests");
|
|
entity.Property(x => x.Title).HasMaxLength(200).IsRequired();
|
|
entity.Property(x => x.RequiredSupportPercent).HasPrecision(5, 2);
|
|
entity.HasIndex(x => new { x.ElectionActivityId, x.SortOrder }).IsUnique();
|
|
entity.HasOne<ElectionActivity>().WithMany().HasForeignKey(x => x.ElectionActivityId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
modelBuilder.Entity<ElectionOption>(entity =>
|
|
{
|
|
entity.ToTable("election_options");
|
|
entity.Property(x => x.Label).HasMaxLength(200).IsRequired();
|
|
entity.HasIndex(x => new { x.ElectionContestId, x.SortOrder }).IsUnique();
|
|
entity.HasIndex(x => new { x.ElectionContestId, x.CandidateId }).IsUnique();
|
|
entity.HasOne<ElectionContest>().WithMany().HasForeignKey(x => x.ElectionContestId).OnDelete(DeleteBehavior.Cascade);
|
|
entity.HasOne<CandidateProfile>().WithMany().HasForeignKey(x => x.CandidateId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<ElectionCountingStation>(entity =>
|
|
{
|
|
entity.ToTable("election_counting_stations");
|
|
entity.HasKey(x => new { x.ElectionActivityId, x.CountingStationId });
|
|
entity.HasOne<ElectionActivity>().WithMany().HasForeignKey(x => x.ElectionActivityId).OnDelete(DeleteBehavior.Cascade);
|
|
entity.HasOne<CountingStation>().WithMany().HasForeignKey(x => x.CountingStationId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<OfflineCountingSession>(entity =>
|
|
{
|
|
entity.ToTable("offline_counting_sessions");
|
|
entity.Property(x => x.CorrectionReason).HasMaxLength(1000);
|
|
entity.HasIndex(x => new { x.ElectionActivityId, x.CountingStationId, x.Version }).IsUnique();
|
|
entity.HasIndex(x => new { x.ElectionActivityId, x.CountingStationId, x.Status });
|
|
entity.HasOne<ElectionActivity>().WithMany().HasForeignKey(x => x.ElectionActivityId).OnDelete(DeleteBehavior.Restrict);
|
|
entity.HasOne<CountingStation>().WithMany().HasForeignKey(x => x.CountingStationId).OnDelete(DeleteBehavior.Restrict);
|
|
entity.HasOne<OfflineCountingSession>().WithMany().HasForeignKey(x => x.ReplacesSessionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<OfflineCountSubmission>(entity =>
|
|
{
|
|
entity.ToTable("offline_count_submissions");
|
|
entity.HasIndex(x => new { x.OfflineCountingSessionId, x.EnteredByUserId }).IsUnique();
|
|
entity.HasOne<OfflineCountingSession>().WithMany().HasForeignKey(x => x.OfflineCountingSessionId).OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
modelBuilder.Entity<OfflineCountTally>(entity =>
|
|
{
|
|
entity.ToTable("offline_count_tallies");
|
|
entity.HasIndex(x => new { x.OfflineCountSubmissionId, x.ElectionContestId, x.ElectionOptionId }).IsUnique();
|
|
entity.HasOne<OfflineCountSubmission>().WithMany().HasForeignKey(x => x.OfflineCountSubmissionId).OnDelete(DeleteBehavior.Cascade);
|
|
entity.HasOne<ElectionContest>().WithMany().HasForeignKey(x => x.ElectionContestId).OnDelete(DeleteBehavior.Restrict);
|
|
entity.HasOne<ElectionOption>().WithMany().HasForeignKey(x => x.ElectionOptionId).OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
modelBuilder.Entity<AuditEntry>(entity =>
|
|
{
|
|
entity.ToTable("audit_entries");
|
|
entity.Property(x => x.Action).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.ResourceType).HasMaxLength(128).IsRequired();
|
|
entity.Property(x => x.TraceId).HasMaxLength(128);
|
|
entity.Property(x => x.ActorId).HasMaxLength(128);
|
|
entity.Property(x => x.MetadataJson).HasColumnType("json");
|
|
entity.HasIndex(x => new { x.OccurredAtUtc, x.ResourceType });
|
|
});
|
|
}
|
|
}
|