Add Login Module
This commit is contained in:
@@ -3,6 +3,7 @@ using AMREZ.EOP.Domain.Entities.Common;
|
||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.Data;
|
||||
|
||||
@@ -37,11 +38,14 @@ public class AppDbContext : DbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
// ====== Global Tenancy Config (meta schema) — ไม่สืบทอด BaseEntity ======
|
||||
// ====== Tenancy (meta) ======
|
||||
model.Entity<TenantConfig>(b =>
|
||||
{
|
||||
b.ToTable("tenants", schema: "meta");
|
||||
b.HasKey(x => x.TenantKey);
|
||||
b.HasKey(x => x.TenantKey); // PK = key (slug)
|
||||
b.HasAlternateKey(x => x.TenantId); // AK = GUID
|
||||
b.HasIndex(x => x.TenantId).IsUnique();
|
||||
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired();
|
||||
b.Property(x => x.Schema).HasMaxLength(128);
|
||||
b.Property(x => x.ConnectionString);
|
||||
@@ -58,7 +62,7 @@ public class AppDbContext : DbContext
|
||||
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.TenantKey).HasMaxLength(128); // optional
|
||||
b.Property(x => x.IsPlatformBaseDomain).HasDefaultValue(false);
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
@@ -81,36 +85,13 @@ public class AppDbContext : DbContext
|
||||
b.ToTable("users");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
// principal key สำหรับ composite FK จากลูก ๆ
|
||||
b.HasAlternateKey(u => new { u.TenantId, u.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 =>
|
||||
@@ -122,11 +103,16 @@ public class AppDbContext : DbContext
|
||||
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.Type, x.Identifier }).IsUnique();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.Type, x.IsPrimary })
|
||||
.HasDatabaseName("ix_user_identity_primary_per_type");
|
||||
|
||||
// (TenantId, UserId) -> User.(TenantId, Id)
|
||||
b.HasOne(i => i.User)
|
||||
.WithMany(u => u.Identities)
|
||||
.HasForeignKey(i => new { i.TenantId, i.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserMfaFactor>(b =>
|
||||
@@ -136,8 +122,13 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Enabled).HasDefaultValue(true);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.MfaFactors)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserSession>(b =>
|
||||
@@ -148,6 +139,12 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.RefreshTokenHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
b.HasIndex(x => new { x.TenantId, x.DeviceId });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.Sessions)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserPasswordHistory>(b =>
|
||||
@@ -156,6 +153,12 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.ChangedAt });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.PasswordHistories)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserExternalAccount>(b =>
|
||||
@@ -165,15 +168,20 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.Property(x => x.Provider).IsRequired();
|
||||
b.Property(x => x.Subject).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject }).IsUnique();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject })
|
||||
.IsUnique();
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.ExternalAccounts)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<Role>(b =>
|
||||
{
|
||||
b.ToTable("roles");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(r => new { r.TenantId, r.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -185,6 +193,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("permissions");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -198,6 +207,18 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique();
|
||||
|
||||
b.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<RolePermission>(b =>
|
||||
@@ -206,6 +227,18 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Permission>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PermissionId })
|
||||
.HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// ====== HR ======
|
||||
@@ -213,22 +246,25 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("user_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.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();
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id }) // <-- เปลี่ยนตรงนี้
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<Department>(b =>
|
||||
{
|
||||
b.ToTable("departments");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(d => new { d.TenantId, d.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -237,7 +273,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.Parent)
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => x.ParentDepartmentId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
@@ -245,6 +282,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("positions");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
|
||||
@@ -264,17 +302,20 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Employments)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Department)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DepartmentId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.DepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne(x => x.Position)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.PositionId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.PositionId })
|
||||
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
@@ -290,7 +331,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Addresses)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -306,7 +348,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.EmergencyContacts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -323,7 +366,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.BankAccounts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -347,10 +391,18 @@ public class AppDbContext : DbContext
|
||||
.HasColumnName("tenant_id")
|
||||
.HasColumnType("uuid")
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.ValueGeneratedNever();
|
||||
|
||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
||||
b.HasCheckConstraint($"ck_{et.GetTableName()}_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
// ชื่อ constraint สร้างแบบ concat แทน string interpolation กัน ambiguous handler
|
||||
var tn = et.GetTableName();
|
||||
if (!string.IsNullOrEmpty(tn))
|
||||
{
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_null"), "tenant_id is not null");
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_zero"),
|
||||
"tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
}
|
||||
|
||||
// Audit
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
|
||||
Reference in New Issue
Block a user