Init Git
This commit is contained in:
365
AMREZ.EOP.Infrastructures/Data/AppDbContext.cs
Normal file
365
AMREZ.EOP.Infrastructures/Data/AppDbContext.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||
using AMREZ.EOP.Domain.Entities.Common;
|
||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
// ===== Auth =====
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<UserIdentity> UserIdentities => Set<UserIdentity>();
|
||||
public DbSet<UserMfaFactor> UserMfaFactors => Set<UserMfaFactor>();
|
||||
public DbSet<UserSession> UserSessions => Set<UserSession>();
|
||||
public DbSet<UserPasswordHistory> UserPasswordHistories => Set<UserPasswordHistory>();
|
||||
public DbSet<UserExternalAccount> UserExternalAccounts => Set<UserExternalAccount>();
|
||||
public DbSet<Role> Roles => Set<Role>();
|
||||
public DbSet<Permission> Permissions => Set<Permission>();
|
||||
public DbSet<UserRole> UserRoles => Set<UserRole>();
|
||||
public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
|
||||
|
||||
// ===== HR =====
|
||||
public DbSet<UserProfile> UserProfiles => Set<UserProfile>();
|
||||
public DbSet<Department> Departments => Set<Department>();
|
||||
public DbSet<Position> Positions => Set<Position>();
|
||||
public DbSet<Employment> Employments => Set<Employment>();
|
||||
public DbSet<EmployeeAddress> EmployeeAddresses => Set<EmployeeAddress>();
|
||||
public DbSet<EmergencyContact> EmergencyContacts => Set<EmergencyContact>();
|
||||
public DbSet<EmployeeBankAccount> EmployeeBankAccounts => Set<EmployeeBankAccount>();
|
||||
|
||||
// ===== Tenancy (meta) =====
|
||||
public DbSet<TenantConfig> Tenants => Set<TenantConfig>();
|
||||
public DbSet<TenantDomain> TenantDomains => Set<TenantDomain>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
// ====== Global Tenancy Config (meta schema) — ไม่สืบทอด BaseEntity ======
|
||||
model.Entity<TenantConfig>(b =>
|
||||
{
|
||||
b.ToTable("tenants", schema: "meta");
|
||||
b.HasKey(x => x.TenantKey);
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired();
|
||||
b.Property(x => x.Schema).HasMaxLength(128);
|
||||
b.Property(x => x.ConnectionString);
|
||||
b.Property(x => x.Mode).IsRequired();
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
.HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.HasIndex(x => x.IsActive);
|
||||
});
|
||||
|
||||
model.Entity<TenantDomain>(b =>
|
||||
{
|
||||
b.ToTable("tenant_domains", schema: "meta");
|
||||
b.HasKey(x => x.Domain);
|
||||
b.Property(x => x.Domain).HasMaxLength(253).IsRequired();
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128);
|
||||
b.Property(x => x.IsPlatformBaseDomain).HasDefaultValue(false);
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
.HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
|
||||
b.HasIndex(x => x.TenantKey);
|
||||
b.HasIndex(x => x.IsPlatformBaseDomain);
|
||||
b.HasIndex(x => x.IsActive);
|
||||
|
||||
b.HasOne<TenantConfig>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.TenantKey)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// ====== Auth ======
|
||||
model.Entity<User>(b =>
|
||||
{
|
||||
b.ToTable("users");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
|
||||
b.Property(x => x.AccessFailedCount).HasDefaultValue(0);
|
||||
b.Property(x => x.MfaEnabled).HasDefaultValue(false);
|
||||
|
||||
b.HasMany(x => x.Identities)
|
||||
.WithOne(i => i.User)
|
||||
.HasForeignKey(i => i.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.MfaFactors)
|
||||
.WithOne(i => i.User)
|
||||
.HasForeignKey(i => i.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.Sessions)
|
||||
.WithOne(s => s.User)
|
||||
.HasForeignKey(s => s.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.PasswordHistories)
|
||||
.WithOne(ph => ph.User)
|
||||
.HasForeignKey(ph => ph.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.ExternalAccounts)
|
||||
.WithOne(ea => ea.User)
|
||||
.HasForeignKey(ea => ea.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserIdentity>(b =>
|
||||
{
|
||||
b.ToTable("user_identities");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Identifier).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.IsPrimary).HasDefaultValue(false);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier })
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.Type, x.IsPrimary })
|
||||
.HasDatabaseName("ix_user_identity_primary_per_type");
|
||||
});
|
||||
|
||||
model.Entity<UserMfaFactor>(b =>
|
||||
{
|
||||
b.ToTable("user_mfa_factors");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Enabled).HasDefaultValue(true);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
});
|
||||
|
||||
model.Entity<UserSession>(b =>
|
||||
{
|
||||
b.ToTable("user_sessions");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.RefreshTokenHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
b.HasIndex(x => new { x.TenantId, x.DeviceId });
|
||||
});
|
||||
|
||||
model.Entity<UserPasswordHistory>(b =>
|
||||
{
|
||||
b.ToTable("user_password_histories");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.ChangedAt });
|
||||
});
|
||||
|
||||
model.Entity<UserExternalAccount>(b =>
|
||||
{
|
||||
b.ToTable("user_external_accounts");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Provider).IsRequired();
|
||||
b.Property(x => x.Subject).IsRequired();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject })
|
||||
.IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<Role>(b =>
|
||||
{
|
||||
b.ToTable("roles");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<Permission>(b =>
|
||||
{
|
||||
b.ToTable("permissions");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<UserRole>(b =>
|
||||
{
|
||||
b.ToTable("user_roles");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<RolePermission>(b =>
|
||||
{
|
||||
b.ToTable("role_permissions");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
||||
});
|
||||
|
||||
// ====== HR ======
|
||||
model.Entity<UserProfile>(b =>
|
||||
{
|
||||
b.ToTable("user_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.FirstName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.LastName).IsRequired().HasMaxLength(128);
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => x.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<Department>(b =>
|
||||
{
|
||||
b.ToTable("departments");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
|
||||
b.HasOne(x => x.Parent)
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => x.ParentDepartmentId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<Position>(b =>
|
||||
{
|
||||
b.ToTable("positions");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<Employment>(b =>
|
||||
{
|
||||
b.ToTable("employments");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.EmploymentType).IsRequired();
|
||||
b.Property(x => x.StartDate).IsRequired();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.StartDate });
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Employments)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Department)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DepartmentId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne(x => x.Position)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.PositionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<EmployeeAddress>(b =>
|
||||
{
|
||||
b.ToTable("employee_addresses");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Line1).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.City).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.PostalCode).IsRequired().HasMaxLength(32);
|
||||
b.Property(x => x.Country).IsRequired().HasMaxLength(64);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Addresses)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
});
|
||||
|
||||
model.Entity<EmergencyContact>(b =>
|
||||
{
|
||||
b.ToTable("emergency_contacts");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Relationship).IsRequired().HasMaxLength(64);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.EmergencyContacts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
});
|
||||
|
||||
model.Entity<EmployeeBankAccount>(b =>
|
||||
{
|
||||
b.ToTable("employee_bank_accounts");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.BankName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.AccountNumber).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.AccountHolder).IsRequired().HasMaxLength(128);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.BankAccounts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
});
|
||||
|
||||
// ====== Enums as ints ======
|
||||
model.Entity<UserIdentity>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserMfaFactor>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserExternalAccount>().Property(x => x.Provider).HasConversion<int>();
|
||||
model.Entity<Employment>().Property(x => x.EmploymentType).HasConversion<int>();
|
||||
model.Entity<UserProfile>().Property(x => x.Gender).HasConversion<int?>();
|
||||
|
||||
// ====== BaseEntity common mapping ======
|
||||
foreach (var et in model.Model.GetEntityTypes()
|
||||
.Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
|
||||
{
|
||||
var b = model.Entity(et.ClrType);
|
||||
|
||||
// Tenant
|
||||
b.Property<Guid>(nameof(BaseEntity.TenantId))
|
||||
.HasColumnName("tenant_id")
|
||||
.HasColumnType("uuid")
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
|
||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
||||
b.HasCheckConstraint($"ck_{et.GetTableName()}_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
// Audit
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.Property<DateTimeOffset?>("UpdatedAt").HasColumnName("updated_at");
|
||||
b.Property<string?>("CreatedBy").HasColumnName("created_by");
|
||||
b.Property<string?>("UpdatedBy").HasColumnName("updated_by");
|
||||
b.Property<bool>("IsDeleted").HasColumnName("is_deleted").HasDefaultValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user