Add Login Module

This commit is contained in:
Thanakarn Klangkasame
2025-10-02 11:18:44 +07:00
parent f505e31cfd
commit 563a341a99
52 changed files with 1127 additions and 2036 deletions

View File

@@ -17,6 +17,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="StackExchange.Redis" Version="2.9.17" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -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")

View File

@@ -29,6 +29,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IJwtFactory, JwtFactory>();
// Options
services.AddOptions<AuthOptions>()
@@ -90,7 +91,9 @@ public static class ServiceCollectionExtensions
services.AddScoped<IDisableMfaUseCase, DisableMfaUseCase>();
services.AddScoped<ILogoutUseCase, LogoutUseCase>();
services.AddScoped<ILogoutAllUseCase, LogoutAllUseCase>();
services.AddScoped<IIssueTokenPairUseCase, IssueTokenPairUseCase>();
services.AddScoped<IRefreshUseCase, RefreshUseCase>();
// UseCases — HR
services.AddScoped<IUpsertUserProfileUseCase, UpsertUserProfileUseCase>();
services.AddScoped<IAddEmploymentUseCase, AddEmploymentUseCase>();

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AMREZ.EOP.Infrastructures.Migrations
{
/// <inheritdoc />
public partial class Add_Tenant_Guid : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "TenantId",
schema: "meta",
table: "tenants",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TenantId",
schema: "meta",
table: "tenants");
}
}
}

View File

