Add Login Module
This commit is contained in:
@@ -4,9 +4,11 @@ using AMREZ.EOP.Contracts.DTOs.Authentications.AddEmailIdentity;
|
|||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.ChangePassword;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.ChangePassword;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.DisableMfa;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.DisableMfa;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.EnableTotp;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.EnableTotp;
|
||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.Login;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Login;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.LogoutAll;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.LogoutAll;
|
||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.Register;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Register;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.VerifyEmail;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.VerifyEmail;
|
||||||
using AMREZ.EOP.Domain.Shared.Contracts;
|
using AMREZ.EOP.Domain.Shared.Contracts;
|
||||||
@@ -32,14 +34,33 @@ public class AuthenticationController : ControllerBase
|
|||||||
private readonly ILogoutUseCase _logout;
|
private readonly ILogoutUseCase _logout;
|
||||||
private readonly ILogoutAllUseCase _logoutAll;
|
private readonly ILogoutAllUseCase _logoutAll;
|
||||||
|
|
||||||
|
private readonly IIssueTokenPairUseCase _issueTokens;
|
||||||
|
private readonly IRefreshUseCase _refresh;
|
||||||
|
|
||||||
public AuthenticationController(
|
public AuthenticationController(
|
||||||
ILoginUseCase login,
|
ILoginUseCase login,
|
||||||
IRegisterUseCase register,
|
IRegisterUseCase register,
|
||||||
IChangePasswordUseCase changePassword)
|
IChangePasswordUseCase changePassword,
|
||||||
|
IAddEmailIdentityUseCase addEmail,
|
||||||
|
IVerifyEmailUseCase verifyEmail,
|
||||||
|
IEnableTotpUseCase enableTotp,
|
||||||
|
IDisableMfaUseCase disableMfa,
|
||||||
|
ILogoutUseCase logout,
|
||||||
|
ILogoutAllUseCase logoutAll,
|
||||||
|
IIssueTokenPairUseCase issueTokens,
|
||||||
|
IRefreshUseCase refresh)
|
||||||
{
|
{
|
||||||
_login = login;
|
_login = login;
|
||||||
_register = register;
|
_register = register;
|
||||||
_changePassword = changePassword;
|
_changePassword = changePassword;
|
||||||
|
_addEmail = addEmail;
|
||||||
|
_verifyEmail = verifyEmail;
|
||||||
|
_enableTotp = enableTotp;
|
||||||
|
_disableMfa = disableMfa;
|
||||||
|
_logout = logout;
|
||||||
|
_logoutAll = logoutAll;
|
||||||
|
_issueTokens = issueTokens;
|
||||||
|
_refresh = refresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
@@ -51,7 +72,7 @@ public class AuthenticationController : ControllerBase
|
|||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new(ClaimTypes.NameIdentifier, res.UserId.ToString()),
|
new(ClaimTypes.NameIdentifier, res.UserId.ToString()),
|
||||||
new(ClaimTypes.Name, string.IsNullOrWhiteSpace(res.DisplayName) ? res.Email : res.DisplayName),
|
new(ClaimTypes.Name, res.Email),
|
||||||
new(ClaimTypes.Email, res.Email),
|
new(ClaimTypes.Email, res.Email),
|
||||||
new("tenant", res.TenantId)
|
new("tenant", res.TenantId)
|
||||||
};
|
};
|
||||||
@@ -59,9 +80,71 @@ public class AuthenticationController : ControllerBase
|
|||||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthPolicies.Scheme));
|
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthPolicies.Scheme));
|
||||||
await HttpContext.SignInAsync(AuthPolicies.Scheme, principal);
|
await HttpContext.SignInAsync(AuthPolicies.Scheme, principal);
|
||||||
|
|
||||||
return Ok(res);
|
var tokenPair = await _issueTokens.ExecuteAsync(new IssueTokenPairRequest()
|
||||||
|
{
|
||||||
|
UserId = res.UserId,
|
||||||
|
Tenant = res.TenantId,
|
||||||
|
Email = res.Email
|
||||||
|
}, ct);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(tokenPair.RefreshToken))
|
||||||
|
{
|
||||||
|
Response.Cookies.Append(
|
||||||
|
"refresh_token",
|
||||||
|
tokenPair.RefreshToken!,
|
||||||
|
new CookieOptions
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = true,
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
|
Expires = tokenPair.RefreshExpiresAt?.UtcDateTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
user = res,
|
||||||
|
access_token = tokenPair.AccessToken,
|
||||||
|
token_type = "Bearer",
|
||||||
|
expires_at = tokenPair.AccessExpiresAt
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("refresh")]
|
||||||
|
public async Task<IActionResult> Refresh([FromBody] RefreshRequest body, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var raw = string.IsNullOrWhiteSpace(body.RefreshToken)
|
||||||
|
? Request.Cookies["refresh_token"]
|
||||||
|
: body.RefreshToken;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(raw))
|
||||||
|
return Unauthorized(new { message = "Missing refresh token" });
|
||||||
|
|
||||||
|
var res = await _refresh.ExecuteAsync(body, ct);
|
||||||
|
if (res is null) return Unauthorized(new { message = "Invalid/expired refresh token" });
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(res.RefreshToken))
|
||||||
|
{
|
||||||
|
Response.Cookies.Append(
|
||||||
|
"refresh_token",
|
||||||
|
res.RefreshToken!,
|
||||||
|
new CookieOptions
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = true,
|
||||||
|
SameSite = SameSiteMode.Strict,
|
||||||
|
Expires = res.RefreshExpiresAt?.UtcDateTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
access_token = res.AccessToken,
|
||||||
|
token_type = "Bearer",
|
||||||
|
expires_at = res.AccessExpiresAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> Register([FromBody] RegisterRequest body, CancellationToken ct)
|
public async Task<IActionResult> Register([FromBody] RegisterRequest body, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
||||||
|
|
||||||
|
public interface IIssueTokenPairUseCase
|
||||||
|
{
|
||||||
|
Task<IssueTokenPairResponse> ExecuteAsync(IssueTokenPairRequest request, CancellationToken ct);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
||||||
|
|
||||||
|
public interface IRefreshUseCase
|
||||||
|
{
|
||||||
|
Task<RefreshResponse?> ExecuteAsync(RefreshRequest request, CancellationToken ct);
|
||||||
|
}
|
||||||
@@ -5,27 +5,34 @@ namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
|||||||
|
|
||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
{
|
{
|
||||||
|
// ===== Users/Identities/Password/MFA (ของเดิม) =====
|
||||||
Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default);
|
Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default);
|
||||||
Task<User?> FindActiveByEmailAsync(string email, CancellationToken ct = default);
|
Task<User?> FindActiveByEmailAsync(string email, CancellationToken ct = default);
|
||||||
Task<bool> EmailExistsAsync(string email, CancellationToken ct = default);
|
Task<bool> EmailExistsAsync(string email, CancellationToken ct = default);
|
||||||
Task AddAsync(User user, CancellationToken ct = default);
|
Task AddAsync(User user, CancellationToken ct = default);
|
||||||
|
|
||||||
// Identities
|
|
||||||
Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default);
|
Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default);
|
||||||
Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default);
|
Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default);
|
||||||
Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default);
|
Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default);
|
||||||
|
|
||||||
// Password
|
|
||||||
Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default);
|
Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default);
|
||||||
Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default);
|
Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default);
|
||||||
|
|
||||||
// MFA
|
|
||||||
Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default);
|
Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default);
|
||||||
Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default);
|
Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default);
|
||||||
Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default);
|
Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default);
|
||||||
|
|
||||||
// Sessions
|
// ===== Sessions (เก็บ refresh ไว้ใน session) =====
|
||||||
Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default);
|
Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default);
|
||||||
|
Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash, CancellationToken ct = default);
|
||||||
|
Task<bool> RotateSessionRefreshAsync(Guid tenantId, Guid sessionId, string newRefreshTokenHash, DateTimeOffset newIssuedAt, DateTimeOffset? newExpiresAt, CancellationToken ct = default);
|
||||||
Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default);
|
Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default);
|
||||||
Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default);
|
Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default);
|
||||||
|
Task<bool> IsSessionActiveAsync(Guid userId, Guid sessionId, CancellationToken ct = default);
|
||||||
|
|
||||||
|
// ===== Kill switches / stamps (ใช้สำหรับ revoke-all ระดับ tenant/user) =====
|
||||||
|
Task<string> GetTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default);
|
||||||
|
Task BumpTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default);
|
||||||
|
Task<string?> GetUserSecurityStampAsync(Guid userId, CancellationToken ct = default);
|
||||||
|
Task BumpUserSecurityStampAsync(Guid userId, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
8
AMREZ.EOP.Abstractions/Security/IJwtFactory.cs
Normal file
8
AMREZ.EOP.Abstractions/Security/IJwtFactory.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Abstractions.Security;
|
||||||
|
|
||||||
|
public interface IJwtFactory
|
||||||
|
{
|
||||||
|
(string token, DateTimeOffset expiresAt) CreateAccessToken(IEnumerable<Claim> claims);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||||
|
using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
||||||
|
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
||||||
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
|
using AMREZ.EOP.Abstractions.Security;
|
||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
||||||
|
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Application.UseCases.Authentications;
|
||||||
|
|
||||||
|
public sealed class IssueTokenPairUseCase : IIssueTokenPairUseCase
|
||||||
|
{
|
||||||
|
private readonly ITenantResolver _tenantResolver; // ctx/key for UoW
|
||||||
|
private readonly ITenantRepository _tenants; // get TenantId (Guid)
|
||||||
|
private readonly IUserRepository _users;
|
||||||
|
private readonly IJwtFactory _jwt;
|
||||||
|
private readonly IHttpContextAccessor _http;
|
||||||
|
private readonly IUnitOfWork _uow;
|
||||||
|
|
||||||
|
private const int RefreshDays = 14;
|
||||||
|
|
||||||
|
public IssueTokenPairUseCase(
|
||||||
|
ITenantResolver resolver,
|
||||||
|
ITenantRepository tenants,
|
||||||
|
IUserRepository users,
|
||||||
|
IJwtFactory jwt,
|
||||||
|
IHttpContextAccessor http,
|
||||||
|
IUnitOfWork uow)
|
||||||
|
{
|
||||||
|
_tenantResolver = resolver;
|
||||||
|
_tenants = tenants;
|
||||||
|
_users = users;
|
||||||
|
_jwt = jwt;
|
||||||
|
_http = http;
|
||||||
|
_uow = uow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IssueTokenPairResponse> ExecuteAsync(IssueTokenPairRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||||
|
|
||||||
|
var tenantCtx = _tenantResolver.Resolve(http, request);
|
||||||
|
if (tenantCtx is null) throw new InvalidOperationException("Cannot resolve tenant context");
|
||||||
|
|
||||||
|
await _uow.BeginAsync(tenantCtx, IsolationLevel.ReadCommitted, ct);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tn = await _tenants.GetAsync(tenantCtx.Id, ct);
|
||||||
|
var tenantId = tn.TenantId;
|
||||||
|
|
||||||
|
var refreshRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
|
||||||
|
var refreshHash = Sha256(refreshRaw);
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
var session = await _users.CreateSessionAsync(new UserSession
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TenantId = tenantId,
|
||||||
|
UserId = request.UserId,
|
||||||
|
RefreshTokenHash = refreshHash,
|
||||||
|
IssuedAt = now,
|
||||||
|
ExpiresAt = now.AddDays(RefreshDays),
|
||||||
|
DeviceId = http.Request.Headers["X-Device-Id"].FirstOrDefault(),
|
||||||
|
UserAgent = http.Request.Headers["User-Agent"].FirstOrDefault(),
|
||||||
|
IpAddress = http.Connection.RemoteIpAddress?.ToString()
|
||||||
|
}, ct);
|
||||||
|
|
||||||
|
// 6) tv / sstamp
|
||||||
|
var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct);
|
||||||
|
var sstamp = await _users.GetUserSecurityStampAsync(request.UserId, ct) ?? string.Empty;
|
||||||
|
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new("sub", request.UserId.ToString()),
|
||||||
|
new("tenant_id", tenantId.ToString()),
|
||||||
|
new("sid", session.Id.ToString()),
|
||||||
|
new("jti", Guid.NewGuid().ToString("N")),
|
||||||
|
new("tv", tv),
|
||||||
|
new("sstamp", sstamp)
|
||||||
|
};
|
||||||
|
var (access, accessExp) = _jwt.CreateAccessToken(claims);
|
||||||
|
|
||||||
|
await _uow.CommitAsync(ct);
|
||||||
|
|
||||||
|
return new IssueTokenPairResponse
|
||||||
|
{
|
||||||
|
AccessToken = access,
|
||||||
|
AccessExpiresAt = accessExp,
|
||||||
|
RefreshToken = refreshRaw, // raw ออกให้ client
|
||||||
|
RefreshExpiresAt = session.ExpiresAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await _uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Sha256(string raw)
|
||||||
|
{
|
||||||
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
|
||||||
|
return Convert.ToHexString(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,8 +39,7 @@ public sealed class LoginUseCase : ILoginUseCase
|
|||||||
|
|
||||||
await _uow.CommitAsync(ct);
|
await _uow.CommitAsync(ct);
|
||||||
|
|
||||||
// NOTE: ไม่ใช้ DisplayName ใน Entity แล้ว — ส่งกลับเป็นค่าว่าง/ไปดึงจาก HR ฝั่ง API
|
return new LoginResponse(user.Id , email, tenant.Id);
|
||||||
return new LoginResponse(user.Id, string.Empty, email, tenant.Id);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,12 +16,7 @@ public sealed class LogoutAllUseCase : ILogoutAllUseCase
|
|||||||
private readonly IHttpContextAccessor _http;
|
private readonly IHttpContextAccessor _http;
|
||||||
|
|
||||||
public LogoutAllUseCase(ITenantResolver r, IUnitOfWork uow, IUserRepository users, IHttpContextAccessor http)
|
public LogoutAllUseCase(ITenantResolver r, IUnitOfWork uow, IUserRepository users, IHttpContextAccessor http)
|
||||||
{
|
{ _resolver = r; _uow = uow; _users = users; _http = http; }
|
||||||
_resolver = r;
|
|
||||||
_uow = uow;
|
|
||||||
_users = users;
|
|
||||||
_http = http;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> ExecuteAsync(LogoutAllRequest request, CancellationToken ct = default)
|
public async Task<int> ExecuteAsync(LogoutAllRequest request, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
|||||||
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
||||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
||||||
|
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace AMREZ.EOP.Application.UseCases.Authentications;
|
namespace AMREZ.EOP.Application.UseCases.Authentications;
|
||||||
@@ -31,6 +32,10 @@ public sealed class LogoutUseCase : ILogoutUseCase
|
|||||||
await _uow.CommitAsync(ct);
|
await _uow.CommitAsync(ct);
|
||||||
return n > 0;
|
return n > 0;
|
||||||
}
|
}
|
||||||
catch { await _uow.RollbackAsync(ct); throw; }
|
catch
|
||||||
|
{
|
||||||
|
await _uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
102
AMREZ.EOP.Application/UseCases/Authentications/RefreshUseCase.cs
Normal file
102
AMREZ.EOP.Application/UseCases/Authentications/RefreshUseCase.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Data;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||||
|
using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
||||||
|
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
||||||
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
|
using AMREZ.EOP.Abstractions.Security;
|
||||||
|
using AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Application.UseCases.Authentications;
|
||||||
|
|
||||||
|
public sealed class RefreshUseCase : IRefreshUseCase
|
||||||
|
{
|
||||||
|
private readonly ITenantResolver _resolver;
|
||||||
|
private readonly ITenantRepository _tenants;
|
||||||
|
private readonly IUserRepository _users;
|
||||||
|
private readonly IJwtFactory _jwt;
|
||||||
|
private readonly IHttpContextAccessor _http;
|
||||||
|
private readonly IUnitOfWork _uow;
|
||||||
|
|
||||||
|
private const int RefreshDays = 14;
|
||||||
|
|
||||||
|
public RefreshUseCase(
|
||||||
|
ITenantResolver resolver,
|
||||||
|
ITenantRepository tenants,
|
||||||
|
IUserRepository users,
|
||||||
|
IJwtFactory jwt,
|
||||||
|
IHttpContextAccessor http,
|
||||||
|
IUnitOfWork uow)
|
||||||
|
{ _resolver = resolver; _tenants = tenants; _users = users; _jwt = jwt; _http = http; _uow = uow; }
|
||||||
|
|
||||||
|
public async Task<RefreshResponse?> ExecuteAsync(RefreshRequest request, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.RefreshToken)) return null;
|
||||||
|
|
||||||
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||||
|
var tenantCtx = _resolver.Resolve(http, request);
|
||||||
|
if (tenantCtx is null) return null;
|
||||||
|
|
||||||
|
await _uow.BeginAsync(tenantCtx, IsolationLevel.ReadCommitted, ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tn = await _tenants.GetAsync(tenantCtx.TenantKey, ct);
|
||||||
|
var tenantId = tn.TenantId;
|
||||||
|
|
||||||
|
var hash = Sha256(request.RefreshToken);
|
||||||
|
var session = await _users.FindSessionByRefreshHashAsync(tenantId, hash, ct);
|
||||||
|
if (session is null || session.RevokedAt.HasValue ||
|
||||||
|
(session.ExpiresAt.HasValue && session.ExpiresAt.Value <= DateTimeOffset.UtcNow))
|
||||||
|
{ await _uow.RollbackAsync(ct); return null; }
|
||||||
|
|
||||||
|
if (!await _users.IsSessionActiveAsync(session.UserId, session.Id, ct))
|
||||||
|
{ await _uow.RollbackAsync(ct); return null; }
|
||||||
|
|
||||||
|
var tv = await _users.GetTenantTokenVersionAsync(tenantId, ct);
|
||||||
|
var sstamp = await _users.GetUserSecurityStampAsync(session.UserId, ct) ?? string.Empty;
|
||||||
|
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new("sub", session.UserId.ToString()),
|
||||||
|
new("tenant_id", tenantId.ToString()),
|
||||||
|
new("sid", session.Id.ToString()),
|
||||||
|
new("jti", Guid.NewGuid().ToString("N")),
|
||||||
|
new("tv", tv),
|
||||||
|
new("sstamp", sstamp)
|
||||||
|
};
|
||||||
|
var (access, accessExp) = _jwt.CreateAccessToken(claims);
|
||||||
|
|
||||||
|
var newRaw = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));
|
||||||
|
var newHash = Sha256(newRaw);
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
var newExp = now.AddDays(RefreshDays);
|
||||||
|
|
||||||
|
var ok = await _users.RotateSessionRefreshAsync(tenantId, session.Id, newHash, now, newExp, ct);
|
||||||
|
if (!ok) { await _uow.RollbackAsync(ct); return null; }
|
||||||
|
|
||||||
|
await _uow.CommitAsync(ct);
|
||||||
|
|
||||||
|
return new RefreshResponse
|
||||||
|
{
|
||||||
|
AccessToken = access,
|
||||||
|
AccessExpiresAt = accessExp,
|
||||||
|
RefreshToken = newRaw,
|
||||||
|
RefreshExpiresAt = newExp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await _uow.RollbackAsync(ct);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Sha256(string raw)
|
||||||
|
{
|
||||||
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
|
||||||
|
return Convert.ToHexString(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,9 +64,9 @@ public sealed class RegisterUseCase : IRegisterUseCase
|
|||||||
IsActive = true,
|
IsActive = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// แนบอัตลักษณ์แบบ Email (เก็บในตารางลูก)
|
|
||||||
user.Identities.Add(new UserIdentity
|
user.Identities.Add(new UserIdentity
|
||||||
{
|
{
|
||||||
|
TenantId = tn.TenantId,
|
||||||
Type = IdentityType.Email,
|
Type = IdentityType.Email,
|
||||||
Identifier = emailNorm,
|
Identifier = emailNorm,
|
||||||
IsPrimary = true,
|
IsPrimary = true,
|
||||||
@@ -76,7 +76,6 @@ public sealed class RegisterUseCase : IRegisterUseCase
|
|||||||
await _users.AddAsync(user, ct);
|
await _users.AddAsync(user, ct);
|
||||||
await _uow.CommitAsync(ct);
|
await _uow.CommitAsync(ct);
|
||||||
|
|
||||||
// ไม่ส่ง DisplayName (ปล่อยให้ HR/Presentation สร้าง)
|
|
||||||
return new RegisterResponse(user.Id, string.Empty, emailNorm, tenant.Id);
|
return new RegisterResponse(user.Id, string.Empty, emailNorm, tenant.Id);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
||||||
|
|
||||||
|
public sealed class IssueTokenPairRequest
|
||||||
|
{
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public string Tenant { get; init; } = default!;
|
||||||
|
public string Email { get; init; } = default!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace AMREZ.EOP.Contracts.DTOs.Authentications.IssueTokenPair;
|
||||||
|
|
||||||
|
public sealed class IssueTokenPairResponse
|
||||||
|
{
|
||||||
|
public string AccessToken { get; init; } = default!;
|
||||||
|
public DateTimeOffset AccessExpiresAt { get; init; }
|
||||||
|
public string? RefreshToken { get; init; }
|
||||||
|
public DateTimeOffset? RefreshExpiresAt { get; init; }
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ namespace AMREZ.EOP.Contracts.DTOs.Authentications.Login;
|
|||||||
|
|
||||||
public sealed record LoginResponse(
|
public sealed record LoginResponse(
|
||||||
Guid UserId,
|
Guid UserId,
|
||||||
string DisplayName,
|
|
||||||
string Email,
|
string Email,
|
||||||
string TenantId
|
string TenantId
|
||||||
);
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
||||||
|
|
||||||
|
public sealed class RefreshRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ถ้าไม่ส่งมา จะไปอ่านจาก HttpOnly cookie ชื่อ "refresh_token" ใน Controller
|
||||||
|
/// </summary>
|
||||||
|
public string? RefreshToken { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace AMREZ.EOP.Contracts.DTOs.Authentications.Refresh;
|
||||||
|
|
||||||
|
public sealed class RefreshResponse
|
||||||
|
{
|
||||||
|
public string AccessToken { get; init; } = default!;
|
||||||
|
public DateTimeOffset AccessExpiresAt { get; init; }
|
||||||
|
public string? RefreshToken { get; init; }
|
||||||
|
public DateTimeOffset? RefreshExpiresAt { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using AMREZ.EOP.Domain.Entities.Common;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||||
|
|
||||||
|
public sealed class AccessTokenDeny : BaseEntity
|
||||||
|
{
|
||||||
|
public string Jti { get; set; } = default!;
|
||||||
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class Permission : BaseEntity
|
public sealed class Permission : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public string Code { get; set; } = default!; // e.g. "auth:session:read"
|
public string Code { get; set; } = default!; // e.g. "auth:session:read"
|
||||||
public string Name { get; set; } = default!;
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
|||||||
18
AMREZ.EOP.Domain/Entities/Authentications/RefreshToken.cs
Normal file
18
AMREZ.EOP.Domain/Entities/Authentications/RefreshToken.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using AMREZ.EOP.Domain.Entities.Common;
|
||||||
|
|
||||||
|
namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||||
|
|
||||||
|
public sealed class RefreshToken : BaseEntity
|
||||||
|
{
|
||||||
|
public string FamilyId { get; set; } = default!;
|
||||||
|
public string RefreshTokenHash { get; set; } = default!;
|
||||||
|
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public Guid SessionId { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
|
public DateTimeOffset? RevokedAt { get; set; }
|
||||||
|
|
||||||
|
public User User { get; set; } = default!;
|
||||||
|
public UserSession Session { get; set; } = default!;
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class Role : BaseEntity
|
public sealed class Role : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public string Code { get; set; } = default!; // system code, unique per tenant
|
public string Code { get; set; } = default!; // system code, unique per tenant
|
||||||
public string Name { get; set; } = default!;
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class RolePermission : BaseEntity
|
public sealed class RolePermission : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid RoleId { get; set; }
|
public Guid RoleId { get; set; }
|
||||||
public Guid PermissionId { get; set; }
|
public Guid PermissionId { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class User : BaseEntity
|
public sealed class User : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
|
|
||||||
public string PasswordHash { get; set; } = default!;
|
public string PasswordHash { get; set; } = default!;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserExternalAccount : BaseEntity
|
public sealed class UserExternalAccount : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public ExternalProvider Provider { get; set; }
|
public ExternalProvider Provider { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserIdentity : BaseEntity
|
public sealed class UserIdentity : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public IdentityType Type { get; set; }
|
public IdentityType Type { get; set; }
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserMfaFactor : BaseEntity
|
public sealed class UserMfaFactor : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public MfaType Type { get; set; }
|
public MfaType Type { get; set; }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserPasswordHistory : BaseEntity
|
public sealed class UserPasswordHistory : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public string PasswordHash { get; set; } = default!;
|
public string PasswordHash { get; set; } = default!;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserRole : BaseEntity
|
public sealed class UserRole : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
public Guid RoleId { get; set; }
|
public Guid RoleId { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
|||||||
|
|
||||||
public sealed class UserSession : BaseEntity
|
public sealed class UserSession : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public string RefreshTokenHash { get; set; } = default!;
|
public string RefreshTokenHash { get; set; } = default!;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace AMREZ.EOP.Domain.Entities.Common;
|
|||||||
public abstract class BaseEntity
|
public abstract class BaseEntity
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string TenantId { get; set; } = default!;
|
public Guid TenantId { get; set; } = default!;
|
||||||
|
|
||||||
public DateTimeOffset CreatedAt { get; set; }
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class Department : BaseEntity
|
public sealed class Department : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public string Code { get; set; } = default!;
|
public string Code { get; set; } = default!;
|
||||||
public string Name { get; set; } = default!;
|
public string Name { get; set; } = default!;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class EmergencyContact : BaseEntity
|
public sealed class EmergencyContact : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserProfileId { get; set; }
|
public Guid UserProfileId { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; } = default!;
|
public string Name { get; set; } = default!;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class EmployeeAddress : BaseEntity
|
public sealed class EmployeeAddress : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserProfileId { get; set; }
|
public Guid UserProfileId { get; set; }
|
||||||
|
|
||||||
public AddressType Type { get; set; } = AddressType.Home;
|
public AddressType Type { get; set; } = AddressType.Home;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class EmployeeBankAccount : BaseEntity
|
public sealed class EmployeeBankAccount : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserProfileId { get; set; }
|
public Guid UserProfileId { get; set; }
|
||||||
|
|
||||||
public string BankName { get; set; } = default!;
|
public string BankName { get; set; } = default!;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class Employment : BaseEntity
|
public sealed class Employment : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserProfileId { get; set; }
|
public Guid UserProfileId { get; set; }
|
||||||
|
|
||||||
public EmploymentType EmploymentType { get; set; } = EmploymentType.Permanent;
|
public EmploymentType EmploymentType { get; set; } = EmploymentType.Permanent;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class Position : BaseEntity
|
public sealed class Position : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public string Code { get; set; } = default!;
|
public string Code { get; set; } = default!;
|
||||||
public string Title { get; set; } = default!;
|
public string Title { get; set; } = default!;
|
||||||
public int? Level { get; set; }
|
public int? Level { get; set; }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
|||||||
|
|
||||||
public sealed class UserProfile : BaseEntity
|
public sealed class UserProfile : BaseEntity
|
||||||
{
|
{
|
||||||
public Guid TenantId { get; set; }
|
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
public string FirstName { get; set; } = default!;
|
public string FirstName { get; set; } = default!;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.9.17" />
|
<PackageReference Include="StackExchange.Redis" Version="2.9.17" />
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using AMREZ.EOP.Domain.Entities.Common;
|
|||||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
|
||||||
namespace AMREZ.EOP.Infrastructures.Data;
|
namespace AMREZ.EOP.Infrastructures.Data;
|
||||||
|
|
||||||
@@ -37,11 +38,14 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder model)
|
protected override void OnModelCreating(ModelBuilder model)
|
||||||
{
|
{
|
||||||
// ====== Global Tenancy Config (meta schema) — ไม่สืบทอด BaseEntity ======
|
// ====== Tenancy (meta) ======
|
||||||
model.Entity<TenantConfig>(b =>
|
model.Entity<TenantConfig>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable("tenants", schema: "meta");
|
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.TenantKey).HasMaxLength(128).IsRequired();
|
||||||
b.Property(x => x.Schema).HasMaxLength(128);
|
b.Property(x => x.Schema).HasMaxLength(128);
|
||||||
b.Property(x => x.ConnectionString);
|
b.Property(x => x.ConnectionString);
|
||||||
@@ -58,7 +62,7 @@ public class AppDbContext : DbContext
|
|||||||
b.ToTable("tenant_domains", schema: "meta");
|
b.ToTable("tenant_domains", schema: "meta");
|
||||||
b.HasKey(x => x.Domain);
|
b.HasKey(x => x.Domain);
|
||||||
b.Property(x => x.Domain).HasMaxLength(253).IsRequired();
|
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.IsPlatformBaseDomain).HasDefaultValue(false);
|
||||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||||
b.Property(x => x.UpdatedAtUtc)
|
b.Property(x => x.UpdatedAtUtc)
|
||||||
@@ -81,36 +85,13 @@ public class AppDbContext : DbContext
|
|||||||
b.ToTable("users");
|
b.ToTable("users");
|
||||||
b.HasKey(x => x.Id);
|
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.PasswordHash).IsRequired();
|
||||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||||
|
|
||||||
b.Property(x => x.AccessFailedCount).HasDefaultValue(0);
|
b.Property(x => x.AccessFailedCount).HasDefaultValue(0);
|
||||||
b.Property(x => x.MfaEnabled).HasDefaultValue(false);
|
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 =>
|
model.Entity<UserIdentity>(b =>
|
||||||
@@ -122,11 +103,16 @@ public class AppDbContext : DbContext
|
|||||||
b.Property(x => x.Identifier).IsRequired().HasMaxLength(256);
|
b.Property(x => x.Identifier).IsRequired().HasMaxLength(256);
|
||||||
b.Property(x => x.IsPrimary).HasDefaultValue(false);
|
b.Property(x => x.IsPrimary).HasDefaultValue(false);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier })
|
b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier }).IsUnique();
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.Type, x.IsPrimary })
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
model.Entity<UserMfaFactor>(b =>
|
model.Entity<UserMfaFactor>(b =>
|
||||||
@@ -136,8 +122,13 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.Property(x => x.Type).IsRequired();
|
b.Property(x => x.Type).IsRequired();
|
||||||
b.Property(x => x.Enabled).HasDefaultValue(true);
|
b.Property(x => x.Enabled).HasDefaultValue(true);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
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 =>
|
model.Entity<UserSession>(b =>
|
||||||
@@ -148,6 +139,12 @@ public class AppDbContext : DbContext
|
|||||||
b.Property(x => x.RefreshTokenHash).IsRequired();
|
b.Property(x => x.RefreshTokenHash).IsRequired();
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||||
b.HasIndex(x => new { x.TenantId, x.DeviceId });
|
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 =>
|
model.Entity<UserPasswordHistory>(b =>
|
||||||
@@ -156,6 +153,12 @@ public class AppDbContext : DbContext
|
|||||||
b.HasKey(x => x.Id);
|
b.HasKey(x => x.Id);
|
||||||
b.Property(x => x.PasswordHash).IsRequired();
|
b.Property(x => x.PasswordHash).IsRequired();
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.ChangedAt });
|
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 =>
|
model.Entity<UserExternalAccount>(b =>
|
||||||
@@ -165,15 +168,20 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.Property(x => x.Provider).IsRequired();
|
b.Property(x => x.Provider).IsRequired();
|
||||||
b.Property(x => x.Subject).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 })
|
b.HasOne(x => x.User)
|
||||||
.IsUnique();
|
.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 =>
|
model.Entity<Role>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable("roles");
|
b.ToTable("roles");
|
||||||
b.HasKey(x => x.Id);
|
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.Code).IsRequired().HasMaxLength(128);
|
||||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||||
@@ -185,6 +193,7 @@ public class AppDbContext : DbContext
|
|||||||
{
|
{
|
||||||
b.ToTable("permissions");
|
b.ToTable("permissions");
|
||||||
b.HasKey(x => x.Id);
|
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.Code).IsRequired().HasMaxLength(256);
|
||||||
b.Property(x => x.Name).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.HasKey(x => x.Id);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique();
|
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 =>
|
model.Entity<RolePermission>(b =>
|
||||||
@@ -206,6 +227,18 @@ public class AppDbContext : DbContext
|
|||||||
b.HasKey(x => x.Id);
|
b.HasKey(x => x.Id);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
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 ======
|
// ====== HR ======
|
||||||
@@ -213,22 +246,25 @@ public class AppDbContext : DbContext
|
|||||||
{
|
{
|
||||||
b.ToTable("user_profiles");
|
b.ToTable("user_profiles");
|
||||||
b.HasKey(x => x.Id);
|
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.FirstName).IsRequired().HasMaxLength(128);
|
||||||
b.Property(x => x.LastName).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.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 =>
|
model.Entity<Department>(b =>
|
||||||
{
|
{
|
||||||
b.ToTable("departments");
|
b.ToTable("departments");
|
||||||
b.HasKey(x => x.Id);
|
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.Code).IsRequired().HasMaxLength(64);
|
||||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||||
@@ -237,7 +273,8 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.HasOne(x => x.Parent)
|
b.HasOne(x => x.Parent)
|
||||||
.WithMany(x => x.Children)
|
.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);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,6 +282,7 @@ public class AppDbContext : DbContext
|
|||||||
{
|
{
|
||||||
b.ToTable("positions");
|
b.ToTable("positions");
|
||||||
b.HasKey(x => x.Id);
|
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.Code).IsRequired().HasMaxLength(64);
|
||||||
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
|
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
|
||||||
@@ -264,17 +302,20 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.HasOne(x => x.UserProfile)
|
b.HasOne(x => x.UserProfile)
|
||||||
.WithMany(p => p.Employments)
|
.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);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasOne(x => x.Department)
|
b.HasOne(x => x.Department)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(x => x.DepartmentId)
|
.HasForeignKey(x => new { x.TenantId, x.DepartmentId })
|
||||||
|
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne(x => x.Position)
|
b.HasOne(x => x.Position)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(x => x.PositionId)
|
.HasForeignKey(x => new { x.TenantId, x.PositionId })
|
||||||
|
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -290,7 +331,8 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.HasOne(x => x.UserProfile)
|
b.HasOne(x => x.UserProfile)
|
||||||
.WithMany(p => p.Addresses)
|
.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);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||||
@@ -306,7 +348,8 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.HasOne(x => x.UserProfile)
|
b.HasOne(x => x.UserProfile)
|
||||||
.WithMany(p => p.EmergencyContacts)
|
.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);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||||
@@ -323,7 +366,8 @@ public class AppDbContext : DbContext
|
|||||||
|
|
||||||
b.HasOne(x => x.UserProfile)
|
b.HasOne(x => x.UserProfile)
|
||||||
.WithMany(p => p.BankAccounts)
|
.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);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||||
@@ -347,10 +391,18 @@ public class AppDbContext : DbContext
|
|||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
.ValueGeneratedNever();
|
||||||
|
|
||||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
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
|
// Audit
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public static class ServiceCollectionExtensions
|
|||||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
|
services.AddScoped<IJwtFactory, JwtFactory>();
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
services.AddOptions<AuthOptions>()
|
services.AddOptions<AuthOptions>()
|
||||||
@@ -90,7 +91,9 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<IDisableMfaUseCase, DisableMfaUseCase>();
|
services.AddScoped<IDisableMfaUseCase, DisableMfaUseCase>();
|
||||||
services.AddScoped<ILogoutUseCase, LogoutUseCase>();
|
services.AddScoped<ILogoutUseCase, LogoutUseCase>();
|
||||||
services.AddScoped<ILogoutAllUseCase, LogoutAllUseCase>();
|
services.AddScoped<ILogoutAllUseCase, LogoutAllUseCase>();
|
||||||
|
services.AddScoped<IIssueTokenPairUseCase, IssueTokenPairUseCase>();
|
||||||
|
services.AddScoped<IRefreshUseCase, RefreshUseCase>();
|
||||||
|
|
||||||
// UseCases — HR
|
// UseCases — HR
|
||||||
services.AddScoped<IUpsertUserProfileUseCase, UpsertUserProfileUseCase>();
|
services.AddScoped<IUpsertUserProfileUseCase, UpsertUserProfileUseCase>();
|
||||||
services.AddScoped<IAddEmploymentUseCase, AddEmploymentUseCase>();
|
services.AddScoped<IAddEmploymentUseCase, AddEmploymentUseCase>();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace AMREZ.EOP.Infrastructures.Migrations
|
namespace AMREZ.EOP.Infrastructures.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDbContext))]
|
[DbContext(typeof(AppDbContext))]
|
||||||
[Migration("20250930101327_Add_Tenant_Guid")]
|
[Migration("20251002040013_InitDatabase")]
|
||||||
partial class Add_Tenant_Guid
|
partial class InitDatabase
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@@ -58,10 +58,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -81,6 +79,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("permissions", null, t =>
|
b.ToTable("permissions", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
|
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)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -140,6 +138,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("roles", null, t =>
|
b.ToTable("roles", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -193,12 +191,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "PermissionId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("role_permissions", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -270,6 +270,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("users", null, t =>
|
b.ToTable("users", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -329,7 +329,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Provider", "Subject")
|
b.HasIndex("TenantId", "Provider", "Subject")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
@@ -337,6 +337,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_external_accounts", null, t =>
|
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_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);
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -399,8 +399,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Type", "Identifier")
|
b.HasIndex("TenantId", "Type", "Identifier")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
@@ -410,6 +408,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_identities", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -488,13 +486,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
|
|
||||||
b.ToTable("user_mfa_factors", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -548,13 +544,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
||||||
|
|
||||||
b.ToTable("user_password_histories", null, t =>
|
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_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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -608,12 +602,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "RoleId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("user_roles", null, t =>
|
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_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");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -682,8 +678,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "DeviceId");
|
b.HasIndex("TenantId", "DeviceId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
@@ -691,6 +685,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_sessions", null, t =>
|
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_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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -745,16 +739,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("ParentDepartmentId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Code")
|
b.HasIndex("TenantId", "Code")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ParentDepartmentId");
|
||||||
|
|
||||||
b.ToTable("departments", null, t =>
|
b.ToTable("departments", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
|
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)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -820,13 +814,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("emergency_contacts", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -905,13 +897,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("employee_addresses", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -982,13 +972,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("employee_bank_accounts", null, t =>
|
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_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");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -1057,19 +1045,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("DepartmentId");
|
|
||||||
|
|
||||||
b.HasIndex("PositionId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
b.HasIndex("TenantId", "DepartmentId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "PositionId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
||||||
|
|
||||||
b.ToTable("employments", null, t =>
|
b.ToTable("employments", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -1132,6 +1118,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("positions", null, t =>
|
b.ToTable("positions", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -1210,15 +1196,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId")
|
b.HasIndex("TenantId", "UserId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("user_profiles", null, t =>
|
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_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.HasKey("TenantKey");
|
||||||
|
|
||||||
|
b.HasAlternateKey("TenantId");
|
||||||
|
|
||||||
b.HasIndex("IsActive");
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("tenants", "meta");
|
b.ToTable("tenants", "meta");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1310,6 +1300,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Permission");
|
||||||
|
|
||||||
b.Navigation("Role");
|
b.Navigation("Role");
|
||||||
@@ -1319,7 +1323,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("ExternalAccounts")
|
.WithMany("ExternalAccounts")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1330,7 +1335,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("Identities")
|
.WithMany("Identities")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1341,7 +1347,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("MfaFactors")
|
.WithMany("MfaFactors")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1352,7 +1359,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("PasswordHistories")
|
.WithMany("PasswordHistories")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1373,6 +1381,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Role");
|
||||||
|
|
||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
@@ -1382,7 +1404,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("Sessions")
|
.WithMany("Sessions")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1393,7 +1416,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
||||||
.WithMany("Children")
|
.WithMany("Children")
|
||||||
.HasForeignKey("ParentDepartmentId")
|
.HasForeignKey("TenantId", "ParentDepartmentId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.Navigation("Parent");
|
b.Navigation("Parent");
|
||||||
@@ -1403,7 +1427,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("EmergencyContacts")
|
.WithMany("EmergencyContacts")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1414,7 +1439,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("Addresses")
|
.WithMany("Addresses")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1425,7 +1451,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("BankAccounts")
|
.WithMany("BankAccounts")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1436,17 +1463,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("DepartmentId")
|
.HasForeignKey("TenantId", "DepartmentId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PositionId")
|
.HasForeignKey("TenantId", "PositionId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("Employments")
|
.WithMany("Employments")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1469,7 +1499,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithOne()
|
.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)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -19,10 +19,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
ParentDepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_departments", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_departments_departments_ParentDepartmentId",
|
name: "FK_departments_departments_tenant_id_ParentDepartmentId",
|
||||||
column: x => x.ParentDepartmentId,
|
columns: x => new { x.tenant_id, x.ParentDepartmentId },
|
||||||
principalTable: "departments",
|
principalTable: "departments",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,9 +48,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
Code = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
Name = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_permissions", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -66,10 +70,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
Level = table.Column<int>(type: "integer", nullable: true),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_positions", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -87,9 +93,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
Code = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
Name = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_roles", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -108,6 +116,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
TenantKey = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
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),
|
Schema = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||||
ConnectionString = table.Column<string>(type: "text", nullable: true),
|
ConnectionString = table.Column<string>(type: "text", nullable: true),
|
||||||
Mode = table.Column<int>(type: "integer", nullable: false),
|
Mode = table.Column<int>(type: "integer", nullable: false),
|
||||||
@@ -117,6 +126,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_tenants", x => x.TenantKey);
|
table.PrimaryKey("PK_tenants", x => x.TenantKey);
|
||||||
|
table.UniqueConstraint("AK_tenants_TenantId", x => x.TenantId);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -124,13 +134,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||||
IsActive = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
IsActive = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||||
AccessFailedCount = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
AccessFailedCount = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||||
LockoutEndUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
LockoutEndUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||||
MfaEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
MfaEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
SecurityStamp = table.Column<string>(type: "text", nullable: true),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_users", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
@@ -148,9 +160,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
RoleId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
PermissionId = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_role_permissions_permissions_PermissionId",
|
name: "FK_role_permissions_permissions_PermissionId",
|
||||||
column: x => x.PermissionId,
|
column: x => x.PermissionId,
|
||||||
principalTable: "permissions",
|
principalTable: "permissions",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
table.ForeignKey(
|
||||||
name: "FK_role_permissions_roles_RoleId",
|
name: "FK_role_permissions_roles_RoleId",
|
||||||
column: x => x.RoleId,
|
column: x => x.RoleId,
|
||||||
principalTable: "roles",
|
principalTable: "roles",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
@@ -203,12 +228,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
Provider = table.Column<int>(type: "integer", nullable: false),
|
Provider = table.Column<int>(type: "integer", nullable: false),
|
||||||
Subject = table.Column<string>(type: "text", nullable: false),
|
Subject = table.Column<string>(type: "text", nullable: false),
|
||||||
Email = table.Column<string>(type: "text", nullable: true),
|
Email = table.Column<string>(type: "text", nullable: true),
|
||||||
LinkedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_external_accounts_users_UserId",
|
name: "FK_user_external_accounts_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -232,12 +258,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
Identifier = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Identifier = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||||
VerifiedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_identities_users_UserId",
|
name: "FK_user_identities_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -261,7 +288,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
Label = table.Column<string>(type: "text", nullable: true),
|
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),
|
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||||
AddedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
AddedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||||
LastUsedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_mfa_factors_users_UserId",
|
name: "FK_user_mfa_factors_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,10 +324,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||||
ChangedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_password_histories_users_UserId",
|
name: "FK_user_password_histories_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -323,7 +352,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
FirstName = table.Column<string>(type: "character varying(128)", maxLength: 128, 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),
|
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),
|
Gender = table.Column<int>(type: "integer", nullable: true),
|
||||||
DepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
|
DepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
PositionId = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_user_profiles", x => x.Id);
|
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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_profiles_departments_DepartmentId",
|
name: "FK_user_profiles_departments_DepartmentId",
|
||||||
column: x => x.DepartmentId,
|
column: x => x.DepartmentId,
|
||||||
@@ -354,10 +385,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
principalTable: "positions",
|
principalTable: "positions",
|
||||||
principalColumn: "Id");
|
principalColumn: "Id");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_profiles_users_UserId",
|
name: "FK_user_profiles_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -366,9 +397,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
RoleId = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_roles_roles_RoleId",
|
name: "FK_user_roles_roles_RoleId",
|
||||||
column: x => x.RoleId,
|
column: x => x.RoleId,
|
||||||
principalTable: "roles",
|
principalTable: "roles",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
table.ForeignKey(
|
||||||
name: "FK_user_roles_users_UserId",
|
name: "FK_user_roles_users_UserId",
|
||||||
column: x => x.UserId,
|
column: x => x.UserId,
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
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(
|
migrationBuilder.CreateTable(
|
||||||
@@ -398,7 +442,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
RefreshTokenHash = table.Column<string>(type: "text", nullable: false),
|
RefreshTokenHash = table.Column<string>(type: "text", nullable: false),
|
||||||
IssuedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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),
|
DeviceId = table.Column<string>(type: "text", nullable: true),
|
||||||
UserAgent = table.Column<string>(type: "text", nullable: true),
|
UserAgent = table.Column<string>(type: "text", nullable: true),
|
||||||
IpAddress = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_user_sessions_users_UserId",
|
name: "FK_user_sessions_users_tenant_id_UserId",
|
||||||
column: x => x.UserId,
|
columns: x => new { x.tenant_id, x.UserId },
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -430,13 +475,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, 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),
|
Relationship = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
Phone = table.Column<string>(type: "text", nullable: true),
|
Phone = table.Column<string>(type: "text", nullable: true),
|
||||||
Email = table.Column<string>(type: "text", nullable: true),
|
Email = table.Column<string>(type: "text", nullable: true),
|
||||||
IsPrimary = table.Column<bool>(type: "boolean", 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_emergency_contacts_user_profiles_UserProfileId",
|
name: "FK_emergency_contacts_user_profiles_tenant_id_UserProfileId",
|
||||||
column: x => x.UserProfileId,
|
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||||
principalTable: "user_profiles",
|
principalTable: "user_profiles",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -460,7 +506,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
Line1 = table.Column<string>(type: "character varying(256)", maxLength: 256, 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),
|
PostalCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
Country = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
Country = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
IsPrimary = table.Column<bool>(type: "boolean", 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_employee_addresses_user_profiles_UserProfileId",
|
name: "FK_employee_addresses_user_profiles_tenant_id_UserProfileId",
|
||||||
column: x => x.UserProfileId,
|
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||||
principalTable: "user_profiles",
|
principalTable: "user_profiles",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -493,7 +540,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
BankName = table.Column<string>(type: "character varying(128)", maxLength: 128, 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),
|
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),
|
Branch = table.Column<string>(type: "text", nullable: true),
|
||||||
Note = table.Column<string>(type: "text", nullable: true),
|
Note = table.Column<string>(type: "text", nullable: true),
|
||||||
IsPrimary = table.Column<bool>(type: "boolean", 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.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_null", "tenant_id is not null");
|
||||||
|
table.CheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_employee_bank_accounts_user_profiles_UserProfileId",
|
name: "FK_employee_bank_accounts_user_profiles_tenant_id_UserProfileId",
|
||||||
column: x => x.UserProfileId,
|
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||||
principalTable: "user_profiles",
|
principalTable: "user_profiles",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -524,7 +572,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
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),
|
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
EmploymentType = table.Column<int>(type: "integer", nullable: false),
|
EmploymentType = table.Column<int>(type: "integer", nullable: false),
|
||||||
StartDate = table.Column<DateTime>(type: "timestamp with time zone", 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),
|
ManagerUserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
WorkEmail = table.Column<string>(type: "text", nullable: true),
|
WorkEmail = table.Column<string>(type: "text", nullable: true),
|
||||||
WorkPhone = 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_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),
|
created_by = table.Column<string>(type: "text", nullable: true),
|
||||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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.PrimaryKey("PK_employments", x => x.Id);
|
||||||
table.CheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
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(
|
table.ForeignKey(
|
||||||
name: "FK_employments_departments_DepartmentId",
|
name: "FK_employments_departments_tenant_id_DepartmentId",
|
||||||
column: x => x.DepartmentId,
|
columns: x => new { x.tenant_id, x.DepartmentId },
|
||||||
principalTable: "departments",
|
principalTable: "departments",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_employments_positions_PositionId",
|
name: "FK_employments_positions_tenant_id_PositionId",
|
||||||
column: x => x.PositionId,
|
columns: x => new { x.tenant_id, x.PositionId },
|
||||||
principalTable: "positions",
|
principalTable: "positions",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_employments_user_profiles_UserProfileId",
|
name: "FK_employments_user_profiles_tenant_id_UserProfileId",
|
||||||
column: x => x.UserProfileId,
|
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||||
principalTable: "user_profiles",
|
principalTable: "user_profiles",
|
||||||
principalColumn: "Id",
|
principalColumns: new[] { "tenant_id", "Id" },
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_departments_ParentDepartmentId",
|
|
||||||
table: "departments",
|
|
||||||
column: "ParentDepartmentId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_departments_tenant_id",
|
name: "IX_departments_tenant_id",
|
||||||
table: "departments",
|
table: "departments",
|
||||||
@@ -580,6 +624,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: new[] { "tenant_id", "Code" },
|
columns: new[] { "tenant_id", "Code" },
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_departments_tenant_id_ParentDepartmentId",
|
||||||
|
table: "departments",
|
||||||
|
columns: new[] { "tenant_id", "ParentDepartmentId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_emergency_contacts_tenant_id",
|
name: "IX_emergency_contacts_tenant_id",
|
||||||
table: "emergency_contacts",
|
table: "emergency_contacts",
|
||||||
@@ -590,11 +639,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "emergency_contacts",
|
table: "emergency_contacts",
|
||||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_emergency_contacts_UserProfileId",
|
|
||||||
table: "emergency_contacts",
|
|
||||||
column: "UserProfileId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_employee_addresses_tenant_id",
|
name: "IX_employee_addresses_tenant_id",
|
||||||
table: "employee_addresses",
|
table: "employee_addresses",
|
||||||
@@ -605,11 +649,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "employee_addresses",
|
table: "employee_addresses",
|
||||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_employee_addresses_UserProfileId",
|
|
||||||
table: "employee_addresses",
|
|
||||||
column: "UserProfileId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_employee_bank_accounts_tenant_id",
|
name: "IX_employee_bank_accounts_tenant_id",
|
||||||
table: "employee_bank_accounts",
|
table: "employee_bank_accounts",
|
||||||
@@ -620,36 +659,26 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "employee_bank_accounts",
|
table: "employee_bank_accounts",
|
||||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
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(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_employments_tenant_id",
|
name: "IX_employments_tenant_id",
|
||||||
table: "employments",
|
table: "employments",
|
||||||
column: "tenant_id");
|
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(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_employments_tenant_id_UserProfileId_StartDate",
|
name: "IX_employments_tenant_id_UserProfileId_StartDate",
|
||||||
table: "employments",
|
table: "employments",
|
||||||
columns: new[] { "tenant_id", "UserProfileId", "StartDate" });
|
columns: new[] { "tenant_id", "UserProfileId", "StartDate" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_employments_UserProfileId",
|
|
||||||
table: "employments",
|
|
||||||
column: "UserProfileId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_permissions_tenant_id",
|
name: "IX_permissions_tenant_id",
|
||||||
table: "permissions",
|
table: "permissions",
|
||||||
@@ -687,6 +716,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "role_permissions",
|
table: "role_permissions",
|
||||||
column: "tenant_id");
|
column: "tenant_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_role_permissions_tenant_id_PermissionId",
|
||||||
|
table: "role_permissions",
|
||||||
|
columns: new[] { "tenant_id", "PermissionId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_role_permissions_tenant_id_RoleId_PermissionId",
|
name: "IX_role_permissions_tenant_id_RoleId_PermissionId",
|
||||||
table: "role_permissions",
|
table: "role_permissions",
|
||||||
@@ -728,6 +762,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "tenants",
|
table: "tenants",
|
||||||
column: "IsActive");
|
column: "IsActive");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_tenants_TenantId",
|
||||||
|
schema: "meta",
|
||||||
|
table: "tenants",
|
||||||
|
column: "TenantId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_external_accounts_tenant_id",
|
name: "IX_user_external_accounts_tenant_id",
|
||||||
table: "user_external_accounts",
|
table: "user_external_accounts",
|
||||||
@@ -740,9 +781,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_external_accounts_UserId",
|
name: "IX_user_external_accounts_tenant_id_UserId",
|
||||||
table: "user_external_accounts",
|
table: "user_external_accounts",
|
||||||
column: "UserId");
|
columns: new[] { "tenant_id", "UserId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_identities_tenant_id",
|
name: "IX_user_identities_tenant_id",
|
||||||
@@ -755,11 +796,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: new[] { "tenant_id", "Type", "Identifier" },
|
columns: new[] { "tenant_id", "Type", "Identifier" },
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_user_identities_UserId",
|
|
||||||
table: "user_identities",
|
|
||||||
column: "UserId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_user_identity_primary_per_type",
|
name: "ix_user_identity_primary_per_type",
|
||||||
table: "user_identities",
|
table: "user_identities",
|
||||||
@@ -775,11 +811,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "user_mfa_factors",
|
table: "user_mfa_factors",
|
||||||
columns: new[] { "tenant_id", "UserId" });
|
columns: new[] { "tenant_id", "UserId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_user_mfa_factors_UserId",
|
|
||||||
table: "user_mfa_factors",
|
|
||||||
column: "UserId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_password_histories_tenant_id",
|
name: "IX_user_password_histories_tenant_id",
|
||||||
table: "user_password_histories",
|
table: "user_password_histories",
|
||||||
@@ -790,11 +821,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "user_password_histories",
|
table: "user_password_histories",
|
||||||
columns: new[] { "tenant_id", "UserId", "ChangedAt" });
|
columns: new[] { "tenant_id", "UserId", "ChangedAt" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_user_password_histories_UserId",
|
|
||||||
table: "user_password_histories",
|
|
||||||
column: "UserId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_profiles_DepartmentId",
|
name: "IX_user_profiles_DepartmentId",
|
||||||
table: "user_profiles",
|
table: "user_profiles",
|
||||||
@@ -816,12 +842,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
columns: new[] { "tenant_id", "UserId" },
|
columns: new[] { "tenant_id", "UserId" },
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_user_profiles_UserId",
|
|
||||||
table: "user_profiles",
|
|
||||||
column: "UserId",
|
|
||||||
unique: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_roles_RoleId",
|
name: "IX_user_roles_RoleId",
|
||||||
table: "user_roles",
|
table: "user_roles",
|
||||||
@@ -832,6 +852,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "user_roles",
|
table: "user_roles",
|
||||||
column: "tenant_id");
|
column: "tenant_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_user_roles_tenant_id_RoleId",
|
||||||
|
table: "user_roles",
|
||||||
|
columns: new[] { "tenant_id", "RoleId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_user_roles_tenant_id_UserId_RoleId",
|
name: "IX_user_roles_tenant_id_UserId_RoleId",
|
||||||
table: "user_roles",
|
table: "user_roles",
|
||||||
@@ -858,11 +883,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
table: "user_sessions",
|
table: "user_sessions",
|
||||||
columns: new[] { "tenant_id", "UserId" });
|
columns: new[] { "tenant_id", "UserId" });
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_user_sessions_UserId",
|
|
||||||
table: "user_sessions",
|
|
||||||
column: "UserId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_users_tenant_id",
|
name: "IX_users_tenant_id",
|
||||||
table: "users",
|
table: "users",
|
||||||
@@ -55,10 +55,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -78,6 +76,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("permissions", null, t =>
|
b.ToTable("permissions", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
|
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)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -137,6 +135,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("roles", null, t =>
|
b.ToTable("roles", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -190,12 +188,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "PermissionId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("role_permissions", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -267,6 +267,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("users", null, t =>
|
b.ToTable("users", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -326,7 +326,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Provider", "Subject")
|
b.HasIndex("TenantId", "Provider", "Subject")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
@@ -334,6 +334,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_external_accounts", null, t =>
|
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_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);
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -396,8 +396,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Type", "Identifier")
|
b.HasIndex("TenantId", "Type", "Identifier")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
@@ -407,6 +405,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_identities", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -485,13 +483,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
|
|
||||||
b.ToTable("user_mfa_factors", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -545,13 +541,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
||||||
|
|
||||||
b.ToTable("user_password_histories", null, t =>
|
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_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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -605,12 +599,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "RoleId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("user_roles", null, t =>
|
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_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");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -679,8 +675,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "DeviceId");
|
b.HasIndex("TenantId", "DeviceId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId");
|
b.HasIndex("TenantId", "UserId");
|
||||||
@@ -688,6 +682,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("user_sessions", null, t =>
|
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_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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -742,16 +736,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("ParentDepartmentId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "Code")
|
b.HasIndex("TenantId", "Code")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "ParentDepartmentId");
|
||||||
|
|
||||||
b.ToTable("departments", null, t =>
|
b.ToTable("departments", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
|
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)");
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -817,13 +811,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("emergency_contacts", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<int>("Type")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -902,13 +894,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("employee_addresses", null, t =>
|
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_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");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -979,13 +969,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||||
|
|
||||||
b.ToTable("employee_bank_accounts", null, t =>
|
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_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");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -1054,19 +1042,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("DepartmentId");
|
|
||||||
|
|
||||||
b.HasIndex("PositionId");
|
|
||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserProfileId");
|
b.HasIndex("TenantId", "DepartmentId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId", "PositionId");
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
||||||
|
|
||||||
b.ToTable("employments", null, t =>
|
b.ToTable("employments", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -1129,6 +1115,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
b.ToTable("positions", null, t =>
|
b.ToTable("positions", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
|
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");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<Guid>("TenantId")
|
b.Property<Guid>("TenantId")
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("tenant_id")
|
.HasColumnName("tenant_id");
|
||||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
@@ -1207,15 +1193,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasIndex("TenantId");
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
b.HasIndex("UserId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.HasIndex("TenantId", "UserId")
|
b.HasIndex("TenantId", "UserId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("user_profiles", null, t =>
|
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_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.HasKey("TenantKey");
|
||||||
|
|
||||||
|
b.HasAlternateKey("TenantId");
|
||||||
|
|
||||||
b.HasIndex("IsActive");
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("tenants", "meta");
|
b.ToTable("tenants", "meta");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1307,6 +1297,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Permission");
|
||||||
|
|
||||||
b.Navigation("Role");
|
b.Navigation("Role");
|
||||||
@@ -1316,7 +1320,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("ExternalAccounts")
|
.WithMany("ExternalAccounts")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1327,7 +1332,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("Identities")
|
.WithMany("Identities")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1338,7 +1344,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("MfaFactors")
|
.WithMany("MfaFactors")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1349,7 +1356,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("PasswordHistories")
|
.WithMany("PasswordHistories")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1370,6 +1378,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.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("Role");
|
||||||
|
|
||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
@@ -1379,7 +1401,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithMany("Sessions")
|
.WithMany("Sessions")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("TenantId", "UserId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1390,7 +1413,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
||||||
.WithMany("Children")
|
.WithMany("Children")
|
||||||
.HasForeignKey("ParentDepartmentId")
|
.HasForeignKey("TenantId", "ParentDepartmentId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.Navigation("Parent");
|
b.Navigation("Parent");
|
||||||
@@ -1400,7 +1424,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("EmergencyContacts")
|
.WithMany("EmergencyContacts")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1411,7 +1436,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("Addresses")
|
.WithMany("Addresses")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1422,7 +1448,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("BankAccounts")
|
.WithMany("BankAccounts")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1433,17 +1460,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
{
|
{
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("DepartmentId")
|
.HasForeignKey("TenantId", "DepartmentId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("PositionId")
|
.HasForeignKey("TenantId", "PositionId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||||
.WithMany("Employments")
|
.WithMany("Employments")
|
||||||
.HasForeignKey("UserProfileId")
|
.HasForeignKey("TenantId", "UserProfileId")
|
||||||
|
.HasPrincipalKey("TenantId", "Id")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
@@ -1466,7 +1496,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
|||||||
|
|
||||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||||
.WithOne()
|
.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)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
|||||||
9
AMREZ.EOP.Infrastructures/Options/JwtOptions.cs
Normal file
9
AMREZ.EOP.Infrastructures/Options/JwtOptions.cs
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
using AMREZ.EOP.Abstractions.Storage;
|
using AMREZ.EOP.Abstractions.Storage;
|
||||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||||
using AMREZ.EOP.Infrastructures.Data;
|
using AMREZ.EOP.Infrastructures.Data;
|
||||||
using AMREZ.EOP.Infrastructures.Options;
|
using AMREZ.EOP.Infrastructures.Options;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
@@ -11,9 +13,19 @@ namespace AMREZ.EOP.Infrastructures.Repositories;
|
|||||||
public sealed class TenantRepository : ITenantRepository
|
public sealed class TenantRepository : ITenantRepository
|
||||||
{
|
{
|
||||||
private readonly IDbScope _scope;
|
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();
|
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 (cur is null) return false;
|
||||||
|
|
||||||
if (ifUnmodifiedSince.HasValue && cur.UpdatedAtUtc > ifUnmodifiedSince.Value)
|
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.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());
|
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);
|
var t = await db.Set<TenantConfig>().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
|
||||||
if (t is null) return false;
|
if (t is null) return false;
|
||||||
|
|
||||||
// ❌ ไม่ลบนะ — ✅ deactivate
|
|
||||||
if (t.IsActive)
|
if (t.IsActive)
|
||||||
{
|
{
|
||||||
t.IsActive = false;
|
t.IsActive = false;
|
||||||
@@ -120,7 +131,7 @@ public sealed class TenantRepository : ITenantRepository
|
|||||||
if (ex is null) return false;
|
if (ex is null) return false;
|
||||||
|
|
||||||
ex.IsActive = false;
|
ex.IsActive = false;
|
||||||
ex.TenantKey = null;
|
ex.TenantKey = null;
|
||||||
ex.IsPlatformBaseDomain = false;
|
ex.IsPlatformBaseDomain = false;
|
||||||
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
ex.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
|||||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
using AMREZ.EOP.Abstractions.Storage;
|
using AMREZ.EOP.Abstractions.Storage;
|
||||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||||
|
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||||
using AMREZ.EOP.Infrastructures.Data;
|
using AMREZ.EOP.Infrastructures.Data;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -19,22 +20,33 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
private Guid TenantId()
|
private Guid TenantId()
|
||||||
{
|
{
|
||||||
var http = _http.HttpContext;
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||||
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
|
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
|
||||||
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
|
_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)
|
public async Task<UserProfile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
return await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == userId, ct);
|
return await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == userId, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpsertAsync(UserProfile profile, CancellationToken ct = default)
|
public async Task UpsertAsync(UserProfile profile, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var existing = await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == profile.UserId, ct);
|
var existing = await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == profile.UserId, ct);
|
||||||
if (existing is null)
|
if (existing is null)
|
||||||
@@ -56,8 +68,9 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task<Employment> AddEmploymentAsync(Employment e, CancellationToken ct = default)
|
public async Task<Employment> AddEmploymentAsync(Employment e, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
e.TenantId = TenantId();
|
e.TenantId = tid;
|
||||||
await db.Employments.AddAsync(e, ct);
|
await db.Employments.AddAsync(e, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
return e;
|
return e;
|
||||||
@@ -65,8 +78,8 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task EndEmploymentAsync(Guid employmentId, DateTime endDate, CancellationToken ct = default)
|
public async Task EndEmploymentAsync(Guid employmentId, DateTime endDate, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
var e = await db.Employments.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == employmentId, ct);
|
var e = await db.Employments.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == employmentId, ct);
|
||||||
if (e is null) return;
|
if (e is null) return;
|
||||||
e.EndDate = endDate;
|
e.EndDate = endDate;
|
||||||
@@ -75,8 +88,9 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task<EmployeeAddress> AddAddressAsync(EmployeeAddress a, CancellationToken ct = default)
|
public async Task<EmployeeAddress> AddAddressAsync(EmployeeAddress a, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
a.TenantId = TenantId();
|
a.TenantId = tid;
|
||||||
await db.EmployeeAddresses.AddAsync(a, ct);
|
await db.EmployeeAddresses.AddAsync(a, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
return a;
|
return a;
|
||||||
@@ -84,8 +98,8 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task SetPrimaryAddressAsync(Guid userProfileId, Guid addressId, CancellationToken ct = default)
|
public async Task SetPrimaryAddressAsync(Guid userProfileId, Guid addressId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var all = await db.EmployeeAddresses.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
var all = await db.EmployeeAddresses.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||||
foreach (var x in all) x.IsPrimary = false;
|
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)
|
public async Task<EmergencyContact> AddEmergencyContactAsync(EmergencyContact c, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
c.TenantId = TenantId();
|
c.TenantId = tid;
|
||||||
await db.EmergencyContacts.AddAsync(c, ct);
|
await db.EmergencyContacts.AddAsync(c, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
return c;
|
return c;
|
||||||
@@ -107,8 +122,8 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task SetPrimaryEmergencyContactAsync(Guid userProfileId, Guid contactId, CancellationToken ct = default)
|
public async Task SetPrimaryEmergencyContactAsync(Guid userProfileId, Guid contactId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var all = await db.EmergencyContacts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
var all = await db.EmergencyContacts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||||
foreach (var x in all) x.IsPrimary = false;
|
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)
|
public async Task<EmployeeBankAccount> AddBankAccountAsync(EmployeeBankAccount b, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
b.TenantId = TenantId();
|
b.TenantId = tid;
|
||||||
await db.EmployeeBankAccounts.AddAsync(b, ct);
|
await db.EmployeeBankAccounts.AddAsync(b, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
return b;
|
return b;
|
||||||
@@ -130,8 +146,8 @@ public class UserProfileRepository : IUserProfileRepository
|
|||||||
|
|
||||||
public async Task SetPrimaryBankAccountAsync(Guid userProfileId, Guid bankAccountId, CancellationToken ct = default)
|
public async Task SetPrimaryBankAccountAsync(Guid userProfileId, Guid bankAccountId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var all = await db.EmployeeBankAccounts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
var all = await db.EmployeeBankAccounts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||||
foreach (var x in all) x.IsPrimary = false;
|
foreach (var x in all) x.IsPrimary = false;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
|||||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||||
using AMREZ.EOP.Abstractions.Storage;
|
using AMREZ.EOP.Abstractions.Storage;
|
||||||
using AMREZ.EOP.Domain.Entities.Authentications;
|
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||||
|
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||||
using AMREZ.EOP.Domain.Shared._Users;
|
using AMREZ.EOP.Domain.Shared._Users;
|
||||||
using AMREZ.EOP.Infrastructures.Data;
|
using AMREZ.EOP.Infrastructures.Data;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@@ -25,16 +26,27 @@ public class UserRepository : IUserRepository
|
|||||||
IHttpContextAccessor http)
|
IHttpContextAccessor http)
|
||||||
{
|
{
|
||||||
_scope = scope;
|
_scope = scope;
|
||||||
_redis = redis; // null = ไม่มี/ต่อไม่ได้ → ข้าม cache
|
_redis = redis;
|
||||||
_tenantResolver = tenantResolver;
|
_tenantResolver = tenantResolver;
|
||||||
_http = http;
|
_http = http;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid TenantId()
|
private Guid TenantId()
|
||||||
{
|
{
|
||||||
var http = _http.HttpContext;
|
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||||
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
|
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
|
||||||
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
|
_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)
|
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)
|
public async Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
return await db.Users.AsNoTracking()
|
return await db.Users.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
||||||
}
|
}
|
||||||
@@ -57,7 +69,6 @@ public class UserRepository : IUserRepository
|
|||||||
var tidStr = tid.ToString();
|
var tidStr = tid.ToString();
|
||||||
var r = _redis?.GetDatabase();
|
var r = _redis?.GetDatabase();
|
||||||
|
|
||||||
// 1) Redis
|
|
||||||
if (r is not null)
|
if (r is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -69,10 +80,9 @@ public class UserRepository : IUserRepository
|
|||||||
if (hit?.IsActive == true) return hit;
|
if (hit?.IsActive == true) return hit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { /* ignore → query DB */ }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) EF: join identities
|
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
var norm = email.Trim().ToLowerInvariant();
|
var norm = email.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
@@ -86,7 +96,6 @@ public class UserRepository : IUserRepository
|
|||||||
i.Identifier == norm))
|
i.Identifier == norm))
|
||||||
.FirstOrDefaultAsync(ct);
|
.FirstOrDefaultAsync(ct);
|
||||||
|
|
||||||
// 3) cache
|
|
||||||
if (user is not null && r is not null)
|
if (user is not null && r is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -96,7 +105,7 @@ public class UserRepository : IUserRepository
|
|||||||
await r.StringSetAsync(IdentityEmailKey(tidStr, norm), payload, ttl);
|
await r.StringSetAsync(IdentityEmailKey(tidStr, norm), payload, ttl);
|
||||||
await r.StringSetAsync(UserIdKey(tidStr, user.Id), payload, ttl);
|
await r.StringSetAsync(UserIdKey(tidStr, user.Id), payload, ttl);
|
||||||
}
|
}
|
||||||
catch { /* ignore */ }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
@@ -104,8 +113,8 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
|
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
var norm = email.Trim().ToLowerInvariant();
|
var norm = email.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
return await db.UserIdentities.AsNoTracking()
|
return await db.UserIdentities.AsNoTracking()
|
||||||
@@ -114,40 +123,36 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task AddAsync(User user, CancellationToken ct = default)
|
public async Task AddAsync(User user, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
user.TenantId = TenantId();
|
user.TenantId = tid;
|
||||||
|
|
||||||
await db.Users.AddAsync(user, ct);
|
await db.Users.AddAsync(user, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
// cache
|
|
||||||
var r = _redis?.GetDatabase();
|
var r = _redis?.GetDatabase();
|
||||||
if (r is not null)
|
if (r is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tid = user.TenantId.ToString();
|
|
||||||
var payload = JsonSerializer.Serialize(user);
|
var payload = JsonSerializer.Serialize(user);
|
||||||
var ttl = TimeSpan.FromMinutes(5);
|
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
|
var email = user.Identities
|
||||||
.FirstOrDefault(i => i.Type == IdentityType.Email && i.IsPrimary)?.Identifier;
|
.FirstOrDefault(i => i.Type == IdentityType.Email && i.IsPrimary)?.Identifier;
|
||||||
if (!string.IsNullOrWhiteSpace(email))
|
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)
|
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
var norm = identifier.Trim().ToLowerInvariant();
|
var norm = identifier.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
if (isPrimary)
|
if (isPrimary)
|
||||||
@@ -171,11 +176,10 @@ public class UserRepository : IUserRepository
|
|||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (2) IMPLEMENTED: VerifyIdentityAsync
|
|
||||||
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 db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
var norm = identifier.Trim().ToLowerInvariant();
|
var norm = identifier.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
var id = await db.UserIdentities
|
var id = await db.UserIdentities
|
||||||
@@ -190,11 +194,10 @@ public class UserRepository : IUserRepository
|
|||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (3) IMPLEMENTED: GetPrimaryIdentityAsync
|
|
||||||
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 db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
return await db.UserIdentities.AsNoTracking()
|
return await db.UserIdentities.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(i =>
|
.FirstOrDefaultAsync(i =>
|
||||||
@@ -204,12 +207,10 @@ public class UserRepository : IUserRepository
|
|||||||
i.IsPrimary, ct);
|
i.IsPrimary, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========= Password =========
|
|
||||||
|
|
||||||
public async Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default)
|
public async Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var user = await db.Users.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
var user = await db.Users.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
||||||
if (user is null) return;
|
if (user is null) return;
|
||||||
@@ -221,8 +222,8 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default)
|
public async Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var h = new UserPasswordHistory
|
var h = new UserPasswordHistory
|
||||||
{
|
{
|
||||||
@@ -236,12 +237,10 @@ public class UserRepository : IUserRepository
|
|||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========= MFA =========
|
|
||||||
|
|
||||||
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 db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var f = new UserMfaFactor
|
var f = new UserMfaFactor
|
||||||
{
|
{
|
||||||
@@ -265,8 +264,8 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default)
|
public async Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var f = await db.UserMfaFactors.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == factorId, ct);
|
var f = await db.UserMfaFactors.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == factorId, ct);
|
||||||
if (f is null) return;
|
if (f is null) return;
|
||||||
@@ -285,26 +284,45 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default)
|
public async Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
return await db.UserMfaFactors.AnyAsync(x => x.TenantId == tid && x.UserId == userId && x.Enabled, ct);
|
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)
|
public async Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
var tid = TenantId();
|
||||||
var db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
session.TenantId = TenantId();
|
session.TenantId = tid;
|
||||||
await db.UserSessions.AddAsync(session, ct);
|
await db.UserSessions.AddAsync(session, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
return session;
|
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>();
|
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 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;
|
if (s is null) return 0;
|
||||||
@@ -315,8 +333,8 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
public async Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default)
|
public async Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var db = _scope.Get<AppDbContext>();
|
|
||||||
var tid = TenantId();
|
var tid = TenantId();
|
||||||
|
var db = _scope.Get<AppDbContext>();
|
||||||
|
|
||||||
var sessions = await db.UserSessions
|
var sessions = await db.UserSessions
|
||||||
.Where(x => x.TenantId == tid && x.UserId == userId && x.RevokedAt == null)
|
.Where(x => x.TenantId == tid && x.UserId == userId && x.RevokedAt == null)
|
||||||
@@ -326,4 +344,61 @@ public class UserRepository : IUserRepository
|
|||||||
|
|
||||||
return await db.SaveChangesAsync(ct);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
51
AMREZ.EOP.Infrastructures/Security/JwtFactory.cs
Normal file
51
AMREZ.EOP.Infrastructures/Security/JwtFactory.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,6 @@ public sealed class DefaultTenantResolver : ITenantResolver
|
|||||||
var cid = http.TraceIdentifier;
|
var cid = http.TraceIdentifier;
|
||||||
var path = http.Request.Path.Value ?? string.Empty;
|
var path = http.Request.Path.Value ?? string.Empty;
|
||||||
|
|
||||||
// ✅ ทางลัด: ขอ platform ตรง ๆ ด้วย hint
|
|
||||||
if (hint is string hs && string.Equals(hs, "@platform", StringComparison.OrdinalIgnoreCase))
|
if (hint is string hs && string.Equals(hs, "@platform", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (_map.TryGetBySlug(PlatformSlug, out var platform))
|
if (_map.TryGetBySlug(PlatformSlug, out var platform))
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ namespace AMREZ.EOP.Infrastructures.Tenancy;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Bootstrap 'public' ถ้ายังไม่มี (กัน chicken-and-egg)
|
|
||||||
if (!map.TryGetBySlug("public", out _))
|
if (!map.TryGetBySlug("public", out _))
|
||||||
{
|
{
|
||||||
map.UpsertTenant(
|
map.UpsertTenant(
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
|||||||
private readonly ITenantDbContextFactory _factory;
|
private readonly ITenantDbContextFactory _factory;
|
||||||
private readonly IConnectionMultiplexer? _redis;
|
private readonly IConnectionMultiplexer? _redis;
|
||||||
|
|
||||||
private AppDbContext? _db;
|
|
||||||
private IDbContextTransaction? _tx;
|
private IDbContextTransaction? _tx;
|
||||||
private ITenantContext? _tenant;
|
private ITenantContext? _tenant;
|
||||||
|
|
||||||
@@ -31,40 +30,38 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
_scope = scope;
|
_scope = scope;
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_redis = redis; // optional; null ได้
|
_redis = redis;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default)
|
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));
|
_tenant = tenant ?? throw new ArgumentNullException(nameof(tenant));
|
||||||
|
|
||||||
_scope.EnsureForTenant(tenant);
|
_scope.EnsureForTenant(tenant);
|
||||||
_db = _scope.Get<AppDbContext>();
|
var db = _scope.Get<AppDbContext>();
|
||||||
_tx = await _db.Database.BeginTransactionAsync(isolation, ct);
|
_tx = await db.Database.BeginTransactionAsync(isolation, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CommitAsync(CancellationToken ct = default)
|
public async Task CommitAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (_db is null) return; // ยังไม่ Begin
|
if (_tx is null) return;
|
||||||
|
|
||||||
// track entities ที่เปลี่ยน (เพื่อ invalidate cache แบบแม่นขึ้น)
|
var db = _scope.Get<AppDbContext>();
|
||||||
var changedUsers = _db.ChangeTracker.Entries<User>()
|
|
||||||
|
var changedUsers = db.ChangeTracker.Entries<User>()
|
||||||
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
|
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
|
||||||
.Select(e => e.Entity)
|
.Select(e => e.Entity)
|
||||||
.ToList();
|
.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)
|
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
|
||||||
.Select(e => e.Entity)
|
.Select(e => e.Entity)
|
||||||
.ToList();
|
.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))
|
if (_redis is not null && _tenant is not null && (changedUsers.Count > 0 || changedIdentities.Count > 0))
|
||||||
{
|
{
|
||||||
var r = _redis.GetDatabase();
|
var r = _redis.GetDatabase();
|
||||||
@@ -75,8 +72,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
var keyId = $"eop:{tenantId}:user:id:{u.Id:N}";
|
var keyId = $"eop:{tenantId}:user:id:{u.Id:N}";
|
||||||
tasks.Add(r.KeyDeleteAsync(keyId));
|
tasks.Add(r.KeyDeleteAsync(keyId));
|
||||||
|
|
||||||
// refresh cache (ถ้าต้องการ)
|
|
||||||
var payload = JsonSerializer.Serialize(u);
|
var payload = JsonSerializer.Serialize(u);
|
||||||
var ttl = TimeSpan.FromMinutes(5);
|
var ttl = TimeSpan.FromMinutes(5);
|
||||||
tasks.Add(r.StringSetAsync(keyId, payload, ttl));
|
tasks.Add(r.StringSetAsync(keyId, payload, ttl));
|
||||||
@@ -88,7 +83,7 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
|||||||
tasks.Add(r.KeyDeleteAsync(k));
|
tasks.Add(r.KeyDeleteAsync(k));
|
||||||
}
|
}
|
||||||
|
|
||||||
try { await Task.WhenAll(tasks); } catch { /* swallow */ }
|
try { await Task.WhenAll(tasks); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
await DisposeAsync();
|
await DisposeAsync();
|
||||||
@@ -109,11 +104,9 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
|||||||
|
|
||||||
public ValueTask DisposeAsync()
|
public ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
try { _tx?.Dispose(); } catch { /* ignore */ }
|
try { _tx?.Dispose(); } catch { }
|
||||||
try { _db?.Dispose(); } catch { /* ignore */ }
|
_tx = null;
|
||||||
|
_tenant = null;
|
||||||
_tx = null; _db = null; _tenant = null;
|
|
||||||
|
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user