using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ValueGeneration; using Modules.User.Database.Database.Entities; namespace Modules.User.Database.Database; public class UserDbContext : DbContext { public DbSet Accounts { get; set; } public DbSet Users { get; set; } public DbSet Sessions { get; set; } public DbSet Roles { get; set; } public DbSet AccountRoles { get; set; } public DbSet Permissions { get; set; } public DbSet AccountPermissions { get; set; } public UserDbContext(DbContextOptions options) : base(options) { } protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder.Properties().HaveConversion(); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(q => { q.ToTable("Users"); q.HasKey(x => x.Id); q.Property(x => x.NickName).IsRequired().HasMaxLength(32); q.Property(x => x.FirstName).HasMaxLength(32); q.Property(x => x.Patronymic).HasMaxLength(64); q.Property(x => x.LastName).HasMaxLength(128); q.Property(x => x.AvatarId).HasMaxLength(128); q.HasOne(x => x.Account) .WithOne(x => x.User) .HasForeignKey(x => x.UserId); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); q.OwnsOne(x => x.RegionalSettings); }); modelBuilder.Entity() .Navigation(x => x.RegionalSettings).IsRequired(); //fix optional dependent modelBuilder.Entity(q => { q.ToTable("Accounts"); q.HasKey(x => x.Id); q.Property(x => x.Email).IsRequired().HasMaxLength(128); q.Property(x => x.HashedPassword).IsRequired().HasMaxLength(256); q.HasMany(x => x.Sessions) .WithOne() .HasForeignKey(x => x.AccountId) .OnDelete(DeleteBehavior.Cascade); q.Property(x => x.CreationDate) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); }); modelBuilder.Entity(q => { //q.Property(q => q.DeviceInfo).HasMaxLength(128); //q.Property(q => q.UserAgent).HasMaxLength(128); q.OwnsOne(x => x.ClientInfo, x => { x.Property(z => z.UserAgent).HasMaxLength(256); x.Property(z => z.Country).HasMaxLength(96); x.Property(z => z.Region).HasMaxLength(128); }); q.Property(x => x.RefreshToken).IsRequired().HasMaxLength(172); //128 bytes to base64 // q.Property(x => x.ExpiredDate).HasDefaultValueSql("'0001-01-01 00:00:00+00'").ValueGeneratedOnAdd(); q.Property(x => x.ExpiredDate).HasDefaultValueSql("now()").ValueGeneratedOnAdd(); q.Property(x => x.LastUpdate).HasDefaultValueSql("now()").ValueGeneratedOnAdd(); }); modelBuilder.Entity(q => { q.ToTable("Permissions"); q.HasKey(x => x.Id); q.Property(x => x.Code).IsRequired().HasMaxLength(128); q.HasIndex(x => x.Code).IsUnique(); q.Property(x => x.Name).IsRequired().HasMaxLength(64); q.Property(x => x.Description).HasMaxLength(512); q.Property(x => x.CreationDate) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); }); modelBuilder.Entity(q => { q.ToTable("Roles"); q.HasKey(x => x.Id); q.Property(x => x.Code).IsRequired().HasMaxLength(64); q.HasIndex(x => x.Code).IsUnique(); q.Property(x => x.Name).IsRequired().HasMaxLength(32); q.Property(x => x.Description).HasMaxLength(512); q.Property(x => x.CreationDate) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); q.HasMany(x => x.Permissions) .WithMany(x => x.Roles) .UsingEntity>("RolePermissions", x => x.HasOne() .WithMany() .HasForeignKey("PermissionId") .OnDelete(DeleteBehavior.Restrict) , x => x.HasOne() .WithMany() .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Restrict), x => { x.ToTable("RolePermissions"); x.HasKey("RoleId", "PermissionId"); }); }); modelBuilder.Entity(q => { q.ToTable("AccountRoles"); q.HasKey(x => x.Id); q.Property(x => x.GrantedAtUtc) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(x => x.IssuerId).IsRequired(); q.Property(x => x.RevokedAtUtc).IsRequired(false); q.Property(x => x.GrantReason).IsRequired(false).HasMaxLength(128); q.Property(x => x.RevokeReason).IsRequired(false).HasMaxLength(128); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); q.HasOne(x => x.Account) .WithMany(a => a.Roles) .HasForeignKey(x => x.AccountId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Role) .WithMany() .HasForeignKey(x => x.RoleId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Issuer) .WithMany() .HasForeignKey(x => x.IssuerId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Revoker) .WithMany() .HasForeignKey(x => x.RevokerId) .OnDelete(DeleteBehavior.Restrict) .IsRequired(false); q.HasIndex(x => new { x.AccountId, x.RoleId }) .HasDatabaseName("UX_AccountRoles_Active") .IsUnique() .HasFilter("\"RevokedAtUtc\" IS NULL"); q.HasIndex(x => new { x.AccountId, x.GrantedAtUtc }); q.HasIndex(x => new { x.IssuerId, x.GrantedAtUtc }); }); modelBuilder.Entity(q => { q.ToTable("AccountPermissions"); q.HasKey(x => x.Id); q.Property(x => x.GrantedAtUtc) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(x => x.IssuerId).IsRequired(); q.Property(x => x.RevokedAtUtc).IsRequired(false); q.Property(x => x.GrantReason).IsRequired(false).HasMaxLength(128); q.Property(x => x.RevokeReason).IsRequired(false).HasMaxLength(128); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); q.HasOne(x => x.Account) .WithMany(a => a.Permissions) .HasForeignKey(x => x.AccountId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Permission) .WithMany() .HasForeignKey(x => x.PermissionId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Issuer) .WithMany() .HasForeignKey(x => x.IssuerId) .OnDelete(DeleteBehavior.Restrict); q.HasOne(x => x.Revoker) .WithMany() .HasForeignKey(x => x.RevokerId) .OnDelete(DeleteBehavior.Restrict) .IsRequired(false); q.HasIndex(x => new { x.AccountId, x.PermissionId }) .HasDatabaseName("UX_AccountPermissions_Active") .IsUnique() .HasFilter("\"RevokedAtUtc\" IS NULL"); q.HasIndex(x => new { x.AccountId, x.GrantedAtUtc }); q.HasIndex(x => new { x.IssuerId, x.GrantedAtUtc }); }); modelBuilder.Entity(q => { q.ToTable("AccountBans"); q.HasKey(x => x.Id); q.Property(x => x.BanDate) .HasDefaultValueSql("now()").ValueGeneratedOnAdd().HasValueGenerator(typeof(DatetimeNowValueGenerator)); q.Property(x => x.IssuerId).IsRequired(); q.Property(x => x.Reason).IsRequired().HasMaxLength(128); q.Property(x => x.UnbanReason).IsRequired(false).HasMaxLength(128); q.Property(e => e.Xmin).HasColumnName("xmin").IsRowVersion(); q.HasOne(x => x.Account) .WithMany(a => a.Bans) .HasForeignKey(x => x.AccountId) .OnDelete(DeleteBehavior.Restrict); q.HasIndex(x => new { x.AccountId, x.BanDate }); // q.HasIndex(x => new { x.AccountId, x.ReleaseDate }) // .HasDatabaseName("IX_AccountBans_Active") // .HasFilter("\"Deleted\" = false AND (\"ReleaseDate\" IS NULL OR \"ReleaseDate\" > now())"); }); RolesAndPermissionSeedList.SeedPermissions(modelBuilder.Entity()); RolesAndPermissionSeedList.SeedRoles(modelBuilder.Entity(), modelBuilder); modelBuilder.Entity().HasQueryFilter(x => !x.Deleted); modelBuilder.Entity().HasQueryFilter(x => !x.Account.Deleted); modelBuilder.Entity().HasQueryFilter(x => !x.Account.Deleted); modelBuilder.Entity().HasQueryFilter(x => !x.Deleted); modelBuilder.Entity().HasQueryFilter(x => !x.Deleted); modelBuilder.Entity().HasQueryFilter(x => !x.Deleted); } private class DatetimeNowValueGenerator : ValueGenerator { public override bool GeneratesTemporaryValues => false; public override DateTime Next(EntityEntry entry) { return DateTime.UtcNow; } } }