@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace AMREZ.EOP.Infrastructures.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20250930101327_Add_Tenant_Guid")]
partial class Add_Tenant_Guid
[Migration("20251002040013_InitDatabase")]
partial class InitDatabase
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -58,10 +58,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -81,6 +79,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("permissions", null, t =>
{
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -117,10 +117,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -140,6 +138,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("roles", null, t =>
{
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -172,10 +172,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -193,12 +191,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("TenantId", "PermissionId");
b.HasIndex("TenantId", "RoleId", "PermissionId")
.IsUnique();
b.ToTable("role_permissions", null, t =>
{
t.HasCheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -250,10 +252,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -270,6 +270,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("users", null, t =>
{
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -309,10 +311,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -329,7 +329,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId");
b.HasIndex("TenantId", "Provider", "Subject")
.IsUnique();
@@ -337,6 +337,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_external_accounts", null, t =>
{
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -373,10 +375,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasDefaultValue(false);
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -399,8 +399,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "Type", "Identifier")
.IsUnique();
@@ -410,6 +408,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_identities", null, t =>
{
t.HasCheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -465,10 +465,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -488,13 +486,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId");
b.ToTable("user_mfa_factors", null, t =>
{
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -528,10 +526,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -548,13 +544,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId", "ChangedAt");
b.ToTable("user_password_histories", null, t =>
{
t.HasCheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -584,10 +580,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -608,12 +602,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("UserId");
b.HasIndex("TenantId", "RoleId");
b.HasIndex("TenantId", "UserId", "RoleId")
.IsUnique();
b.ToTable("user_roles", null, t =>
{
t.HasCheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -659,10 +657,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -682,8 +678,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "DeviceId");
b.HasIndex("TenantId", "UserId");
@@ -691,6 +685,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_sessions", null, t =>
{
t.HasCheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -730,10 +726,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -745,16 +739,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("Id");
b.HasIndex("ParentDepartmentId");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "Code")
.IsUnique();
b.HasIndex("TenantId", "ParentDepartmentId");
b.ToTable("departments", null, t =>
{
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -800,10 +796,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(64)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -820,13 +814,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("emergency_contacts", null, t =>
{
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -882,10 +876,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -905,13 +897,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("employee_addresses", null, t =>
{
t.HasCheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -962,10 +954,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -982,13 +972,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("employee_bank_accounts", null, t =>
{
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1033,10 +1023,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -1057,19 +1045,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("Id");
b.HasIndex("DepartmentId");
b.HasIndex("PositionId");
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "DepartmentId");
b.HasIndex("TenantId", "PositionId");
b.HasIndex("TenantId", "UserProfileId", "StartDate");
b.ToTable("employments", null, t =>
{
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1104,10 +1092,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<string>("Title")
.IsRequired()
@@ -1132,6 +1118,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("positions", null, t =>
{
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1186,10 +1174,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -1210,15 +1196,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId")
.IsUnique();
b.HasIndex("TenantId", "UserId")
.IsUnique();
b.ToTable("user_profiles", null, t =>
{
t.HasCheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1254,8 +1239,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("TenantKey");
b.HasAlternateKey("TenantId");
b.HasIndex("IsActive");
b.HasIndex("TenantId")
.IsUnique();
b.ToTable("tenants", "meta");
});
@@ -1310,6 +1300,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Permission", null)
.WithMany()
.HasForeignKey("TenantId", "PermissionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Permission");
b.Navigation("Role");
@@ -1319,7 +1323,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("ExternalAccounts")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1330,7 +1335,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("Identities")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1341,7 +1347,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("MfaFactors")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1352,7 +1359,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("PasswordHistories")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1373,6 +1381,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", null)
.WithMany()
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
@@ -1382,7 +1404,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("Sessions")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1393,7 +1416,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
.WithMany("Children")
.HasForeignKey("ParentDepartmentId")
.HasForeignKey("TenantId", "ParentDepartmentId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Parent");
@@ -1403,7 +1427,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("EmergencyContacts")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1414,7 +1439,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("Addresses")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1425,7 +1451,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("BankAccounts")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1436,17 +1463,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
.WithMany()
.HasForeignKey("DepartmentId")
.HasForeignKey("TenantId", "DepartmentId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
.WithMany()
.HasForeignKey("PositionId")
.HasForeignKey("TenantId", "PositionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("Employments")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1469,7 +1499,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithOne()
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserId")
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "TenantId", "UserId")
.HasPrincipalKey("AMREZ.EOP.Domain.Entities.Authentications.User", "TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();

View File

@@ -19,10 +19,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
ParentDepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -32,12 +32,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_departments", x => x.Id);
table.UniqueConstraint("AK_departments_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_departments_departments_ParentDepartmentId",
column: x => x.ParentDepartmentId,
name: "FK_departments_departments_tenant_id_ParentDepartmentId",
columns: x => new { x.tenant_id, x.ParentDepartmentId },
principalTable: "departments",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Restrict);
});
@@ -46,9 +48,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
Code = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -58,7 +60,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_permissions", x => x.Id);
table.UniqueConstraint("AK_permissions_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
migrationBuilder.CreateTable(
@@ -66,10 +70,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Level = table.Column<int>(type: "integer", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -79,7 +83,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_positions", x => x.Id);
table.UniqueConstraint("AK_positions_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
migrationBuilder.CreateTable(
@@ -87,9 +93,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
Code = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -99,7 +105,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_roles", x => x.Id);
table.UniqueConstraint("AK_roles_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
migrationBuilder.CreateTable(
@@ -108,6 +116,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
TenantKey = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
Schema = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
ConnectionString = table.Column<string>(type: "text", nullable: true),
Mode = table.Column<int>(type: "integer", nullable: false),
@@ -117,6 +126,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_tenants", x => x.TenantKey);
table.UniqueConstraint("AK_tenants_TenantId", x => x.TenantId);
});
migrationBuilder.CreateTable(
@@ -124,13 +134,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
PasswordHash = table.Column<string>(type: "text", nullable: false),
IsActive = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
AccessFailedCount = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
LockoutEndUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
MfaEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
SecurityStamp = table.Column<string>(type: "text", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -140,7 +150,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_users", x => x.Id);
table.UniqueConstraint("AK_users_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
migrationBuilder.CreateTable(
@@ -148,9 +160,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
RoleId = table.Column<Guid>(type: "uuid", nullable: false),
PermissionId = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -161,18 +173,31 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_role_permissions", x => x.Id);
table.CheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_role_permissions_permissions_PermissionId",
column: x => x.PermissionId,
principalTable: "permissions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_role_permissions_permissions_tenant_id_PermissionId",
columns: x => new { x.tenant_id, x.PermissionId },
principalTable: "permissions",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_role_permissions_roles_RoleId",
column: x => x.RoleId,
principalTable: "roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_role_permissions_roles_tenant_id_RoleId",
columns: x => new { x.tenant_id, x.RoleId },
principalTable: "roles",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
@@ -203,12 +228,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
Provider = table.Column<int>(type: "integer", nullable: false),
Subject = table.Column<string>(type: "text", nullable: false),
Email = table.Column<string>(type: "text", nullable: true),
LinkedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -219,11 +244,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_external_accounts", x => x.Id);
table.CheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_external_accounts_users_UserId",
column: x => x.UserId,
name: "FK_user_external_accounts_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -232,12 +258,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Identifier = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
VerifiedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -248,11 +274,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_identities", x => x.Id);
table.CheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_identities_users_UserId",
column: x => x.UserId,
name: "FK_user_identities_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -261,7 +288,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Label = table.Column<string>(type: "text", nullable: true),
@@ -273,6 +299,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
AddedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
LastUsedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -283,11 +310,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_mfa_factors", x => x.Id);
table.CheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_mfa_factors_users_UserId",
column: x => x.UserId,
name: "FK_user_mfa_factors_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -296,10 +324,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
PasswordHash = table.Column<string>(type: "text", nullable: false),
ChangedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -310,11 +338,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_password_histories", x => x.Id);
table.CheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_password_histories_users_UserId",
column: x => x.UserId,
name: "FK_user_password_histories_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -323,7 +352,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
FirstName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
LastName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
@@ -333,6 +361,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
Gender = table.Column<int>(type: "integer", nullable: true),
DepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
PositionId = table.Column<Guid>(type: "uuid", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -342,7 +371,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
constraints: table =>
{
table.PrimaryKey("PK_user_profiles", x => x.Id);
table.UniqueConstraint("AK_user_profiles_tenant_id_Id", x => new { x.tenant_id, x.Id });
table.CheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_profiles_departments_DepartmentId",
column: x => x.DepartmentId,
@@ -354,10 +385,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
principalTable: "positions",
principalColumn: "Id");
table.ForeignKey(
name: "FK_user_profiles_users_UserId",
column: x => x.UserId,
name: "FK_user_profiles_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -366,9 +397,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
RoleId = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -379,18 +410,31 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_roles", x => x.Id);
table.CheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_roles_roles_RoleId",
column: x => x.RoleId,
principalTable: "roles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_roles_roles_tenant_id_RoleId",
columns: x => new { x.tenant_id, x.RoleId },
principalTable: "roles",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_roles_users_UserId",
column: x => x.UserId,
principalTable: "users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_roles_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
@@ -398,7 +442,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserId = table.Column<Guid>(type: "uuid", nullable: false),
RefreshTokenHash = table.Column<string>(type: "text", nullable: false),
IssuedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
@@ -407,6 +450,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
DeviceId = table.Column<string>(type: "text", nullable: true),
UserAgent = table.Column<string>(type: "text", nullable: true),
IpAddress = table.Column<string>(type: "text", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -417,11 +461,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_user_sessions", x => x.Id);
table.CheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_user_sessions_users_UserId",
column: x => x.UserId,
name: "FK_user_sessions_users_tenant_id_UserId",
columns: x => new { x.tenant_id, x.UserId },
principalTable: "users",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -430,13 +475,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
Relationship = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
Phone = table.Column<string>(type: "text", nullable: true),
Email = table.Column<string>(type: "text", nullable: true),
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -447,11 +492,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_emergency_contacts", x => x.Id);
table.CheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_emergency_contacts_user_profiles_UserProfileId",
column: x => x.UserProfileId,
name: "FK_emergency_contacts_user_profiles_tenant_id_UserProfileId",
columns: x => new { x.tenant_id, x.UserProfileId },
principalTable: "user_profiles",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -460,7 +506,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
Type = table.Column<int>(type: "integer", nullable: false),
Line1 = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
@@ -470,6 +515,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
PostalCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
Country = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -480,11 +526,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_employee_addresses", x => x.Id);
table.CheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_employee_addresses_user_profiles_UserProfileId",
column: x => x.UserProfileId,
name: "FK_employee_addresses_user_profiles_tenant_id_UserProfileId",
columns: x => new { x.tenant_id, x.UserProfileId },
principalTable: "user_profiles",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -493,7 +540,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
BankName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
AccountNumber = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
@@ -501,6 +547,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
Branch = table.Column<string>(type: "text", nullable: true),
Note = table.Column<string>(type: "text", nullable: true),
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -511,11 +558,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_employee_bank_accounts", x => x.Id);
table.CheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_employee_bank_accounts_user_profiles_UserProfileId",
column: x => x.UserProfileId,
name: "FK_employee_bank_accounts_user_profiles_tenant_id_UserProfileId",
columns: x => new { x.tenant_id, x.UserProfileId },
principalTable: "user_profiles",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
@@ -524,7 +572,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
EmploymentType = table.Column<int>(type: "integer", nullable: false),
StartDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
@@ -534,6 +581,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
ManagerUserId = table.Column<Guid>(type: "uuid", nullable: true),
WorkEmail = table.Column<string>(type: "text", nullable: true),
WorkPhone = table.Column<string>(type: "text", nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
created_by = table.Column<string>(type: "text", nullable: true),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -544,31 +592,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
table.PrimaryKey("PK_employments", x => x.Id);
table.CheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
table.CheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
table.ForeignKey(
name: "FK_employments_departments_DepartmentId",
column: x => x.DepartmentId,
name: "FK_employments_departments_tenant_id_DepartmentId",
columns: x => new { x.tenant_id, x.DepartmentId },
principalTable: "departments",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_employments_positions_PositionId",
column: x => x.PositionId,
name: "FK_employments_positions_tenant_id_PositionId",
columns: x => new { x.tenant_id, x.PositionId },
principalTable: "positions",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_employments_user_profiles_UserProfileId",
column: x => x.UserProfileId,
name: "FK_employments_user_profiles_tenant_id_UserProfileId",
columns: x => new { x.tenant_id, x.UserProfileId },
principalTable: "user_profiles",
principalColumn: "Id",
principalColumns: new[] { "tenant_id", "Id" },
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_departments_ParentDepartmentId",
table: "departments",
column: "ParentDepartmentId");
migrationBuilder.CreateIndex(
name: "IX_departments_tenant_id",
table: "departments",
@@ -580,6 +624,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: new[] { "tenant_id", "Code" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_departments_tenant_id_ParentDepartmentId",
table: "departments",
columns: new[] { "tenant_id", "ParentDepartmentId" });
migrationBuilder.CreateIndex(
name: "IX_emergency_contacts_tenant_id",
table: "emergency_contacts",
@@ -590,11 +639,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "emergency_contacts",
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
migrationBuilder.CreateIndex(
name: "IX_emergency_contacts_UserProfileId",
table: "emergency_contacts",
column: "UserProfileId");
migrationBuilder.CreateIndex(
name: "IX_employee_addresses_tenant_id",
table: "employee_addresses",
@@ -605,11 +649,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "employee_addresses",
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
migrationBuilder.CreateIndex(
name: "IX_employee_addresses_UserProfileId",
table: "employee_addresses",
column: "UserProfileId");
migrationBuilder.CreateIndex(
name: "IX_employee_bank_accounts_tenant_id",
table: "employee_bank_accounts",
@@ -620,36 +659,26 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "employee_bank_accounts",
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
migrationBuilder.CreateIndex(
name: "IX_employee_bank_accounts_UserProfileId",
table: "employee_bank_accounts",
column: "UserProfileId");
migrationBuilder.CreateIndex(
name: "IX_employments_DepartmentId",
table: "employments",
column: "DepartmentId");
migrationBuilder.CreateIndex(
name: "IX_employments_PositionId",
table: "employments",
column: "PositionId");
migrationBuilder.CreateIndex(
name: "IX_employments_tenant_id",
table: "employments",
column: "tenant_id");
migrationBuilder.CreateIndex(
name: "IX_employments_tenant_id_DepartmentId",
table: "employments",
columns: new[] { "tenant_id", "DepartmentId" });
migrationBuilder.CreateIndex(
name: "IX_employments_tenant_id_PositionId",
table: "employments",
columns: new[] { "tenant_id", "PositionId" });
migrationBuilder.CreateIndex(
name: "IX_employments_tenant_id_UserProfileId_StartDate",
table: "employments",
columns: new[] { "tenant_id", "UserProfileId", "StartDate" });
migrationBuilder.CreateIndex(
name: "IX_employments_UserProfileId",
table: "employments",
column: "UserProfileId");
migrationBuilder.CreateIndex(
name: "IX_permissions_tenant_id",
table: "permissions",
@@ -687,6 +716,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "role_permissions",
column: "tenant_id");
migrationBuilder.CreateIndex(
name: "IX_role_permissions_tenant_id_PermissionId",
table: "role_permissions",
columns: new[] { "tenant_id", "PermissionId" });
migrationBuilder.CreateIndex(
name: "IX_role_permissions_tenant_id_RoleId_PermissionId",
table: "role_permissions",
@@ -728,6 +762,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "tenants",
column: "IsActive");
migrationBuilder.CreateIndex(
name: "IX_tenants_TenantId",
schema: "meta",
table: "tenants",
column: "TenantId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_user_external_accounts_tenant_id",
table: "user_external_accounts",
@@ -740,9 +781,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
unique: true);
migrationBuilder.CreateIndex(
name: "IX_user_external_accounts_UserId",
name: "IX_user_external_accounts_tenant_id_UserId",
table: "user_external_accounts",
column: "UserId");
columns: new[] { "tenant_id", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_user_identities_tenant_id",
@@ -755,11 +796,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: new[] { "tenant_id", "Type", "Identifier" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_user_identities_UserId",
table: "user_identities",
column: "UserId");
migrationBuilder.CreateIndex(
name: "ix_user_identity_primary_per_type",
table: "user_identities",
@@ -775,11 +811,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "user_mfa_factors",
columns: new[] { "tenant_id", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_user_mfa_factors_UserId",
table: "user_mfa_factors",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_user_password_histories_tenant_id",
table: "user_password_histories",
@@ -790,11 +821,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "user_password_histories",
columns: new[] { "tenant_id", "UserId", "ChangedAt" });
migrationBuilder.CreateIndex(
name: "IX_user_password_histories_UserId",
table: "user_password_histories",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_user_profiles_DepartmentId",
table: "user_profiles",
@@ -816,12 +842,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
columns: new[] { "tenant_id", "UserId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_user_profiles_UserId",
table: "user_profiles",
column: "UserId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_user_roles_RoleId",
table: "user_roles",
@@ -832,6 +852,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "user_roles",
column: "tenant_id");
migrationBuilder.CreateIndex(
name: "IX_user_roles_tenant_id_RoleId",
table: "user_roles",
columns: new[] { "tenant_id", "RoleId" });
migrationBuilder.CreateIndex(
name: "IX_user_roles_tenant_id_UserId_RoleId",
table: "user_roles",
@@ -858,11 +883,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
table: "user_sessions",
columns: new[] { "tenant_id", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_user_sessions_UserId",
table: "user_sessions",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_users_tenant_id",
table: "users",

View File

@@ -55,10 +55,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -78,6 +76,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("permissions", null, t =>
{
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -114,10 +114,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(256)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -137,6 +135,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("roles", null, t =>
{
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -169,10 +169,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -190,12 +188,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("TenantId", "PermissionId");
b.HasIndex("TenantId", "RoleId", "PermissionId")
.IsUnique();
b.ToTable("role_permissions", null, t =>
{
t.HasCheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -247,10 +249,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -267,6 +267,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("users", null, t =>
{
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -306,10 +308,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -326,7 +326,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId");
b.HasIndex("TenantId", "Provider", "Subject")
.IsUnique();
@@ -334,6 +334,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_external_accounts", null, t =>
{
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -370,10 +372,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasDefaultValue(false);
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -396,8 +396,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "Type", "Identifier")
.IsUnique();
@@ -407,6 +405,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_identities", null, t =>
{
t.HasCheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -462,10 +462,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -485,13 +483,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId");
b.ToTable("user_mfa_factors", null, t =>
{
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -525,10 +523,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -545,13 +541,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "UserId", "ChangedAt");
b.ToTable("user_password_histories", null, t =>
{
t.HasCheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -581,10 +577,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -605,12 +599,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("UserId");
b.HasIndex("TenantId", "RoleId");
b.HasIndex("TenantId", "UserId", "RoleId")
.IsUnique();
b.ToTable("user_roles", null, t =>
{
t.HasCheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -656,10 +654,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -679,8 +675,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId");
b.HasIndex("TenantId", "DeviceId");
b.HasIndex("TenantId", "UserId");
@@ -688,6 +682,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("user_sessions", null, t =>
{
t.HasCheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -727,10 +723,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -742,16 +736,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("Id");
b.HasIndex("ParentDepartmentId");
b.HasIndex("TenantId");
b.HasIndex("TenantId", "Code")
.IsUnique();
b.HasIndex("TenantId", "ParentDepartmentId");
b.ToTable("departments", null, t =>
{
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -797,10 +793,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("character varying(64)");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -817,13 +811,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("emergency_contacts", null, t =>
{
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -879,10 +873,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<int>("Type")
.HasColumnType("integer");
@@ -902,13 +894,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("employee_addresses", null, t =>
{
t.HasCheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -959,10 +951,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("text");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -979,13 +969,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
b.ToTable("employee_bank_accounts", null, t =>
{
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1030,10 +1020,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -1054,19 +1042,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("Id");
b.HasIndex("DepartmentId");
b.HasIndex("PositionId");
b.HasIndex("TenantId");
b.HasIndex("UserProfileId");
b.HasIndex("TenantId", "DepartmentId");
b.HasIndex("TenantId", "PositionId");
b.HasIndex("TenantId", "UserProfileId", "StartDate");
b.ToTable("employments", null, t =>
{
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1101,10 +1089,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("integer");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<string>("Title")
.IsRequired()
@@ -1129,6 +1115,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.ToTable("positions", null, t =>
{
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1183,10 +1171,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.HasColumnType("uuid");
b.Property<Guid>("TenantId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("tenant_id")
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
.HasColumnName("tenant_id");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone")
@@ -1207,15 +1193,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasIndex("TenantId");
b.HasIndex("UserId")
.IsUnique();
b.HasIndex("TenantId", "UserId")
.IsUnique();
b.ToTable("user_profiles", null, t =>
{
t.HasCheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
t.HasCheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
});
});
@@ -1251,8 +1236,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasKey("TenantKey");
b.HasAlternateKey("TenantId");
b.HasIndex("IsActive");
b.HasIndex("TenantId")
.IsUnique();
b.ToTable("tenants", "meta");
});
@@ -1307,6 +1297,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Permission", null)
.WithMany()
.HasForeignKey("TenantId", "PermissionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Permission");
b.Navigation("Role");
@@ -1316,7 +1320,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("ExternalAccounts")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1327,7 +1332,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("Identities")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1338,7 +1344,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("MfaFactors")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1349,7 +1356,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("PasswordHistories")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1370,6 +1378,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", null)
.WithMany()
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
@@ -1379,7 +1401,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithMany("Sessions")
.HasForeignKey("UserId")
.HasForeignKey("TenantId", "UserId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1390,7 +1413,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
.WithMany("Children")
.HasForeignKey("ParentDepartmentId")
.HasForeignKey("TenantId", "ParentDepartmentId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.Navigation("Parent");
@@ -1400,7 +1424,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("EmergencyContacts")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1411,7 +1436,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("Addresses")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1422,7 +1448,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("BankAccounts")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1433,17 +1460,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
{
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
.WithMany()
.HasForeignKey("DepartmentId")
.HasForeignKey("TenantId", "DepartmentId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
.WithMany()
.HasForeignKey("PositionId")
.HasForeignKey("TenantId", "PositionId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
.WithMany("Employments")
.HasForeignKey("UserProfileId")
.HasForeignKey("TenantId", "UserProfileId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -1466,7 +1496,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
.WithOne()
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserId")
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "TenantId", "UserId")
.HasPrincipalKey("AMREZ.EOP.Domain.Entities.Authentications.User", "TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Infrastructures.Options;
public sealed class JwtOptions
{
public string Issuer { get; init; } = default!;
public string Audience { get; init; } = default!;
public string SigningKey { get; init; } = default!;
public int AccessMinutes { get; init; } = 10;
}

View File

@@ -1,8 +1,10 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Domain.Entities.Tenancy;
using AMREZ.EOP.Infrastructures.Data;
using AMREZ.EOP.Infrastructures.Options;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@@ -11,9 +13,19 @@ namespace AMREZ.EOP.Infrastructures.Repositories;
public sealed class TenantRepository : ITenantRepository
{
private readonly IDbScope _scope;
public TenantRepository(IDbScope scope) => _scope = scope;
private readonly ITenantResolver _resolver;
private readonly IHttpContextAccessor _http;
private AppDbContext Db() => _scope.Get<AppDbContext>();
public TenantRepository(IDbScope scope, ITenantResolver resolver, IHttpContextAccessor http)
{ _scope = scope; _resolver = resolver; _http = http; }
private AppDbContext Db()
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var platform = _resolver.Resolve(http, "@platform") ?? throw new InvalidOperationException("No platform tenant");
_scope.EnsureForTenant(platform);
return _scope.Get<AppDbContext>();
}
private static string Norm(string s) => (s ?? string.Empty).Trim().ToLowerInvariant();
@@ -51,7 +63,7 @@ public sealed class TenantRepository : ITenantRepository
if (cur is null) return false;
if (ifUnmodifiedSince.HasValue && cur.UpdatedAtUtc > ifUnmodifiedSince.Value)
return false; // 412
return false;
cur.Schema = row.Schema is null ? cur.Schema : (string.IsNullOrWhiteSpace(row.Schema) ? null : row.Schema.Trim());
cur.ConnectionString = row.ConnectionString is null ? cur.ConnectionString : (string.IsNullOrWhiteSpace(row.ConnectionString) ? null : row.ConnectionString.Trim());
@@ -70,7 +82,6 @@ public sealed class TenantRepository : ITenantRepository
var t = await db.Set<TenantConfig>().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
if (t is null) return false;
// ❌ ไม่ลบนะ — ✅ deactivate
if (t.IsActive)
{
t.IsActive = false;
@@ -120,7 +131,7 @@ public sealed class TenantRepository : ITenantRepository
if (ex is null) return false;
ex.IsActive = false;
ex.TenantKey = null;
ex.TenantKey = null;
ex.IsPlatformBaseDomain = false;
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;

View File

@@ -2,6 +2,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Domain.Entities.HumanResources;
using AMREZ.EOP.Domain.Entities.Tenancy;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
@@ -19,22 +20,33 @@ public class UserProfileRepository : IUserProfileRepository
private Guid TenantId()
{
var http = _http.HttpContext;
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.TenantKey;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var db = _scope.Get<AppDbContext>();
var cfg = db.Set<TenantConfig>().AsNoTracking().FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
public async Task<UserProfile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
return await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == userId, ct);
}
public async Task UpsertAsync(UserProfile profile, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var existing = await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == profile.UserId, ct);
if (existing is null)
@@ -56,8 +68,9 @@ public class UserProfileRepository : IUserProfileRepository
public async Task<Employment> AddEmploymentAsync(Employment e, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
e.TenantId = TenantId();
e.TenantId = tid;
await db.Employments.AddAsync(e, ct);
await db.SaveChangesAsync(ct);
return e;
@@ -65,8 +78,8 @@ public class UserProfileRepository : IUserProfileRepository
public async Task EndEmploymentAsync(Guid employmentId, DateTime endDate, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var e = await db.Employments.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == employmentId, ct);
if (e is null) return;
e.EndDate = endDate;
@@ -75,8 +88,9 @@ public class UserProfileRepository : IUserProfileRepository
public async Task<EmployeeAddress> AddAddressAsync(EmployeeAddress a, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
a.TenantId = TenantId();
a.TenantId = tid;
await db.EmployeeAddresses.AddAsync(a, ct);
await db.SaveChangesAsync(ct);
return a;
@@ -84,8 +98,8 @@ public class UserProfileRepository : IUserProfileRepository
public async Task SetPrimaryAddressAsync(Guid userProfileId, Guid addressId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var all = await db.EmployeeAddresses.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
foreach (var x in all) x.IsPrimary = false;
@@ -98,8 +112,9 @@ public class UserProfileRepository : IUserProfileRepository
public async Task<EmergencyContact> AddEmergencyContactAsync(EmergencyContact c, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
c.TenantId = TenantId();
c.TenantId = tid;
await db.EmergencyContacts.AddAsync(c, ct);
await db.SaveChangesAsync(ct);
return c;
@@ -107,8 +122,8 @@ public class UserProfileRepository : IUserProfileRepository
public async Task SetPrimaryEmergencyContactAsync(Guid userProfileId, Guid contactId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var all = await db.EmergencyContacts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
foreach (var x in all) x.IsPrimary = false;
@@ -121,8 +136,9 @@ public class UserProfileRepository : IUserProfileRepository
public async Task<EmployeeBankAccount> AddBankAccountAsync(EmployeeBankAccount b, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
b.TenantId = TenantId();
b.TenantId = tid;
await db.EmployeeBankAccounts.AddAsync(b, ct);
await db.SaveChangesAsync(ct);
return b;
@@ -130,8 +146,8 @@ public class UserProfileRepository : IUserProfileRepository
public async Task SetPrimaryBankAccountAsync(Guid userProfileId, Guid bankAccountId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var all = await db.EmployeeBankAccounts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
foreach (var x in all) x.IsPrimary = false;

View File

@@ -3,6 +3,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Domain.Entities.Authentications;
using AMREZ.EOP.Domain.Entities.Tenancy;
using AMREZ.EOP.Domain.Shared._Users;
using AMREZ.EOP.Infrastructures.Data;
using Microsoft.AspNetCore.Http;
@@ -25,16 +26,27 @@ public class UserRepository : IUserRepository
IHttpContextAccessor http)
{
_scope = scope;
_redis = redis; // null = ไม่มี/ต่อไม่ได้ → ข้าม cache
_redis = redis;
_tenantResolver = tenantResolver;
_http = http;
}
private Guid TenantId()
{
var http = _http.HttpContext;
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
_scope.EnsureForTenant(tc);
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
return g;
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
var db = _scope.Get<AppDbContext>();
var cfg = db.Set<TenantConfig>().AsNoTracking().FirstOrDefault(x => x.TenantKey == key);
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
return cfg.TenantId;
}
private static string IdentityEmailKey(string tenantId, string email)
@@ -45,8 +57,8 @@ public class UserRepository : IUserRepository
public async Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
return await db.Users.AsNoTracking()
.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
}
@@ -57,7 +69,6 @@ public class UserRepository : IUserRepository
var tidStr = tid.ToString();
var r = _redis?.GetDatabase();
// 1) Redis
if (r is not null)
{
try
@@ -69,10 +80,9 @@ public class UserRepository : IUserRepository
if (hit?.IsActive == true) return hit;
}
}
catch { /* ignore → query DB */ }
catch { }
}
// 2) EF: join identities
var db = _scope.Get<AppDbContext>();
var norm = email.Trim().ToLowerInvariant();
@@ -86,7 +96,6 @@ public class UserRepository : IUserRepository
i.Identifier == norm))
.FirstOrDefaultAsync(ct);
// 3) cache
if (user is not null && r is not null)
{
try
@@ -96,7 +105,7 @@ public class UserRepository : IUserRepository
await r.StringSetAsync(IdentityEmailKey(tidStr, norm), payload, ttl);
await r.StringSetAsync(UserIdKey(tidStr, user.Id), payload, ttl);
}
catch { /* ignore */ }
catch { }
}
return user;
@@ -104,8 +113,8 @@ public class UserRepository : IUserRepository
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var norm = email.Trim().ToLowerInvariant();
return await db.UserIdentities.AsNoTracking()
@@ -114,40 +123,36 @@ public class UserRepository : IUserRepository
public async Task AddAsync(User user, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
user.TenantId = TenantId();
user.TenantId = tid;
await db.Users.AddAsync(user, ct);
await db.SaveChangesAsync(ct);
// cache
var r = _redis?.GetDatabase();
if (r is not null)
{
try
{
var tid = user.TenantId.ToString();
var payload = JsonSerializer.Serialize(user);
var ttl = TimeSpan.FromMinutes(5);
await r.StringSetAsync(UserIdKey(tid, user.Id), payload, ttl);
await r.StringSetAsync(UserIdKey(tid.ToString(), user.Id), payload, ttl);
var email = user.Identities
.FirstOrDefault(i => i.Type == IdentityType.Email && i.IsPrimary)?.Identifier;
if (!string.IsNullOrWhiteSpace(email))
await r.StringSetAsync(IdentityEmailKey(tid, email!), payload, ttl);
await r.StringSetAsync(IdentityEmailKey(tid.ToString(), email!), payload, ttl);
}
catch { /* ignore */ }
catch { }
}
}
// ========= Identities =========
// (1) IMPLEMENTED: AddIdentityAsync
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var norm = identifier.Trim().ToLowerInvariant();
if (isPrimary)
@@ -171,11 +176,10 @@ public class UserRepository : IUserRepository
await db.SaveChangesAsync(ct);
}
// (2) IMPLEMENTED: VerifyIdentityAsync
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var norm = identifier.Trim().ToLowerInvariant();
var id = await db.UserIdentities
@@ -190,11 +194,10 @@ public class UserRepository : IUserRepository
await db.SaveChangesAsync(ct);
}
// (3) IMPLEMENTED: GetPrimaryIdentityAsync
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
return await db.UserIdentities.AsNoTracking()
.FirstOrDefaultAsync(i =>
@@ -204,12 +207,10 @@ public class UserRepository : IUserRepository
i.IsPrimary, ct);
}
// ========= Password =========
public async Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var user = await db.Users.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
if (user is null) return;
@@ -221,8 +222,8 @@ public class UserRepository : IUserRepository
public async Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var h = new UserPasswordHistory
{
@@ -236,12 +237,10 @@ public class UserRepository : IUserRepository
await db.SaveChangesAsync(ct);
}
// ========= MFA =========
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var f = new UserMfaFactor
{
@@ -265,8 +264,8 @@ public class UserRepository : IUserRepository
public async Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var f = await db.UserMfaFactors.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == factorId, ct);
if (f is null) return;
@@ -285,26 +284,45 @@ public class UserRepository : IUserRepository
public async Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
return await db.UserMfaFactors.AnyAsync(x => x.TenantId == tid && x.UserId == userId && x.Enabled, ct);
}
// ========= Sessions =========
public async Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
session.TenantId = TenantId();
session.TenantId = tid;
await db.UserSessions.AddAsync(session, ct);
await db.SaveChangesAsync(ct);
return session;
}
public async Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
return await db.UserSessions
.AsNoTracking()
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.RefreshTokenHash == refreshTokenHash, ct);
}
public async Task<bool> RotateSessionRefreshAsync(Guid tenantId, Guid sessionId, string newRefreshTokenHash, DateTimeOffset newIssuedAt, DateTimeOffset? newExpiresAt, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == sessionId, ct);
if (s is null || s.RevokedAt.HasValue) return false;
s.RefreshTokenHash = newRefreshTokenHash;
s.IssuedAt = newIssuedAt;
s.ExpiresAt = newExpiresAt;
await db.SaveChangesAsync(ct);
return true;
}
public async Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == sessionId && x.UserId == userId, ct);
if (s is null) return 0;
@@ -315,8 +333,8 @@ public class UserRepository : IUserRepository
public async Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default)
{
var db = _scope.Get<AppDbContext>();
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var sessions = await db.UserSessions
.Where(x => x.TenantId == tid && x.UserId == userId && x.RevokedAt == null)
@@ -326,4 +344,61 @@ public class UserRepository : IUserRepository
return await db.SaveChangesAsync(ct);
}
public async Task<bool> IsSessionActiveAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var now = DateTimeOffset.UtcNow;
return await db.UserSessions
.AsNoTracking()
.AnyAsync(x =>
x.TenantId == tid &&
x.Id == sessionId &&
x.UserId == userId &&
x.RevokedAt == null &&
(!x.ExpiresAt.HasValue || x.ExpiresAt > now), ct);
}
private static string TenantTokenVersionKey(Guid tenantId) => $"eop:{tenantId}:token:version";
public async Task<string> GetTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default)
{
var r = _redis?.GetDatabase();
if (r is null) return "1";
var key = TenantTokenVersionKey(tenantId);
var val = await r.StringGetAsync(key);
if (val.HasValue) return val.ToString();
await r.StringSetAsync(key, "1");
return "1";
}
public async Task BumpTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default)
{
var r = _redis?.GetDatabase();
if (r is null) return;
var key = TenantTokenVersionKey(tenantId);
await r.StringIncrementAsync(key);
}
public async Task<string?> GetUserSecurityStampAsync(Guid userId, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
return await db.Users
.AsNoTracking()
.Where(x => x.TenantId == tid && x.Id == userId)
.Select(x => x.SecurityStamp)
.FirstOrDefaultAsync(ct);
}
public async Task BumpUserSecurityStampAsync(Guid userId, CancellationToken ct = default)
{
var tid = TenantId();
var db = _scope.Get<AppDbContext>();
var u = await db.Users.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == userId, ct);
if (u is null) return;
u.SecurityStamp = Guid.NewGuid().ToString("N");
await db.SaveChangesAsync(ct);
}
}

View File

@@ -0,0 +1,51 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Security;
using AMREZ.EOP.Infrastructures.Options;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace AMREZ.EOP.Infrastructures.Security;
public sealed class JwtFactory : IJwtFactory
{
private readonly ITenantResolver _resolver;
private readonly IHttpContextAccessor _http;
private const string Issuer = "amrez.eop";
private const string Audience = "amrez.eop.clients";
private const int AccessMinutes = 10;
public JwtFactory(ITenantResolver resolver, IHttpContextAccessor http)
{
_resolver = resolver;
_http = http;
}
public (string token, DateTimeOffset expiresAt) CreateAccessToken(IEnumerable<Claim> claims)
{
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
var tenant = _resolver.Resolve(http) ?? throw new InvalidOperationException("No tenant context");
var material = !string.IsNullOrWhiteSpace(tenant.Id) ? tenant.Id! : tenant.TenantKey!;
var keyBytes = SHA256.HashData(Encoding.UTF8.GetBytes(material));
var cred = new SigningCredentials(new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256);
var now = DateTimeOffset.UtcNow;
var exp = now.AddMinutes(AccessMinutes);
var jwt = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
notBefore: now.UtcDateTime,
expires: exp.UtcDateTime,
signingCredentials: cred);
return (new JwtSecurityTokenHandler().WriteToken(jwt), exp);
}
}

View File

@@ -28,7 +28,6 @@ public sealed class DefaultTenantResolver : ITenantResolver
var cid = http.TraceIdentifier;
var path = http.Request.Path.Value ?? string.Empty;
// ✅ ทางลัด: ขอ platform ตรง ๆ ด้วย hint
if (hint is string hs && string.Equals(hs, "@platform", StringComparison.OrdinalIgnoreCase))
{
if (_map.TryGetBySlug(PlatformSlug, out var platform))

View File

@@ -63,7 +63,6 @@ namespace AMREZ.EOP.Infrastructures.Tenancy;
}
}
// 3) Bootstrap 'public' ถ้ายังไม่มี (กัน chicken-and-egg)
if (!map.TryGetBySlug("public", out _))
{
map.UpsertTenant(

View File

@@ -18,7 +18,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
private readonly ITenantDbContextFactory _factory;
private readonly IConnectionMultiplexer? _redis;
private AppDbContext? _db;
private IDbContextTransaction? _tx;
private ITenantContext? _tenant;
@@ -31,40 +30,38 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
{
_scope = scope;
_factory = factory;
_redis = redis; // optional; null ได้
_redis = redis;
}
public async Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default)
{
if (_db is not null) return;
if (_tx is not null) return;
_tenant = tenant ?? throw new ArgumentNullException(nameof(tenant));
_scope.EnsureForTenant(tenant);
_db = _scope.Get<AppDbContext>();
_tx = await _db.Database.BeginTransactionAsync(isolation, ct);
var db = _scope.Get<AppDbContext>();
_tx = await db.Database.BeginTransactionAsync(isolation, ct);
}
public async Task CommitAsync(CancellationToken ct = default)
{
if (_db is null) return; // ยังไม่ Begin
if (_tx is null) return;
// track entities ที่เปลี่ยน (เพื่อ invalidate cache แบบแม่นขึ้น)
var changedUsers = _db.ChangeTracker.Entries<User>()
var db = _scope.Get<AppDbContext>();
var changedUsers = db.ChangeTracker.Entries<User>()
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
.Select(e => e.Entity)
.ToList();
var changedIdentities = _db.ChangeTracker.Entries<UserIdentity>()
var changedIdentities = db.ChangeTracker.Entries<UserIdentity>()
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
.Select(e => e.Entity)
.ToList();
await _db.SaveChangesAsync(ct);
await db.SaveChangesAsync(ct);
await _tx.CommitAsync(ct);
if (_tx is not null)
await _tx.CommitAsync(ct);
// optional: invalidate/refresh Redis (ล่มก็ไม่พัง UoW)
if (_redis is not null && _tenant is not null && (changedUsers.Count > 0 || changedIdentities.Count > 0))
{
var r = _redis.GetDatabase();
@@ -75,8 +72,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
{
var keyId = $"eop:{tenantId}:user:id:{u.Id:N}";
tasks.Add(r.KeyDeleteAsync(keyId));
// refresh cache (ถ้าต้องการ)
var payload = JsonSerializer.Serialize(u);
var ttl = TimeSpan.FromMinutes(5);
tasks.Add(r.StringSetAsync(keyId, payload, ttl));
@@ -88,7 +83,7 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
tasks.Add(r.KeyDeleteAsync(k));
}
try { await Task.WhenAll(tasks); } catch { /* swallow */ }
try { await Task.WhenAll(tasks); } catch { }
}
await DisposeAsync();
@@ -109,11 +104,9 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
public ValueTask DisposeAsync()
{
try { _tx?.Dispose(); } catch { /* ignore */ }
try { _db?.Dispose(); } catch { /* ignore */ }
_tx = null; _db = null; _tenant = null;
try { _tx?.Dispose(); } catch { }
_tx = null;
_tenant = null;
return ValueTask.CompletedTask;
}
}