Add Master Data
This commit is contained in:
@@ -1,15 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||
using AMREZ.EOP.Domain.Entities.Common;
|
||||
using AMREZ.EOP.Domain.Entities.Customers;
|
||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
using AMREZ.EOP.Domain.Entities.MasterData;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Route = AMREZ.EOP.Domain.Entities.MasterData.Route;
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
// ===== Auth =====
|
||||
public DbSet<User> Users => Set<User>();
|
||||
@@ -36,14 +43,170 @@ public class AppDbContext : DbContext
|
||||
public DbSet<TenantConfig> Tenants => Set<TenantConfig>();
|
||||
public DbSet<TenantDomain> TenantDomains => Set<TenantDomain>();
|
||||
|
||||
// ===== Customers =====
|
||||
public DbSet<CustomerProfile> CustomerProfiles => Set<CustomerProfile>();
|
||||
public DbSet<PersonProfile> PersonProfiles => Set<PersonProfile>();
|
||||
public DbSet<OrganizationProfile> OrganizationProfiles => Set<OrganizationProfile>();
|
||||
public DbSet<Address> Addresses => Set<Address>();
|
||||
public DbSet<CustomerAddress> CustomerAddresses => Set<CustomerAddress>();
|
||||
public DbSet<CustomerContact> CustomerContacts => Set<CustomerContact>();
|
||||
public DbSet<CustomerTag> CustomerTags => Set<CustomerTag>();
|
||||
|
||||
// ===== Master Data =====
|
||||
public DbSet<Brand> Brands => Set<Brand>();
|
||||
public DbSet<BrandBlock> BrandBlocks => Set<BrandBlock>();
|
||||
|
||||
public DbSet<Category> Categories => Set<Category>();
|
||||
public DbSet<CategoryBlock> CategoryBlocks => Set<CategoryBlock>();
|
||||
public DbSet<CategoryExt> CategoryExts => Set<CategoryExt>();
|
||||
|
||||
public DbSet<Uom> Uoms => Set<Uom>();
|
||||
public DbSet<UomBlock> UomBlocks => Set<UomBlock>();
|
||||
|
||||
public DbSet<Currency> Currencies => Set<Currency>();
|
||||
public DbSet<CurrencyBlock> CurrencyBlocks => Set<CurrencyBlock>();
|
||||
|
||||
public DbSet<Country> Countries => Set<Country>();
|
||||
public DbSet<CountryBlock> CountryBlocks => Set<CountryBlock>();
|
||||
|
||||
public DbSet<Manufacturer> Manufacturers => Set<Manufacturer>();
|
||||
public DbSet<ManufacturerBlock> ManufacturerBlocks => Set<ManufacturerBlock>();
|
||||
|
||||
public DbSet<Market> Markets => Set<Market>();
|
||||
public DbSet<MarketBlock> MarketBlocks => Set<MarketBlock>();
|
||||
|
||||
public DbSet<Language> Languages => Set<Language>();
|
||||
public DbSet<LanguageBlock> LanguageBlocks => Set<LanguageBlock>();
|
||||
|
||||
public DbSet<ComplianceStatus> ComplianceStatuses => Set<ComplianceStatus>();
|
||||
public DbSet<ComplianceStatusBlock> ComplianceStatusBlocks => Set<ComplianceStatusBlock>();
|
||||
|
||||
public DbSet<QaStage> QaStages => Set<QaStage>();
|
||||
public DbSet<QaStageBlock> QaStageBlocks => Set<QaStageBlock>();
|
||||
|
||||
public DbSet<Route> Routes => Set<Route>();
|
||||
public DbSet<RouteBlock> RouteBlocks => Set<RouteBlock>();
|
||||
|
||||
public DbSet<RxSchedule> RxSchedules => Set<RxSchedule>();
|
||||
public DbSet<RxScheduleBlock> RxScheduleBlocks => Set<RxScheduleBlock>();
|
||||
|
||||
public DbSet<StabilityStatus> StabilityStatuses => Set<StabilityStatus>();
|
||||
public DbSet<StabilityStatusBlock> StabilityStatusBlocks => Set<StabilityStatusBlock>();
|
||||
|
||||
public DbSet<RiskClass> RiskClasses => Set<RiskClass>();
|
||||
public DbSet<RiskClassBlock> RiskClassBlocks => Set<RiskClassBlock>();
|
||||
|
||||
public DbSet<SterilizationMethod> SterilizationMethods => Set<SterilizationMethod>();
|
||||
public DbSet<SterilizationMethodBlock> SterilizationMethodBlocks => Set<SterilizationMethodBlock>();
|
||||
|
||||
public DbSet<QcStatus> QcStatuses => Set<QcStatus>();
|
||||
public DbSet<QcStatusBlock> QcStatusBlocks => Set<QcStatusBlock>();
|
||||
|
||||
public DbSet<DocControlStatus> DocControlStatuses => Set<DocControlStatus>();
|
||||
public DbSet<DocControlStatusBlock> DocControlStatusBlocks => Set<DocControlStatusBlock>();
|
||||
|
||||
public DbSet<RecallClass> RecallClasses => Set<RecallClass>();
|
||||
public DbSet<RecallClassBlock> RecallClassBlocks => Set<RecallClassBlock>();
|
||||
|
||||
public DbSet<PackingGroup> PackingGroups => Set<PackingGroup>();
|
||||
public DbSet<PackingGroupBlock> PackingGroupBlocks => Set<PackingGroupBlock>();
|
||||
|
||||
public DbSet<Vvm> Vvms => Set<Vvm>();
|
||||
public DbSet<VvmBlock> VvmBlocks => Set<VvmBlock>();
|
||||
|
||||
public DbSet<FuncTest> FuncTests => Set<FuncTest>();
|
||||
public DbSet<FuncTestBlock> FuncTestBlocks => Set<FuncTestBlock>();
|
||||
|
||||
public DbSet<HazardClass> HazardClasses => Set<HazardClass>();
|
||||
public DbSet<HazardClassBlock> HazardClassBlocks => Set<HazardClassBlock>();
|
||||
|
||||
public DbSet<Species> Species => Set<Species>();
|
||||
public DbSet<SpeciesBlock> SpeciesBlocks => Set<SpeciesBlock>();
|
||||
|
||||
public DbSet<Allergen> Allergens => Set<Allergen>();
|
||||
public DbSet<AllergenBlock> AllergenBlocks => Set<AllergenBlock>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
// JSON converters/comparers
|
||||
var jsonOpts = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
||||
|
||||
var dictStrConverter = new ValueConverter<Dictionary<string, string>?, string?>(
|
||||
v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts),
|
||||
v => string.IsNullOrWhiteSpace(v)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, string>>(v!, jsonOpts)!);
|
||||
|
||||
var dictStrComparer = new ValueComparer<Dictionary<string, string>?>(
|
||||
(l, r) => JsonSerializer.Serialize(l, jsonOpts) == JsonSerializer.Serialize(r, jsonOpts),
|
||||
v => v == null ? 0 : JsonSerializer.Serialize(v, jsonOpts).GetHashCode(),
|
||||
v => v == null
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, string>>(JsonSerializer.Serialize(v, jsonOpts),
|
||||
jsonOpts));
|
||||
|
||||
var dictObjConverter = new ValueConverter<Dictionary<string, object>?, string?>(
|
||||
v => v == null ? null : JsonSerializer.Serialize(v, jsonOpts),
|
||||
v => string.IsNullOrWhiteSpace(v)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(v!, jsonOpts)!);
|
||||
|
||||
var dictObjComparer = new ValueComparer<Dictionary<string, object>?>(
|
||||
(l, r) => JsonSerializer.Serialize(l, jsonOpts) == JsonSerializer.Serialize(r, jsonOpts),
|
||||
v => v == null ? 0 : JsonSerializer.Serialize(v, jsonOpts).GetHashCode(),
|
||||
v => v == null
|
||||
? null
|
||||
: JsonSerializer.Deserialize<Dictionary<string, object>>(JsonSerializer.Serialize(v, jsonOpts),
|
||||
jsonOpts));
|
||||
|
||||
void ConfigureMasterBase<TEntity>(string table, string schema = "master")
|
||||
where TEntity : MasterBase
|
||||
{
|
||||
var b = model.Entity<TEntity>();
|
||||
b.ToTable(table, schema);
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(x => new { x.TenantId, x.Id });
|
||||
|
||||
b.Property(x => x.Scope).IsRequired().HasDefaultValue("global");
|
||||
b.Property(x => x.Code).IsRequired();
|
||||
b.Property(x => x.Name).IsRequired();
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.IsSystem).HasDefaultValue(false);
|
||||
|
||||
var nameI18nProp = b.Property(x => x.NameI18n).HasConversion(dictStrConverter);
|
||||
nameI18nProp.Metadata.SetValueComparer(dictStrComparer);
|
||||
nameI18nProp.HasColumnType("jsonb");
|
||||
|
||||
var metaProp = b.Property(x => x.Meta).HasConversion(dictObjConverter);
|
||||
metaProp.Metadata.SetValueComparer(dictObjComparer);
|
||||
metaProp.HasColumnType("jsonb");
|
||||
|
||||
b.HasIndex(x => new { x.Scope, x.TenantId, x.Code }).IsUnique();
|
||||
// b.HasIndex(x => new { x.TenantId, x.OverridesGlobalId }).IsUnique().HasFilter("\"OverridesGlobalId\" IS NOT NULL");
|
||||
}
|
||||
|
||||
void ConfigureBlockBase<TBlock, TMaster>(string table, string schema = "master")
|
||||
where TBlock : MasterBlockBase
|
||||
where TMaster : MasterBase
|
||||
{
|
||||
var b = model.Entity<TBlock>();
|
||||
b.ToTable(table, schema);
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasIndex(x => new { x.TenantId, x.GlobalId }).IsUnique();
|
||||
b.HasIndex(x => x.GlobalId);
|
||||
|
||||
b.HasOne<TMaster>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.GlobalId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
// ====== Tenancy (meta) ======
|
||||
model.Entity<TenantConfig>(b =>
|
||||
{
|
||||
b.ToTable("tenants", schema: "meta");
|
||||
b.HasKey(x => x.TenantKey); // PK = key (slug)
|
||||
b.HasAlternateKey(x => x.TenantId); // AK = GUID
|
||||
b.HasKey(x => x.TenantKey);
|
||||
b.HasAlternateKey(x => x.TenantId);
|
||||
b.HasIndex(x => x.TenantId).IsUnique();
|
||||
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired();
|
||||
@@ -51,8 +214,7 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.ConnectionString);
|
||||
b.Property(x => x.Mode).IsRequired();
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
.HasColumnName("updated_at_utc")
|
||||
b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.HasIndex(x => x.IsActive);
|
||||
});
|
||||
@@ -62,11 +224,10 @@ 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); // optional
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128);
|
||||
b.Property(x => x.IsPlatformBaseDomain).HasDefaultValue(false);
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
.HasColumnName("updated_at_utc")
|
||||
b.Property(x => x.UpdatedAtUtc).HasColumnName("updated_at_utc")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
|
||||
b.HasIndex(x => x.TenantKey);
|
||||
@@ -84,8 +245,6 @@ 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();
|
||||
@@ -105,14 +264,13 @@ public class AppDbContext : DbContext
|
||||
|
||||
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");
|
||||
.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);
|
||||
.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 =>
|
||||
@@ -125,10 +283,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 =>
|
||||
@@ -141,10 +299,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 =>
|
||||
@@ -155,10 +313,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 =>
|
||||
@@ -171,10 +329,10 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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 =>
|
||||
@@ -209,16 +367,16 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<RolePermission>(b =>
|
||||
@@ -229,16 +387,16 @@ public class AppDbContext : DbContext
|
||||
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);
|
||||
.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);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PermissionId })
|
||||
.HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// ====== HR ======
|
||||
@@ -256,7 +414,7 @@ public class AppDbContext : DbContext
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id }) // <-- เปลี่ยนตรงนี้
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
@@ -272,10 +430,10 @@ public class AppDbContext : DbContext
|
||||
b.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
|
||||
|
||||
b.HasOne(x => x.Parent)
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<Position>(b =>
|
||||
@@ -301,22 +459,22 @@ public class AppDbContext : DbContext
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.StartDate });
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Employments)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany(p => p.Employments)
|
||||
.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 => new { x.TenantId, x.DepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
.WithMany()
|
||||
.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 => new { x.TenantId, x.PositionId })
|
||||
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PositionId })
|
||||
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
model.Entity<EmployeeAddress>(b =>
|
||||
@@ -330,10 +488,10 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.Country).IsRequired().HasMaxLength(64);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Addresses)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany(p => p.Addresses)
|
||||
.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 +505,10 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.Relationship).IsRequired().HasMaxLength(64);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.EmergencyContacts)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany(p => p.EmergencyContacts)
|
||||
.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 });
|
||||
});
|
||||
@@ -365,28 +523,280 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.AccountHolder).IsRequired().HasMaxLength(128);
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.BankAccounts)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.WithMany(p => p.BankAccounts)
|
||||
.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 });
|
||||
});
|
||||
|
||||
// ====== Enums as ints ======
|
||||
// ====== Customers ======
|
||||
model.Entity<CustomerProfile>(b =>
|
||||
{
|
||||
b.ToTable("customer_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(x => new { x.TenantId, x.Id });
|
||||
|
||||
b.Property(x => x.DisplayName).HasMaxLength(256).IsRequired();
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Status).IsRequired();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.DisplayName });
|
||||
|
||||
b.HasMany(x => x.Contacts)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.Addresses)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.Tags)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Person)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey<PersonProfile>(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey<CustomerProfile>(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Organization)
|
||||
.WithOne(x => x.Customer)
|
||||
.HasForeignKey<OrganizationProfile>(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey<CustomerProfile>(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<PersonProfile>(b =>
|
||||
{
|
||||
b.ToTable("person_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Prefix).HasMaxLength(32);
|
||||
b.Property(x => x.FirstNameTh).HasMaxLength(128);
|
||||
b.Property(x => x.LastNameTh).HasMaxLength(128);
|
||||
b.Property(x => x.FirstNameEn).HasMaxLength(128);
|
||||
b.Property(x => x.LastNameEn).HasMaxLength(128);
|
||||
b.Property(x => x.NationalId).HasMaxLength(13);
|
||||
b.Property(x => x.PassportNo).HasMaxLength(32);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId }).IsUnique();
|
||||
});
|
||||
|
||||
model.Entity<OrganizationProfile>(b =>
|
||||
{
|
||||
b.ToTable("organization_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.LegalNameTh).HasMaxLength(256).IsRequired();
|
||||
b.Property(x => x.LegalNameEn).HasMaxLength(256);
|
||||
b.Property(x => x.RegistrationNo).HasMaxLength(64);
|
||||
b.Property(x => x.TaxId13).HasMaxLength(13);
|
||||
b.Property(x => x.BranchCode).HasMaxLength(5);
|
||||
b.Property(x => x.CompanyType).HasMaxLength(64);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId }).IsUnique();
|
||||
b.HasIndex(x => new { x.TenantId, x.TaxId13, x.BranchCode }).IsUnique()
|
||||
.HasFilter("\"TaxId13\" IS NOT NULL");
|
||||
});
|
||||
|
||||
model.Entity<CustomerContact>(b =>
|
||||
{
|
||||
b.ToTable("customer_contacts");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Value).HasMaxLength(256).IsRequired();
|
||||
b.Property(x => x.IsPrimary).HasDefaultValue(false);
|
||||
b.Property(x => x.IsVerified).HasDefaultValue(false);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Type });
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Type, x.IsPrimary })
|
||||
.HasDatabaseName("ux_customer_contact_primary_per_type")
|
||||
.IsUnique()
|
||||
.HasFilter("\"IsPrimary\" = TRUE");
|
||||
});
|
||||
|
||||
model.Entity<Address>(b =>
|
||||
{
|
||||
b.ToTable("addresses");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(x => new { x.TenantId, x.Id });
|
||||
|
||||
b.Property(x => x.Line1).HasMaxLength(256);
|
||||
b.Property(x => x.Line2).HasMaxLength(256);
|
||||
b.Property(x => x.Village).HasMaxLength(128);
|
||||
b.Property(x => x.Soi).HasMaxLength(128);
|
||||
b.Property(x => x.Road).HasMaxLength(128);
|
||||
b.Property(x => x.Subdistrict).HasMaxLength(128);
|
||||
b.Property(x => x.District).HasMaxLength(128);
|
||||
b.Property(x => x.Province).HasMaxLength(128);
|
||||
b.Property(x => x.PostalCode).HasMaxLength(10);
|
||||
b.Property(x => x.CountryCode).HasMaxLength(2).IsRequired();
|
||||
});
|
||||
|
||||
model.Entity<CustomerAddress>(b =>
|
||||
{
|
||||
b.ToTable("customer_addresses");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.Property(x => x.Label).IsRequired();
|
||||
b.Property(x => x.IsDefault).HasDefaultValue(false);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Label });
|
||||
|
||||
b.HasOne(x => x.Customer)
|
||||
.WithMany(c => c.Addresses)
|
||||
.HasForeignKey(x => new { x.TenantId, x.CustomerProfileId })
|
||||
.HasPrincipalKey(c => new { c.TenantId, c.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Address)
|
||||
.WithMany(a => a.CustomerLinks)
|
||||
.HasForeignKey(x => new { x.TenantId, x.AddressId })
|
||||
.HasPrincipalKey(a => new { a.TenantId, a.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Label, x.IsDefault })
|
||||
.HasDatabaseName("ux_default_customer_address_per_label")
|
||||
.IsUnique()
|
||||
.HasFilter("\"IsDefault\" = TRUE");
|
||||
});
|
||||
|
||||
model.Entity<CustomerTag>(b =>
|
||||
{
|
||||
b.ToTable("customer_tags");
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.Tag).HasMaxLength(64).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.CustomerProfileId, x.Tag }).IsUnique();
|
||||
});
|
||||
|
||||
// ===== Master: mapping =====
|
||||
ConfigureMasterBase<Brand>("brands");
|
||||
ConfigureBlockBase<BrandBlock, Brand>("brand_blocks");
|
||||
|
||||
model.Entity<Category>(b =>
|
||||
{
|
||||
ConfigureMasterBase<Category>("categories");
|
||||
b.HasOne(x => x.Parent)
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentId })
|
||||
.HasPrincipalKey(x => new { x.TenantId, x.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasIndex(x => x.ParentId);
|
||||
b.HasIndex(x => x.Path);
|
||||
b.HasIndex(x => new { x.Scope, x.TenantId, x.SortOrder });
|
||||
});
|
||||
ConfigureBlockBase<CategoryBlock, Category>("category_blocks");
|
||||
|
||||
model.Entity<CategoryExt>(b =>
|
||||
{
|
||||
b.ToTable("category_exts", "master");
|
||||
b.HasKey(x => x.CategoryId);
|
||||
b.HasOne(x => x.Category).WithOne().HasForeignKey<CategoryExt>(x => x.CategoryId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
var chVisProp = b.Property(x => x.ChannelVisibility).HasConversion(dictObjConverter);
|
||||
chVisProp.Metadata.SetValueComparer(dictObjComparer);
|
||||
chVisProp.HasColumnType("jsonb");
|
||||
|
||||
var facetSchemaProp = b.Property(x => x.FacetSchema).HasConversion(dictObjConverter);
|
||||
facetSchemaProp.Metadata.SetValueComparer(dictObjComparer);
|
||||
facetSchemaProp.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
ConfigureMasterBase<Uom>("uoms");
|
||||
ConfigureBlockBase<UomBlock, Uom>("uom_blocks");
|
||||
|
||||
ConfigureMasterBase<Currency>("currencies");
|
||||
ConfigureBlockBase<CurrencyBlock, Currency>("currency_blocks");
|
||||
|
||||
ConfigureMasterBase<Country>("countries");
|
||||
ConfigureBlockBase<CountryBlock, Country>("country_blocks");
|
||||
|
||||
ConfigureMasterBase<Manufacturer>("manufacturers");
|
||||
ConfigureBlockBase<ManufacturerBlock, Manufacturer>("manufacturer_blocks");
|
||||
|
||||
ConfigureMasterBase<Market>("markets");
|
||||
ConfigureBlockBase<MarketBlock, Market>("market_blocks");
|
||||
|
||||
ConfigureMasterBase<Language>("languages");
|
||||
ConfigureBlockBase<LanguageBlock, Language>("language_blocks");
|
||||
|
||||
ConfigureMasterBase<ComplianceStatus>("compliance_statuses");
|
||||
ConfigureBlockBase<ComplianceStatusBlock, ComplianceStatus>("compliance_status_blocks");
|
||||
|
||||
ConfigureMasterBase<QaStage>("qa_stages");
|
||||
ConfigureBlockBase<QaStageBlock, QaStage>("qa_stage_blocks");
|
||||
|
||||
ConfigureMasterBase<Route>("routes");
|
||||
ConfigureBlockBase<RouteBlock, Route>("route_blocks");
|
||||
|
||||
ConfigureMasterBase<RxSchedule>("rx_schedules");
|
||||
ConfigureBlockBase<RxScheduleBlock, RxSchedule>("rx_schedule_blocks");
|
||||
|
||||
ConfigureMasterBase<StabilityStatus>("stability_statuses");
|
||||
ConfigureBlockBase<StabilityStatusBlock, StabilityStatus>("stability_status_blocks");
|
||||
|
||||
ConfigureMasterBase<RiskClass>("risk_classes");
|
||||
ConfigureBlockBase<RiskClassBlock, RiskClass>("risk_class_blocks");
|
||||
|
||||
ConfigureMasterBase<SterilizationMethod>("sterilization_methods");
|
||||
ConfigureBlockBase<SterilizationMethodBlock, SterilizationMethod>("sterilization_method_blocks");
|
||||
|
||||
ConfigureMasterBase<QcStatus>("qc_statuses");
|
||||
ConfigureBlockBase<QcStatusBlock, QcStatus>("qc_status_blocks");
|
||||
|
||||
ConfigureMasterBase<DocControlStatus>("doc_control_statuses");
|
||||
ConfigureBlockBase<DocControlStatusBlock, DocControlStatus>("doc_control_status_blocks");
|
||||
|
||||
ConfigureMasterBase<RecallClass>("recall_classes");
|
||||
ConfigureBlockBase<RecallClassBlock, RecallClass>("recall_class_blocks");
|
||||
|
||||
ConfigureMasterBase<PackingGroup>("packing_groups");
|
||||
ConfigureBlockBase<PackingGroupBlock, PackingGroup>("packing_group_blocks");
|
||||
|
||||
ConfigureMasterBase<Vvm>("vvms");
|
||||
ConfigureBlockBase<VvmBlock, Vvm>("vvm_blocks");
|
||||
|
||||
ConfigureMasterBase<FuncTest>("func_tests");
|
||||
ConfigureBlockBase<FuncTestBlock, FuncTest>("func_test_blocks");
|
||||
|
||||
ConfigureMasterBase<HazardClass>("hazard_classes");
|
||||
ConfigureBlockBase<HazardClassBlock, HazardClass>("hazard_class_blocks");
|
||||
|
||||
ConfigureMasterBase<Species>("species");
|
||||
ConfigureBlockBase<SpeciesBlock, Species>("species_blocks");
|
||||
|
||||
ConfigureMasterBase<Allergen>("allergens");
|
||||
ConfigureBlockBase<AllergenBlock, Allergen>("allergen_blocks");
|
||||
|
||||
// ====== Enum conversions ======
|
||||
model.Entity<UserIdentity>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserMfaFactor>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<UserExternalAccount>().Property(x => x.Provider).HasConversion<int>();
|
||||
model.Entity<Employment>().Property(x => x.EmploymentType).HasConversion<int>();
|
||||
model.Entity<UserProfile>().Property(x => x.Gender).HasConversion<int?>();
|
||||
|
||||
model.Entity<CustomerProfile>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<CustomerProfile>().Property(x => x.Status).HasConversion<int>();
|
||||
model.Entity<CustomerContact>().Property(x => x.Type).HasConversion<int>();
|
||||
model.Entity<CustomerAddress>().Property(x => x.Label).HasConversion<int>();
|
||||
|
||||
// ====== BaseEntity common mapping ======
|
||||
foreach (var et in model.Model.GetEntityTypes()
|
||||
.Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
|
||||
foreach (var et in model.Model.GetEntityTypes().Where(t => typeof(BaseEntity).IsAssignableFrom(t.ClrType)))
|
||||
{
|
||||
var b = model.Entity(et.ClrType);
|
||||
|
||||
// Tenant
|
||||
b.Property<Guid>(nameof(BaseEntity.TenantId))
|
||||
.HasColumnName("tenant_id")
|
||||
.HasColumnType("uuid")
|
||||
@@ -395,23 +805,19 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
||||
|
||||
// ชื่อ 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'");
|
||||
b.HasCheckConstraint($"ck_{tn}_tenant_not_null", "tenant_id is not null");
|
||||
b.HasCheckConstraint($"ck_{tn}_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
}
|
||||
|
||||
// Audit
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnName("created_at")
|
||||
b.Property<DateTimeOffset>(nameof(BaseEntity.CreatedAt)).HasColumnName("created_at")
|
||||
.HasDefaultValueSql("now() at time zone 'utc'");
|
||||
b.Property<DateTimeOffset?>("UpdatedAt").HasColumnName("updated_at");
|
||||
b.Property<string?>("CreatedBy").HasColumnName("created_by");
|
||||
b.Property<string?>("UpdatedBy").HasColumnName("updated_by");
|
||||
b.Property<bool>("IsDeleted").HasColumnName("is_deleted").HasDefaultValue(false);
|
||||
b.Property<DateTimeOffset?>(nameof(BaseEntity.UpdatedAt)).HasColumnName("updated_at");
|
||||
b.Property<string?>(nameof(BaseEntity.CreatedBy)).HasColumnName("created_by");
|
||||
b.Property<string?>(nameof(BaseEntity.UpdatedBy)).HasColumnName("updated_by");
|
||||
b.Property<bool>(nameof(BaseEntity.IsDeleted)).HasColumnName("is_deleted").HasDefaultValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
2176
AMREZ.EOP.Infrastructures/Migrations/20251009040810_AddCustomer.Designer.cs
generated
Normal file
2176
AMREZ.EOP.Infrastructures/Migrations/20251009040810_AddCustomer.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,348 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCustomer : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "addresses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Line1 = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
Line2 = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
Village = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
Soi = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
Road = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
Subdistrict = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
District = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
Province = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
PostalCode = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: true),
|
||||
CountryCode = table.Column<string>(type: "character varying(2)", maxLength: 2, nullable: false),
|
||||
Verified = table.Column<bool>(type: "boolean", nullable: false),
|
||||
GeoLat = table.Column<double>(type: "double precision", nullable: true),
|
||||
GeoLng = table.Column<double>(type: "double precision", 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_addresses", x => x.Id);
|
||||
table.UniqueConstraint("AK_addresses_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_addresses_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "customer_profiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
DisplayName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Status = table.Column<int>(type: "integer", 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_customer_profiles", x => x.Id);
|
||||
table.UniqueConstraint("AK_customer_profiles_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_customer_profiles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_customer_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "customer_addresses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CustomerProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
AddressId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Label = table.Column<int>(type: "integer", nullable: false),
|
||||
IsDefault = table.Column<bool>(type: "boolean", nullable: false, defaultValue: 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_customer_addresses", x => x.Id);
|
||||
table.CheckConstraint("ck_customer_addresses_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_customer_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_customer_addresses_addresses_tenant_id_AddressId",
|
||||
columns: x => new { x.tenant_id, x.AddressId },
|
||||
principalTable: "addresses",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_customer_addresses_customer_profiles_tenant_id_CustomerProf~",
|
||||
columns: x => new { x.tenant_id, x.CustomerProfileId },
|
||||
principalTable: "customer_profiles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "customer_contacts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CustomerProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
IsVerified = table.Column<bool>(type: "boolean", nullable: false, defaultValue: 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_customer_contacts", x => x.Id);
|
||||
table.CheckConstraint("ck_customer_contacts_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_customer_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_customer_contacts_customer_profiles_tenant_id_CustomerProfi~",
|
||||
columns: x => new { x.tenant_id, x.CustomerProfileId },
|
||||
principalTable: "customer_profiles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "customer_tags",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CustomerProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Tag = table.Column<string>(type: "character varying(64)", maxLength: 64, 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_customer_tags", x => x.Id);
|
||||
table.CheckConstraint("ck_customer_tags_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_customer_tags_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_customer_tags_customer_profiles_tenant_id_CustomerProfileId",
|
||||
columns: x => new { x.tenant_id, x.CustomerProfileId },
|
||||
principalTable: "customer_profiles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "organization_profiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CustomerProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
LegalNameTh = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
LegalNameEn = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
RegistrationNo = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
|
||||
TaxId13 = table.Column<string>(type: "character varying(13)", maxLength: 13, nullable: true),
|
||||
BranchCode = table.Column<string>(type: "character varying(5)", maxLength: 5, nullable: true),
|
||||
CompanyType = table.Column<string>(type: "character varying(64)", maxLength: 64, 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_organization_profiles", x => x.Id);
|
||||
table.CheckConstraint("ck_organization_profiles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_organization_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_organization_profiles_customer_profiles_tenant_id_CustomerP~",
|
||||
columns: x => new { x.tenant_id, x.CustomerProfileId },
|
||||
principalTable: "customer_profiles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "person_profiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CustomerProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Prefix = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||
FirstNameTh = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
LastNameTh = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
FirstNameEn = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
LastNameEn = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
BirthDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
NationalId = table.Column<string>(type: "character varying(13)", maxLength: 13, nullable: true),
|
||||
PassportNo = table.Column<string>(type: "character varying(32)", maxLength: 32, 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),
|
||||
updated_by = table.Column<string>(type: "text", nullable: true),
|
||||
is_deleted = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_person_profiles", x => x.Id);
|
||||
table.CheckConstraint("ck_person_profiles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_person_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_person_profiles_customer_profiles_tenant_id_CustomerProfile~",
|
||||
columns: x => new { x.tenant_id, x.CustomerProfileId },
|
||||
principalTable: "customer_profiles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_addresses_tenant_id",
|
||||
table: "addresses",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_addresses_tenant_id",
|
||||
table: "customer_addresses",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_addresses_tenant_id_AddressId",
|
||||
table: "customer_addresses",
|
||||
columns: new[] { "tenant_id", "AddressId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_addresses_tenant_id_CustomerProfileId_Label",
|
||||
table: "customer_addresses",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId", "Label" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ux_default_customer_address_per_label",
|
||||
table: "customer_addresses",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId", "Label", "IsDefault" },
|
||||
unique: true,
|
||||
filter: "\"IsDefault\" = TRUE");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_contacts_tenant_id",
|
||||
table: "customer_contacts",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_contacts_tenant_id_CustomerProfileId_Type",
|
||||
table: "customer_contacts",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ux_customer_contact_primary_per_type",
|
||||
table: "customer_contacts",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId", "Type", "IsPrimary" },
|
||||
unique: true,
|
||||
filter: "\"IsPrimary\" = TRUE");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_profiles_tenant_id",
|
||||
table: "customer_profiles",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_profiles_tenant_id_DisplayName",
|
||||
table: "customer_profiles",
|
||||
columns: new[] { "tenant_id", "DisplayName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_tags_tenant_id",
|
||||
table: "customer_tags",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_customer_tags_tenant_id_CustomerProfileId_Tag",
|
||||
table: "customer_tags",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId", "Tag" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_organization_profiles_tenant_id",
|
||||
table: "organization_profiles",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_organization_profiles_tenant_id_CustomerProfileId",
|
||||
table: "organization_profiles",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_organization_profiles_tenant_id_TaxId13_BranchCode",
|
||||
table: "organization_profiles",
|
||||
columns: new[] { "tenant_id", "TaxId13", "BranchCode" },
|
||||
unique: true,
|
||||
filter: "\"TaxId13\" IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_person_profiles_tenant_id",
|
||||
table: "person_profiles",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_person_profiles_tenant_id_CustomerProfileId",
|
||||
table: "person_profiles",
|
||||
columns: new[] { "tenant_id", "CustomerProfileId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "customer_addresses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "customer_contacts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "customer_tags");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "organization_profiles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "person_profiles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "addresses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "customer_profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
5789
AMREZ.EOP.Infrastructures/Migrations/20251010092049_AddProductMasterData.Designer.cs
generated
Normal file
5789
AMREZ.EOP.Infrastructures/Migrations/20251010092049_AddProductMasterData.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -65,47 +65,16 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<User?> FindActiveByEmailAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
var norm = email.Trim().ToLowerInvariant();
|
||||
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cached = await r.StringGetAsync($"uidx:{norm}");
|
||||
if (cached.HasValue)
|
||||
{
|
||||
var hit = JsonSerializer.Deserialize<User>(cached!);
|
||||
if (hit?.IsActive == true) return hit;
|
||||
}
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var user = await (
|
||||
from u in db.Users.AsNoTracking()
|
||||
join i in db.UserIdentities.AsNoTracking() on u.Id equals i.UserId
|
||||
where u.IsActive
|
||||
&& i.Type == IdentityType.Email
|
||||
&& i.Identifier == norm
|
||||
select u
|
||||
).FirstOrDefaultAsync(ct);
|
||||
|
||||
if (user is not null && r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(user);
|
||||
var ttl = TimeSpan.FromMinutes(5);
|
||||
await r.StringSetAsync($"uidx:{norm}", payload, ttl);
|
||||
await r.StringSetAsync($"user:{user.Id:N}", payload, ttl);
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
return user;
|
||||
return await db.Users
|
||||
.AsNoTracking()
|
||||
.Include(u => u.UserRoles)
|
||||
.ThenInclude(ur => ur.Role)
|
||||
.Where(u => u.IsActive &&
|
||||
u.Identities.Any(i => i.Type == IdentityType.Email && i.Identifier == norm))
|
||||
.FirstOrDefaultAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
|
||||
@@ -142,11 +111,61 @@ public class UserRepository : IUserRepository
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
await r.StringSetAsync(IdentityEmailKey(tid.ToString(), email!), payload, ttl);
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default)
|
||||
public async Task<string[]> GetRoleCodesByUserIdAsync(Guid userId, Guid tenantId, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
var cacheKey = $"urole:{tenantId:N}:{userId:N}";
|
||||
|
||||
// ---- cache hit ----
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cached = await r.StringGetAsync(cacheKey);
|
||||
if (cached.HasValue)
|
||||
{
|
||||
var arr = JsonSerializer.Deserialize<string[]>(cached!) ?? Array.Empty<string>();
|
||||
if (arr.Length > 0) return arr;
|
||||
}
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
// NOTE:
|
||||
// - ไม่อ้างอิง r.TenantId เพื่อให้คอมไพล์ได้แม้ Role ยังไม่มีฟิลด์ TenantId
|
||||
// - ถ้าคุณเพิ่ม Role.TenantId แล้ว และต้องการกรองตาม tenant ให้เติม .Where(role => role.TenantId == tenantId)
|
||||
var roles = await db.UserRoles
|
||||
.AsNoTracking()
|
||||
.Where(ur => ur.UserId == userId)
|
||||
.Select(ur => ur.Role)
|
||||
.Where(role => role != null)
|
||||
.Select(role => role!.Code) // ใช้ Code เป็นค่าของ claim "role"
|
||||
.Distinct()
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
// ---- cache miss → set ----
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await r.StringSetAsync(cacheKey, JsonSerializer.Serialize(roles), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
catch { /* ignore cache errors */ }
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -162,18 +181,19 @@ public class UserRepository : IUserRepository
|
||||
|
||||
var entity = new UserIdentity
|
||||
{
|
||||
TenantId = tid,
|
||||
UserId = userId,
|
||||
Type = type,
|
||||
TenantId = tid,
|
||||
UserId = userId,
|
||||
Type = type,
|
||||
Identifier = norm,
|
||||
IsPrimary = isPrimary
|
||||
IsPrimary = isPrimary
|
||||
};
|
||||
|
||||
await db.UserIdentities.AddAsync(entity, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default)
|
||||
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -191,7 +211,8 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default)
|
||||
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -234,7 +255,8 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default)
|
||||
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
@@ -294,7 +316,8 @@ public class UserRepository : IUserRepository
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash, CancellationToken ct = default)
|
||||
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.UserSessions
|
||||
@@ -302,7 +325,8 @@ public class UserRepository : IUserRepository
|
||||
.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)
|
||||
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);
|
||||
@@ -319,7 +343,8 @@ public class UserRepository : IUserRepository
|
||||
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);
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(
|
||||
x => x.TenantId == tid && x.Id == sessionId && x.UserId == userId, ct);
|
||||
if (s is null) return 0;
|
||||
|
||||
s.RevokedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
Reference in New Issue
Block a user