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.DisableMfa;
|
||||
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.Logout;
|
||||
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.VerifyEmail;
|
||||
using AMREZ.EOP.Domain.Shared.Contracts;
|
||||
@@ -32,14 +34,33 @@ public class AuthenticationController : ControllerBase
|
||||
private readonly ILogoutUseCase _logout;
|
||||
private readonly ILogoutAllUseCase _logoutAll;
|
||||
|
||||
private readonly IIssueTokenPairUseCase _issueTokens;
|
||||
private readonly IRefreshUseCase _refresh;
|
||||
|
||||
public AuthenticationController(
|
||||
ILoginUseCase login,
|
||||
IRegisterUseCase register,
|
||||
IChangePasswordUseCase changePassword)
|
||||
IChangePasswordUseCase changePassword,
|
||||
IAddEmailIdentityUseCase addEmail,
|
||||
IVerifyEmailUseCase verifyEmail,
|
||||
IEnableTotpUseCase enableTotp,
|
||||
IDisableMfaUseCase disableMfa,
|
||||
ILogoutUseCase logout,
|
||||
ILogoutAllUseCase logoutAll,
|
||||
IIssueTokenPairUseCase issueTokens,
|
||||
IRefreshUseCase refresh)
|
||||
{
|
||||
_login = login;
|
||||
_register = register;
|
||||
_changePassword = changePassword;
|
||||
_addEmail = addEmail;
|
||||
_verifyEmail = verifyEmail;
|
||||
_enableTotp = enableTotp;
|
||||
_disableMfa = disableMfa;
|
||||
_logout = logout;
|
||||
_logoutAll = logoutAll;
|
||||
_issueTokens = issueTokens;
|
||||
_refresh = refresh;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
@@ -51,7 +72,7 @@ public class AuthenticationController : ControllerBase
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
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("tenant", res.TenantId)
|
||||
};
|
||||
@@ -59,7 +80,69 @@ public class AuthenticationController : ControllerBase
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, AuthPolicies.Scheme));
|
||||
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")]
|
||||
|
||||
@@ -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
|
||||
{
|
||||
// ===== Users/Identities/Password/MFA (ของเดิม) =====
|
||||
Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default);
|
||||
Task<User?> FindActiveByEmailAsync(string email, CancellationToken ct = default);
|
||||
Task<bool> EmailExistsAsync(string email, 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 VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default);
|
||||
Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default);
|
||||
|
||||
// Password
|
||||
Task ChangePasswordAsync(Guid userId, string newPasswordHash, 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 DisableMfaFactorAsync(Guid factorId, 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?> 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> 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);
|
||||
|
||||
// NOTE: ไม่ใช้ DisplayName ใน Entity แล้ว — ส่งกลับเป็นค่าว่าง/ไปดึงจาก HR ฝั่ง API
|
||||
return new LoginResponse(user.Id, string.Empty, email, tenant.Id);
|
||||
return new LoginResponse(user.Id , email, tenant.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -16,12 +16,7 @@ public sealed class LogoutAllUseCase : ILogoutAllUseCase
|
||||
private readonly 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)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
|
||||
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace AMREZ.EOP.Application.UseCases.Authentications;
|
||||
@@ -31,6 +32,10 @@ public sealed class LogoutUseCase : ILogoutUseCase
|
||||
await _uow.CommitAsync(ct);
|
||||
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,
|
||||
};
|
||||
|
||||
// แนบอัตลักษณ์แบบ Email (เก็บในตารางลูก)
|
||||
user.Identities.Add(new UserIdentity
|
||||
{
|
||||
TenantId = tn.TenantId,
|
||||
Type = IdentityType.Email,
|
||||
Identifier = emailNorm,
|
||||
IsPrimary = true,
|
||||
@@ -76,7 +76,6 @@ public sealed class RegisterUseCase : IRegisterUseCase
|
||||
await _users.AddAsync(user, ct);
|
||||
await _uow.CommitAsync(ct);
|
||||
|
||||
// ไม่ส่ง DisplayName (ปล่อยให้ HR/Presentation สร้าง)
|
||||
return new RegisterResponse(user.Id, string.Empty, emailNorm, tenant.Id);
|
||||
}
|
||||
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(
|
||||
Guid UserId,
|
||||
string DisplayName,
|
||||
string Email,
|
||||
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 Guid TenantId { get; set; }
|
||||
public string Code { get; set; } = default!; // e.g. "auth:session:read"
|
||||
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 Guid TenantId { get; set; }
|
||||
public string Code { get; set; } = default!; // system code, unique per tenant
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class RolePermission : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid RoleId { get; set; }
|
||||
public Guid PermissionId { get; set; }
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class User : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
|
||||
public string PasswordHash { get; set; } = default!;
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserExternalAccount : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public ExternalProvider Provider { get; set; }
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserIdentity : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public IdentityType Type { get; set; }
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserMfaFactor : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public MfaType Type { get; set; }
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserPasswordHistory : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public string PasswordHash { get; set; } = default!;
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserRole : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid RoleId { get; set; }
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.Authentications;
|
||||
|
||||
public sealed class UserSession : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public string RefreshTokenHash { get; set; } = default!;
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace AMREZ.EOP.Domain.Entities.Common;
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string TenantId { get; set; } = default!;
|
||||
public Guid TenantId { get; set; } = default!;
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class Department : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public string Code { 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 Guid TenantId { get; set; }
|
||||
public Guid UserProfileId { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class EmployeeAddress : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserProfileId { get; set; }
|
||||
|
||||
public AddressType Type { get; set; } = AddressType.Home;
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class EmployeeBankAccount : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserProfileId { get; set; }
|
||||
|
||||
public string BankName { get; set; } = default!;
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class Employment : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserProfileId { get; set; }
|
||||
|
||||
public EmploymentType EmploymentType { get; set; } = EmploymentType.Permanent;
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class Position : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public string Code { get; set; } = default!;
|
||||
public string Title { get; set; } = default!;
|
||||
public int? Level { get; set; }
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
|
||||
public sealed class UserProfile : BaseEntity
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public string FirstName { get; set; } = default!;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.9.17" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,6 +3,7 @@ using AMREZ.EOP.Domain.Entities.Common;
|
||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.Data;
|
||||
|
||||
@@ -37,11 +38,14 @@ public class AppDbContext : DbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder model)
|
||||
{
|
||||
// ====== Global Tenancy Config (meta schema) — ไม่สืบทอด BaseEntity ======
|
||||
// ====== Tenancy (meta) ======
|
||||
model.Entity<TenantConfig>(b =>
|
||||
{
|
||||
b.ToTable("tenants", schema: "meta");
|
||||
b.HasKey(x => x.TenantKey);
|
||||
b.HasKey(x => x.TenantKey); // PK = key (slug)
|
||||
b.HasAlternateKey(x => x.TenantId); // AK = GUID
|
||||
b.HasIndex(x => x.TenantId).IsUnique();
|
||||
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128).IsRequired();
|
||||
b.Property(x => x.Schema).HasMaxLength(128);
|
||||
b.Property(x => x.ConnectionString);
|
||||
@@ -58,7 +62,7 @@ public class AppDbContext : DbContext
|
||||
b.ToTable("tenant_domains", schema: "meta");
|
||||
b.HasKey(x => x.Domain);
|
||||
b.Property(x => x.Domain).HasMaxLength(253).IsRequired();
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128);
|
||||
b.Property(x => x.TenantKey).HasMaxLength(128); // optional
|
||||
b.Property(x => x.IsPlatformBaseDomain).HasDefaultValue(false);
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
b.Property(x => x.UpdatedAtUtc)
|
||||
@@ -81,36 +85,13 @@ public class AppDbContext : DbContext
|
||||
b.ToTable("users");
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
// principal key สำหรับ composite FK จากลูก ๆ
|
||||
b.HasAlternateKey(u => new { u.TenantId, u.Id });
|
||||
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
b.Property(x => x.IsActive).HasDefaultValue(true);
|
||||
|
||||
b.Property(x => x.AccessFailedCount).HasDefaultValue(0);
|
||||
b.Property(x => x.MfaEnabled).HasDefaultValue(false);
|
||||
|
||||
b.HasMany(x => x.Identities)
|
||||
.WithOne(i => i.User)
|
||||
.HasForeignKey(i => i.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.MfaFactors)
|
||||
.WithOne(i => i.User)
|
||||
.HasForeignKey(i => i.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.Sessions)
|
||||
.WithOne(s => s.User)
|
||||
.HasForeignKey(s => s.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.PasswordHistories)
|
||||
.WithOne(ph => ph.User)
|
||||
.HasForeignKey(ph => ph.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasMany(x => x.ExternalAccounts)
|
||||
.WithOne(ea => ea.User)
|
||||
.HasForeignKey(ea => ea.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserIdentity>(b =>
|
||||
@@ -122,11 +103,16 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.Identifier).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.IsPrimary).HasDefaultValue(false);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier })
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Type, x.Identifier }).IsUnique();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.Type, x.IsPrimary })
|
||||
.HasDatabaseName("ix_user_identity_primary_per_type");
|
||||
|
||||
// (TenantId, UserId) -> User.(TenantId, Id)
|
||||
b.HasOne(i => i.User)
|
||||
.WithMany(u => u.Identities)
|
||||
.HasForeignKey(i => new { i.TenantId, i.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserMfaFactor>(b =>
|
||||
@@ -136,8 +122,13 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.Property(x => x.Type).IsRequired();
|
||||
b.Property(x => x.Enabled).HasDefaultValue(true);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.MfaFactors)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserSession>(b =>
|
||||
@@ -148,6 +139,12 @@ public class AppDbContext : DbContext
|
||||
b.Property(x => x.RefreshTokenHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId });
|
||||
b.HasIndex(x => new { x.TenantId, x.DeviceId });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.Sessions)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserPasswordHistory>(b =>
|
||||
@@ -156,6 +153,12 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
b.Property(x => x.PasswordHash).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.ChangedAt });
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.PasswordHistories)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<UserExternalAccount>(b =>
|
||||
@@ -165,15 +168,20 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.Property(x => x.Provider).IsRequired();
|
||||
b.Property(x => x.Subject).IsRequired();
|
||||
b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject }).IsUnique();
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.Provider, x.Subject })
|
||||
.IsUnique();
|
||||
b.HasOne(x => x.User)
|
||||
.WithMany(u => u.ExternalAccounts)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<Role>(b =>
|
||||
{
|
||||
b.ToTable("roles");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(r => new { r.TenantId, r.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -185,6 +193,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("permissions");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(256);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -198,6 +207,18 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId, x.RoleId }).IsUnique();
|
||||
|
||||
b.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey(nameof(User.TenantId), nameof(User.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<RolePermission>(b =>
|
||||
@@ -206,6 +227,18 @@ public class AppDbContext : DbContext
|
||||
b.HasKey(x => x.Id);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.RoleId, x.PermissionId }).IsUnique();
|
||||
|
||||
b.HasOne<Role>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.RoleId })
|
||||
.HasPrincipalKey(nameof(Role.TenantId), nameof(Role.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne<Permission>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => new { x.TenantId, x.PermissionId })
|
||||
.HasPrincipalKey(nameof(Permission.TenantId), nameof(Permission.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
// ====== HR ======
|
||||
@@ -213,22 +246,25 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("user_profiles");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.Id });
|
||||
|
||||
b.Property(x => x.FirstName).IsRequired().HasMaxLength(128);
|
||||
b.Property(x => x.LastName).IsRequired().HasMaxLength(128);
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => x.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserId }).IsUnique();
|
||||
|
||||
b.HasOne(x => x.User)
|
||||
.WithOne()
|
||||
.HasForeignKey<UserProfile>(x => new { x.TenantId, x.UserId })
|
||||
.HasPrincipalKey<User>(u => new { u.TenantId, u.Id }) // <-- เปลี่ยนตรงนี้
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
model.Entity<Department>(b =>
|
||||
{
|
||||
b.ToTable("departments");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(d => new { d.TenantId, d.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Name).IsRequired().HasMaxLength(256);
|
||||
@@ -237,7 +273,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.Parent)
|
||||
.WithMany(x => x.Children)
|
||||
.HasForeignKey(x => x.ParentDepartmentId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.ParentDepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
@@ -245,6 +282,7 @@ public class AppDbContext : DbContext
|
||||
{
|
||||
b.ToTable("positions");
|
||||
b.HasKey(x => x.Id);
|
||||
b.HasAlternateKey(p => new { p.TenantId, p.Id });
|
||||
|
||||
b.Property(x => x.Code).IsRequired().HasMaxLength(64);
|
||||
b.Property(x => x.Title).IsRequired().HasMaxLength(256);
|
||||
@@ -264,17 +302,20 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Employments)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne(x => x.Department)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.DepartmentId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.DepartmentId })
|
||||
.HasPrincipalKey(nameof(Department.TenantId), nameof(Department.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne(x => x.Position)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.PositionId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.PositionId })
|
||||
.HasPrincipalKey(nameof(Position.TenantId), nameof(Position.Id))
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
@@ -290,7 +331,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.Addresses)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -306,7 +348,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.EmergencyContacts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -323,7 +366,8 @@ public class AppDbContext : DbContext
|
||||
|
||||
b.HasOne(x => x.UserProfile)
|
||||
.WithMany(p => p.BankAccounts)
|
||||
.HasForeignKey(x => x.UserProfileId)
|
||||
.HasForeignKey(x => new { x.TenantId, x.UserProfileId })
|
||||
.HasPrincipalKey(nameof(UserProfile.TenantId), nameof(UserProfile.Id))
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasIndex(x => new { x.TenantId, x.UserProfileId, x.IsPrimary });
|
||||
@@ -347,10 +391,18 @@ public class AppDbContext : DbContext
|
||||
.HasColumnName("tenant_id")
|
||||
.HasColumnType("uuid")
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.ValueGeneratedNever();
|
||||
|
||||
b.HasIndex(nameof(BaseEntity.TenantId));
|
||||
b.HasCheckConstraint($"ck_{et.GetTableName()}_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
// ชื่อ constraint สร้างแบบ concat แทน string interpolation กัน ambiguous handler
|
||||
var tn = et.GetTableName();
|
||||
if (!string.IsNullOrEmpty(tn))
|
||||
{
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_null"), "tenant_id is not null");
|
||||
b.HasCheckConstraint(string.Concat("ck_", tn, "_tenant_not_zero"),
|
||||
"tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
}
|
||||
|
||||
// Audit
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
|
||||
@@ -29,6 +29,7 @@ public static class ServiceCollectionExtensions
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
||||
{
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddScoped<IJwtFactory, JwtFactory>();
|
||||
|
||||
// Options
|
||||
services.AddOptions<AuthOptions>()
|
||||
@@ -90,6 +91,8 @@ public static class ServiceCollectionExtensions
|
||||
services.AddScoped<IDisableMfaUseCase, DisableMfaUseCase>();
|
||||
services.AddScoped<ILogoutUseCase, LogoutUseCase>();
|
||||
services.AddScoped<ILogoutAllUseCase, LogoutAllUseCase>();
|
||||
services.AddScoped<IIssueTokenPairUseCase, IssueTokenPairUseCase>();
|
||||
services.AddScoped<IRefreshUseCase, RefreshUseCase>();
|
||||
|
||||
// UseCases — HR
|
||||
services.AddScoped<IUpsertUserProfileUseCase, UpsertUserProfileUseCase>();
|
||||
|
||||
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
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20250930101327_Add_Tenant_Guid")]
|
||||
partial class Add_Tenant_Guid
|
||||
[Migration("20251002040013_InitDatabase")]
|
||||
partial class InitDatabase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -58,10 +58,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -81,6 +79,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("permissions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,10 +117,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -140,6 +138,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("roles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -172,10 +172,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -193,12 +191,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "PermissionId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_permissions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,10 +252,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -270,6 +270,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("users", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -309,10 +311,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -329,7 +329,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
|
||||
b.HasIndex("TenantId", "Provider", "Subject")
|
||||
.IsUnique();
|
||||
@@ -337,6 +337,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_external_accounts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -373,10 +375,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -399,8 +399,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "Type", "Identifier")
|
||||
.IsUnique();
|
||||
|
||||
@@ -410,6 +408,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_identities", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -465,10 +465,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -488,13 +486,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
|
||||
b.ToTable("user_mfa_factors", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -528,10 +526,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -548,13 +544,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
||||
|
||||
b.ToTable("user_password_histories", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -584,10 +580,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -608,12 +602,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_roles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -659,10 +657,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -682,8 +678,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "DeviceId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
@@ -691,6 +685,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_sessions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -730,10 +726,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -745,16 +739,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentDepartmentId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TenantId", "ParentDepartmentId");
|
||||
|
||||
b.ToTable("departments", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -800,10 +796,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -820,13 +814,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("emergency_contacts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -882,10 +876,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -905,13 +897,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("employee_addresses", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -962,10 +954,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -982,13 +972,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("employee_bank_accounts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1033,10 +1023,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -1057,19 +1045,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("PositionId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
b.HasIndex("TenantId", "DepartmentId");
|
||||
|
||||
b.HasIndex("TenantId", "PositionId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
||||
|
||||
b.ToTable("employments", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1104,10 +1092,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
@@ -1132,6 +1118,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("positions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1186,10 +1174,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -1210,15 +1196,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TenantId", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_profiles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1254,8 +1239,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("TenantKey");
|
||||
|
||||
b.HasAlternateKey("TenantId");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("tenants", "meta");
|
||||
});
|
||||
|
||||
@@ -1310,6 +1300,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Permission", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "PermissionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "RoleId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Permission");
|
||||
|
||||
b.Navigation("Role");
|
||||
@@ -1319,7 +1323,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("ExternalAccounts")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1330,7 +1335,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("Identities")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1341,7 +1347,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("MfaFactors")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1352,7 +1359,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("PasswordHistories")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1373,6 +1381,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "RoleId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
@@ -1382,7 +1404,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("Sessions")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1393,7 +1416,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentDepartmentId")
|
||||
.HasForeignKey("TenantId", "ParentDepartmentId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Parent");
|
||||
@@ -1403,7 +1427,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("EmergencyContacts")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1414,7 +1439,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("Addresses")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1425,7 +1451,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("BankAccounts")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1436,17 +1463,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
||||
.WithMany()
|
||||
.HasForeignKey("DepartmentId")
|
||||
.HasForeignKey("TenantId", "DepartmentId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
||||
.WithMany()
|
||||
.HasForeignKey("PositionId")
|
||||
.HasForeignKey("TenantId", "PositionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("Employments")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1469,7 +1499,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithOne()
|
||||
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserId")
|
||||
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "TenantId", "UserId")
|
||||
.HasPrincipalKey("AMREZ.EOP.Domain.Entities.Authentications.User", "TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -19,10 +19,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
ParentDepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -32,12 +32,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_departments", x => x.Id);
|
||||
table.UniqueConstraint("AK_departments_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_departments_departments_ParentDepartmentId",
|
||||
column: x => x.ParentDepartmentId,
|
||||
name: "FK_departments_departments_tenant_id_ParentDepartmentId",
|
||||
columns: x => new { x.tenant_id, x.ParentDepartmentId },
|
||||
principalTable: "departments",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
@@ -46,9 +48,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
Code = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -58,7 +60,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_permissions", x => x.Id);
|
||||
table.UniqueConstraint("AK_permissions_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -66,10 +70,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
Code = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
Level = table.Column<int>(type: "integer", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -79,7 +83,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_positions", x => x.Id);
|
||||
table.UniqueConstraint("AK_positions_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -87,9 +93,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
Code = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -99,7 +105,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_roles", x => x.Id);
|
||||
table.UniqueConstraint("AK_roles_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -108,6 +116,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
TenantKey = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Schema = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
ConnectionString = table.Column<string>(type: "text", nullable: true),
|
||||
Mode = table.Column<int>(type: "integer", nullable: false),
|
||||
@@ -117,6 +126,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_tenants", x => x.TenantKey);
|
||||
table.UniqueConstraint("AK_tenants_TenantId", x => x.TenantId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -124,13 +134,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
AccessFailedCount = table.Column<int>(type: "integer", nullable: false, defaultValue: 0),
|
||||
LockoutEndUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
MfaEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
SecurityStamp = table.Column<string>(type: "text", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -140,7 +150,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_users", x => x.Id);
|
||||
table.UniqueConstraint("AK_users_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -148,9 +160,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
RoleId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
PermissionId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -161,18 +173,31 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_role_permissions", x => x.Id);
|
||||
table.CheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_role_permissions_permissions_PermissionId",
|
||||
column: x => x.PermissionId,
|
||||
principalTable: "permissions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_role_permissions_permissions_tenant_id_PermissionId",
|
||||
columns: x => new { x.tenant_id, x.PermissionId },
|
||||
principalTable: "permissions",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_role_permissions_roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_role_permissions_roles_tenant_id_RoleId",
|
||||
columns: x => new { x.tenant_id, x.RoleId },
|
||||
principalTable: "roles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -203,12 +228,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Provider = table.Column<int>(type: "integer", nullable: false),
|
||||
Subject = table.Column<string>(type: "text", nullable: false),
|
||||
Email = table.Column<string>(type: "text", nullable: true),
|
||||
LinkedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -219,11 +244,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_external_accounts", x => x.Id);
|
||||
table.CheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_external_accounts_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_external_accounts_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -232,12 +258,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Identifier = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false, defaultValue: false),
|
||||
VerifiedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -248,11 +274,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_identities", x => x.Id);
|
||||
table.CheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_identities_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_identities_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -261,7 +288,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Label = table.Column<string>(type: "text", nullable: true),
|
||||
@@ -273,6 +299,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
AddedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
LastUsedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -283,11 +310,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_mfa_factors", x => x.Id);
|
||||
table.CheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_mfa_factors_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_mfa_factors_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -296,10 +324,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
||||
ChangedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -310,11 +338,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_password_histories", x => x.Id);
|
||||
table.CheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_password_histories_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_password_histories_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -323,7 +352,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
FirstName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
LastName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
@@ -333,6 +361,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
Gender = table.Column<int>(type: "integer", nullable: true),
|
||||
DepartmentId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
PositionId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -342,7 +371,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_user_profiles", x => x.Id);
|
||||
table.UniqueConstraint("AK_user_profiles_tenant_id_Id", x => new { x.tenant_id, x.Id });
|
||||
table.CheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_profiles_departments_DepartmentId",
|
||||
column: x => x.DepartmentId,
|
||||
@@ -354,10 +385,10 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
principalTable: "positions",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_profiles_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_profiles_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -366,9 +397,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
RoleId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -379,18 +410,31 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_roles", x => x.Id);
|
||||
table.CheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_roles_roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_user_roles_roles_tenant_id_RoleId",
|
||||
columns: x => new { x.tenant_id, x.RoleId },
|
||||
principalTable: "roles",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_user_roles_users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_user_roles_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
@@ -398,7 +442,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
RefreshTokenHash = table.Column<string>(type: "text", nullable: false),
|
||||
IssuedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -407,6 +450,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
DeviceId = table.Column<string>(type: "text", nullable: true),
|
||||
UserAgent = table.Column<string>(type: "text", nullable: true),
|
||||
IpAddress = table.Column<string>(type: "text", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -417,11 +461,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_user_sessions", x => x.Id);
|
||||
table.CheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_user_sessions_users_UserId",
|
||||
column: x => x.UserId,
|
||||
name: "FK_user_sessions_users_tenant_id_UserId",
|
||||
columns: x => new { x.tenant_id, x.UserId },
|
||||
principalTable: "users",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -430,13 +475,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Relationship = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
Phone = table.Column<string>(type: "text", nullable: true),
|
||||
Email = table.Column<string>(type: "text", nullable: true),
|
||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -447,11 +492,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_emergency_contacts", x => x.Id);
|
||||
table.CheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_emergency_contacts_user_profiles_UserProfileId",
|
||||
column: x => x.UserProfileId,
|
||||
name: "FK_emergency_contacts_user_profiles_tenant_id_UserProfileId",
|
||||
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -460,7 +506,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Line1 = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
@@ -470,6 +515,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
PostalCode = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
Country = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -480,11 +526,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_employee_addresses", x => x.Id);
|
||||
table.CheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_employee_addresses_user_profiles_UserProfileId",
|
||||
column: x => x.UserProfileId,
|
||||
name: "FK_employee_addresses_user_profiles_tenant_id_UserProfileId",
|
||||
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -493,7 +540,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
BankName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
AccountNumber = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
@@ -501,6 +547,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
Branch = table.Column<string>(type: "text", nullable: true),
|
||||
Note = table.Column<string>(type: "text", nullable: true),
|
||||
IsPrimary = table.Column<bool>(type: "boolean", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -511,11 +558,12 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_employee_bank_accounts", x => x.Id);
|
||||
table.CheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_employee_bank_accounts_user_profiles_UserProfileId",
|
||||
column: x => x.UserProfileId,
|
||||
name: "FK_employee_bank_accounts_user_profiles_tenant_id_UserProfileId",
|
||||
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
@@ -524,7 +572,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "nullif(current_setting('app.tenant_id', true),'')::uuid"),
|
||||
UserProfileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
EmploymentType = table.Column<int>(type: "integer", nullable: false),
|
||||
StartDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
@@ -534,6 +581,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
ManagerUserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
WorkEmail = table.Column<string>(type: "text", nullable: true),
|
||||
WorkPhone = table.Column<string>(type: "text", nullable: true),
|
||||
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"),
|
||||
created_by = table.Column<string>(type: "text", nullable: true),
|
||||
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
@@ -544,31 +592,27 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
table.PrimaryKey("PK_employments", x => x.Id);
|
||||
table.CheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
||||
table.CheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
table.ForeignKey(
|
||||
name: "FK_employments_departments_DepartmentId",
|
||||
column: x => x.DepartmentId,
|
||||
name: "FK_employments_departments_tenant_id_DepartmentId",
|
||||
columns: x => new { x.tenant_id, x.DepartmentId },
|
||||
principalTable: "departments",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_employments_positions_PositionId",
|
||||
column: x => x.PositionId,
|
||||
name: "FK_employments_positions_tenant_id_PositionId",
|
||||
columns: x => new { x.tenant_id, x.PositionId },
|
||||
principalTable: "positions",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_employments_user_profiles_UserProfileId",
|
||||
column: x => x.UserProfileId,
|
||||
name: "FK_employments_user_profiles_tenant_id_UserProfileId",
|
||||
columns: x => new { x.tenant_id, x.UserProfileId },
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
principalColumns: new[] { "tenant_id", "Id" },
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_departments_ParentDepartmentId",
|
||||
table: "departments",
|
||||
column: "ParentDepartmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_departments_tenant_id",
|
||||
table: "departments",
|
||||
@@ -580,6 +624,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: new[] { "tenant_id", "Code" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_departments_tenant_id_ParentDepartmentId",
|
||||
table: "departments",
|
||||
columns: new[] { "tenant_id", "ParentDepartmentId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_emergency_contacts_tenant_id",
|
||||
table: "emergency_contacts",
|
||||
@@ -590,11 +639,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "emergency_contacts",
|
||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_emergency_contacts_UserProfileId",
|
||||
table: "emergency_contacts",
|
||||
column: "UserProfileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employee_addresses_tenant_id",
|
||||
table: "employee_addresses",
|
||||
@@ -605,11 +649,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "employee_addresses",
|
||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employee_addresses_UserProfileId",
|
||||
table: "employee_addresses",
|
||||
column: "UserProfileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employee_bank_accounts_tenant_id",
|
||||
table: "employee_bank_accounts",
|
||||
@@ -620,36 +659,26 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "employee_bank_accounts",
|
||||
columns: new[] { "tenant_id", "UserProfileId", "IsPrimary" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employee_bank_accounts_UserProfileId",
|
||||
table: "employee_bank_accounts",
|
||||
column: "UserProfileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_DepartmentId",
|
||||
table: "employments",
|
||||
column: "DepartmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_PositionId",
|
||||
table: "employments",
|
||||
column: "PositionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_tenant_id",
|
||||
table: "employments",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_tenant_id_DepartmentId",
|
||||
table: "employments",
|
||||
columns: new[] { "tenant_id", "DepartmentId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_tenant_id_PositionId",
|
||||
table: "employments",
|
||||
columns: new[] { "tenant_id", "PositionId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_tenant_id_UserProfileId_StartDate",
|
||||
table: "employments",
|
||||
columns: new[] { "tenant_id", "UserProfileId", "StartDate" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_employments_UserProfileId",
|
||||
table: "employments",
|
||||
column: "UserProfileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_permissions_tenant_id",
|
||||
table: "permissions",
|
||||
@@ -687,6 +716,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "role_permissions",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_role_permissions_tenant_id_PermissionId",
|
||||
table: "role_permissions",
|
||||
columns: new[] { "tenant_id", "PermissionId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_role_permissions_tenant_id_RoleId_PermissionId",
|
||||
table: "role_permissions",
|
||||
@@ -728,6 +762,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "tenants",
|
||||
column: "IsActive");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_tenants_TenantId",
|
||||
schema: "meta",
|
||||
table: "tenants",
|
||||
column: "TenantId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_external_accounts_tenant_id",
|
||||
table: "user_external_accounts",
|
||||
@@ -740,9 +781,9 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_external_accounts_UserId",
|
||||
name: "IX_user_external_accounts_tenant_id_UserId",
|
||||
table: "user_external_accounts",
|
||||
column: "UserId");
|
||||
columns: new[] { "tenant_id", "UserId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_identities_tenant_id",
|
||||
@@ -755,11 +796,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: new[] { "tenant_id", "Type", "Identifier" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_identities_UserId",
|
||||
table: "user_identities",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_user_identity_primary_per_type",
|
||||
table: "user_identities",
|
||||
@@ -775,11 +811,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "user_mfa_factors",
|
||||
columns: new[] { "tenant_id", "UserId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_mfa_factors_UserId",
|
||||
table: "user_mfa_factors",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_password_histories_tenant_id",
|
||||
table: "user_password_histories",
|
||||
@@ -790,11 +821,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "user_password_histories",
|
||||
columns: new[] { "tenant_id", "UserId", "ChangedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_password_histories_UserId",
|
||||
table: "user_password_histories",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_profiles_DepartmentId",
|
||||
table: "user_profiles",
|
||||
@@ -816,12 +842,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
columns: new[] { "tenant_id", "UserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_profiles_UserId",
|
||||
table: "user_profiles",
|
||||
column: "UserId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_roles_RoleId",
|
||||
table: "user_roles",
|
||||
@@ -832,6 +852,11 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "user_roles",
|
||||
column: "tenant_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_roles_tenant_id_RoleId",
|
||||
table: "user_roles",
|
||||
columns: new[] { "tenant_id", "RoleId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_roles_tenant_id_UserId_RoleId",
|
||||
table: "user_roles",
|
||||
@@ -858,11 +883,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
table: "user_sessions",
|
||||
columns: new[] { "tenant_id", "UserId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_user_sessions_UserId",
|
||||
table: "user_sessions",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_users_tenant_id",
|
||||
table: "users",
|
||||
@@ -55,10 +55,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -78,6 +76,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("permissions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_permissions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -114,10 +114,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -137,6 +135,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("roles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_roles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -169,10 +169,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -190,12 +188,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "PermissionId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId", "PermissionId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("role_permissions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_role_permissions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_role_permissions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,10 +249,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -267,6 +267,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("users", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_users_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_users_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -306,10 +308,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -326,7 +326,7 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
|
||||
b.HasIndex("TenantId", "Provider", "Subject")
|
||||
.IsUnique();
|
||||
@@ -334,6 +334,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_external_accounts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_external_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -370,10 +372,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -396,8 +396,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "Type", "Identifier")
|
||||
.IsUnique();
|
||||
|
||||
@@ -407,6 +405,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_identities", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_identities_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_identities_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -462,10 +462,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -485,13 +483,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
|
||||
b.ToTable("user_mfa_factors", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_mfa_factors_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -525,10 +523,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -545,13 +541,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "ChangedAt");
|
||||
|
||||
b.ToTable("user_password_histories", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_password_histories_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_password_histories_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -581,10 +577,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -605,12 +599,16 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "RoleId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId", "RoleId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_roles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_roles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_roles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -656,10 +654,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -679,8 +675,6 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("TenantId", "DeviceId");
|
||||
|
||||
b.HasIndex("TenantId", "UserId");
|
||||
@@ -688,6 +682,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("user_sessions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_sessions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_sessions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -727,10 +723,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -742,16 +736,18 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentDepartmentId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("TenantId", "Code")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TenantId", "ParentDepartmentId");
|
||||
|
||||
b.ToTable("departments", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_departments_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_departments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -797,10 +793,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -817,13 +811,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("emergency_contacts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_emergency_contacts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -879,10 +873,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
@@ -902,13 +894,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("employee_addresses", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employee_addresses_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employee_addresses_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -959,10 +951,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -979,13 +969,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "IsPrimary");
|
||||
|
||||
b.ToTable("employee_bank_accounts", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employee_bank_accounts_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1030,10 +1020,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -1054,19 +1042,19 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DepartmentId");
|
||||
|
||||
b.HasIndex("PositionId");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserProfileId");
|
||||
b.HasIndex("TenantId", "DepartmentId");
|
||||
|
||||
b.HasIndex("TenantId", "PositionId");
|
||||
|
||||
b.HasIndex("TenantId", "UserProfileId", "StartDate");
|
||||
|
||||
b.ToTable("employments", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_employments_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_employments_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1101,10 +1089,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
@@ -1129,6 +1115,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
b.ToTable("positions", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_positions_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_positions_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1183,10 +1171,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("tenant_id")
|
||||
.HasDefaultValueSql("nullif(current_setting('app.tenant_id', true),'')::uuid");
|
||||
.HasColumnName("tenant_id");
|
||||
|
||||
b.Property<DateTimeOffset?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
@@ -1207,15 +1193,14 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("TenantId", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("user_profiles", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_user_profiles_tenant_not_null", "tenant_id is not null");
|
||||
|
||||
t.HasCheckConstraint("ck_user_profiles_tenant_not_zero", "tenant_id <> '00000000-0000-0000-0000-000000000000'");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1251,8 +1236,13 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasKey("TenantKey");
|
||||
|
||||
b.HasAlternateKey("TenantId");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("TenantId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("tenants", "meta");
|
||||
});
|
||||
|
||||
@@ -1307,6 +1297,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Permission", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "PermissionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "RoleId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Permission");
|
||||
|
||||
b.Navigation("Role");
|
||||
@@ -1316,7 +1320,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("ExternalAccounts")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1327,7 +1332,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("Identities")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1338,7 +1344,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("MfaFactors")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1349,7 +1356,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("PasswordHistories")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1370,6 +1378,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.Role", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "RoleId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Role");
|
||||
|
||||
b.Navigation("User");
|
||||
@@ -1379,7 +1401,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithMany("Sessions")
|
||||
.HasForeignKey("UserId")
|
||||
.HasForeignKey("TenantId", "UserId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1390,7 +1413,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentDepartmentId")
|
||||
.HasForeignKey("TenantId", "ParentDepartmentId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Parent");
|
||||
@@ -1400,7 +1424,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("EmergencyContacts")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1411,7 +1436,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("Addresses")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1422,7 +1448,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("BankAccounts")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1433,17 +1460,20 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
{
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Department", "Department")
|
||||
.WithMany()
|
||||
.HasForeignKey("DepartmentId")
|
||||
.HasForeignKey("TenantId", "DepartmentId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.Position", "Position")
|
||||
.WithMany()
|
||||
.HasForeignKey("PositionId")
|
||||
.HasForeignKey("TenantId", "PositionId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserProfile")
|
||||
.WithMany("Employments")
|
||||
.HasForeignKey("UserProfileId")
|
||||
.HasForeignKey("TenantId", "UserProfileId")
|
||||
.HasPrincipalKey("TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
@@ -1466,7 +1496,8 @@ namespace AMREZ.EOP.Infrastructures.Migrations
|
||||
|
||||
b.HasOne("AMREZ.EOP.Domain.Entities.Authentications.User", "User")
|
||||
.WithOne()
|
||||
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "UserId")
|
||||
.HasForeignKey("AMREZ.EOP.Domain.Entities.HumanResources.UserProfile", "TenantId", "UserId")
|
||||
.HasPrincipalKey("AMREZ.EOP.Domain.Entities.Authentications.User", "TenantId", "Id")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
|
||||
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.Storage;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using AMREZ.EOP.Infrastructures.Data;
|
||||
using AMREZ.EOP.Infrastructures.Options;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -11,9 +13,19 @@ namespace AMREZ.EOP.Infrastructures.Repositories;
|
||||
public sealed class TenantRepository : ITenantRepository
|
||||
{
|
||||
private readonly IDbScope _scope;
|
||||
public TenantRepository(IDbScope scope) => _scope = scope;
|
||||
private readonly ITenantResolver _resolver;
|
||||
private readonly IHttpContextAccessor _http;
|
||||
|
||||
private AppDbContext Db() => _scope.Get<AppDbContext>();
|
||||
public TenantRepository(IDbScope scope, ITenantResolver resolver, IHttpContextAccessor http)
|
||||
{ _scope = scope; _resolver = resolver; _http = http; }
|
||||
|
||||
private AppDbContext Db()
|
||||
{
|
||||
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||
var platform = _resolver.Resolve(http, "@platform") ?? throw new InvalidOperationException("No platform tenant");
|
||||
_scope.EnsureForTenant(platform);
|
||||
return _scope.Get<AppDbContext>();
|
||||
}
|
||||
|
||||
private static string Norm(string s) => (s ?? string.Empty).Trim().ToLowerInvariant();
|
||||
|
||||
@@ -51,7 +63,7 @@ public sealed class TenantRepository : ITenantRepository
|
||||
if (cur is null) return false;
|
||||
|
||||
if (ifUnmodifiedSince.HasValue && cur.UpdatedAtUtc > ifUnmodifiedSince.Value)
|
||||
return false; // 412
|
||||
return false;
|
||||
|
||||
cur.Schema = row.Schema is null ? cur.Schema : (string.IsNullOrWhiteSpace(row.Schema) ? null : row.Schema.Trim());
|
||||
cur.ConnectionString = row.ConnectionString is null ? cur.ConnectionString : (string.IsNullOrWhiteSpace(row.ConnectionString) ? null : row.ConnectionString.Trim());
|
||||
@@ -70,7 +82,6 @@ public sealed class TenantRepository : ITenantRepository
|
||||
var t = await db.Set<TenantConfig>().FirstOrDefaultAsync(x => x.TenantKey == key, ct);
|
||||
if (t is null) return false;
|
||||
|
||||
// ❌ ไม่ลบนะ — ✅ deactivate
|
||||
if (t.IsActive)
|
||||
{
|
||||
t.IsActive = false;
|
||||
|
||||
@@ -2,6 +2,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||
using AMREZ.EOP.Abstractions.Storage;
|
||||
using AMREZ.EOP.Domain.Entities.HumanResources;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using AMREZ.EOP.Infrastructures.Data;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -19,22 +20,33 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
private Guid TenantId()
|
||||
{
|
||||
var http = _http.HttpContext;
|
||||
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
|
||||
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
|
||||
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
|
||||
_scope.EnsureForTenant(tc);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
|
||||
return g;
|
||||
|
||||
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.TenantKey;
|
||||
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var cfg = db.Set<TenantConfig>().AsNoTracking().FirstOrDefault(x => x.TenantKey == key);
|
||||
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
|
||||
return cfg.TenantId;
|
||||
}
|
||||
|
||||
public async Task<UserProfile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == userId, ct);
|
||||
}
|
||||
|
||||
public async Task UpsertAsync(UserProfile profile, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var existing = await db.UserProfiles.FirstOrDefaultAsync(p => p.TenantId == tid && p.UserId == profile.UserId, ct);
|
||||
if (existing is null)
|
||||
@@ -56,8 +68,9 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task<Employment> AddEmploymentAsync(Employment e, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
e.TenantId = TenantId();
|
||||
e.TenantId = tid;
|
||||
await db.Employments.AddAsync(e, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return e;
|
||||
@@ -65,8 +78,8 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task EndEmploymentAsync(Guid employmentId, DateTime endDate, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var e = await db.Employments.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == employmentId, ct);
|
||||
if (e is null) return;
|
||||
e.EndDate = endDate;
|
||||
@@ -75,8 +88,9 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task<EmployeeAddress> AddAddressAsync(EmployeeAddress a, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
a.TenantId = TenantId();
|
||||
a.TenantId = tid;
|
||||
await db.EmployeeAddresses.AddAsync(a, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return a;
|
||||
@@ -84,8 +98,8 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task SetPrimaryAddressAsync(Guid userProfileId, Guid addressId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var all = await db.EmployeeAddresses.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||
foreach (var x in all) x.IsPrimary = false;
|
||||
@@ -98,8 +112,9 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task<EmergencyContact> AddEmergencyContactAsync(EmergencyContact c, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
c.TenantId = TenantId();
|
||||
c.TenantId = tid;
|
||||
await db.EmergencyContacts.AddAsync(c, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return c;
|
||||
@@ -107,8 +122,8 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task SetPrimaryEmergencyContactAsync(Guid userProfileId, Guid contactId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var all = await db.EmergencyContacts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||
foreach (var x in all) x.IsPrimary = false;
|
||||
@@ -121,8 +136,9 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task<EmployeeBankAccount> AddBankAccountAsync(EmployeeBankAccount b, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
b.TenantId = TenantId();
|
||||
b.TenantId = tid;
|
||||
await db.EmployeeBankAccounts.AddAsync(b, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return b;
|
||||
@@ -130,8 +146,8 @@ public class UserProfileRepository : IUserProfileRepository
|
||||
|
||||
public async Task SetPrimaryBankAccountAsync(Guid userProfileId, Guid bankAccountId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var all = await db.EmployeeBankAccounts.Where(x => x.TenantId == tid && x.UserProfileId == userProfileId).ToListAsync(ct);
|
||||
foreach (var x in all) x.IsPrimary = false;
|
||||
|
||||
@@ -3,6 +3,7 @@ using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
|
||||
using AMREZ.EOP.Abstractions.Storage;
|
||||
using AMREZ.EOP.Domain.Entities.Authentications;
|
||||
using AMREZ.EOP.Domain.Entities.Tenancy;
|
||||
using AMREZ.EOP.Domain.Shared._Users;
|
||||
using AMREZ.EOP.Infrastructures.Data;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -25,16 +26,27 @@ public class UserRepository : IUserRepository
|
||||
IHttpContextAccessor http)
|
||||
{
|
||||
_scope = scope;
|
||||
_redis = redis; // null = ไม่มี/ต่อไม่ได้ → ข้าม cache
|
||||
_redis = redis;
|
||||
_tenantResolver = tenantResolver;
|
||||
_http = http;
|
||||
}
|
||||
|
||||
private Guid TenantId()
|
||||
{
|
||||
var http = _http.HttpContext;
|
||||
var tc = http is not null ? _tenantResolver.Resolve(http) : null;
|
||||
return Guid.TryParse(tc?.Id, out var g) ? g : Guid.Empty;
|
||||
var http = _http.HttpContext ?? throw new InvalidOperationException("No HttpContext");
|
||||
var tc = _tenantResolver.Resolve(http) ?? throw new InvalidOperationException("No tenant");
|
||||
_scope.EnsureForTenant(tc);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tc.Id) && Guid.TryParse(tc.Id, out var g) && g != Guid.Empty)
|
||||
return g;
|
||||
|
||||
var key = (http.Items.TryGetValue("TargetTenantKey", out var v) ? v as string : null) ?? tc.Id;
|
||||
if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException("Tenant key missing");
|
||||
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var cfg = db.Set<TenantConfig>().AsNoTracking().FirstOrDefault(x => x.TenantKey == key);
|
||||
if (cfg is null) throw new InvalidOperationException($"Tenant '{key}' not found");
|
||||
return cfg.TenantId;
|
||||
}
|
||||
|
||||
private static string IdentityEmailKey(string tenantId, string email)
|
||||
@@ -45,8 +57,8 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<User?> FindByIdAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.Users.AsNoTracking()
|
||||
.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
||||
}
|
||||
@@ -57,7 +69,6 @@ public class UserRepository : IUserRepository
|
||||
var tidStr = tid.ToString();
|
||||
var r = _redis?.GetDatabase();
|
||||
|
||||
// 1) Redis
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
@@ -69,10 +80,9 @@ public class UserRepository : IUserRepository
|
||||
if (hit?.IsActive == true) return hit;
|
||||
}
|
||||
}
|
||||
catch { /* ignore → query DB */ }
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 2) EF: join identities
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var norm = email.Trim().ToLowerInvariant();
|
||||
|
||||
@@ -86,7 +96,6 @@ public class UserRepository : IUserRepository
|
||||
i.Identifier == norm))
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
// 3) cache
|
||||
if (user is not null && r is not null)
|
||||
{
|
||||
try
|
||||
@@ -96,7 +105,7 @@ public class UserRepository : IUserRepository
|
||||
await r.StringSetAsync(IdentityEmailKey(tidStr, norm), payload, ttl);
|
||||
await r.StringSetAsync(UserIdKey(tidStr, user.Id), payload, ttl);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
catch { }
|
||||
}
|
||||
|
||||
return user;
|
||||
@@ -104,8 +113,8 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string email, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var norm = email.Trim().ToLowerInvariant();
|
||||
|
||||
return await db.UserIdentities.AsNoTracking()
|
||||
@@ -114,40 +123,36 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task AddAsync(User user, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
user.TenantId = TenantId();
|
||||
user.TenantId = tid;
|
||||
|
||||
await db.Users.AddAsync(user, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// cache
|
||||
var r = _redis?.GetDatabase();
|
||||
if (r is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tid = user.TenantId.ToString();
|
||||
var payload = JsonSerializer.Serialize(user);
|
||||
var ttl = TimeSpan.FromMinutes(5);
|
||||
|
||||
await r.StringSetAsync(UserIdKey(tid, user.Id), payload, ttl);
|
||||
await r.StringSetAsync(UserIdKey(tid.ToString(), user.Id), payload, ttl);
|
||||
|
||||
var email = user.Identities
|
||||
.FirstOrDefault(i => i.Type == IdentityType.Email && i.IsPrimary)?.Identifier;
|
||||
if (!string.IsNullOrWhiteSpace(email))
|
||||
await r.StringSetAsync(IdentityEmailKey(tid, email!), payload, ttl);
|
||||
await r.StringSetAsync(IdentityEmailKey(tid.ToString(), email!), payload, ttl);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Identities =========
|
||||
|
||||
// (1) IMPLEMENTED: AddIdentityAsync
|
||||
public async Task AddIdentityAsync(Guid userId, IdentityType type, string identifier, bool isPrimary, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var norm = identifier.Trim().ToLowerInvariant();
|
||||
|
||||
if (isPrimary)
|
||||
@@ -171,11 +176,10 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
// (2) IMPLEMENTED: VerifyIdentityAsync
|
||||
public async Task VerifyIdentityAsync(Guid userId, IdentityType type, string identifier, DateTimeOffset verifiedAt, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var norm = identifier.Trim().ToLowerInvariant();
|
||||
|
||||
var id = await db.UserIdentities
|
||||
@@ -190,11 +194,10 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
// (3) IMPLEMENTED: GetPrimaryIdentityAsync
|
||||
public async Task<UserIdentity?> GetPrimaryIdentityAsync(Guid userId, IdentityType type, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
return await db.UserIdentities.AsNoTracking()
|
||||
.FirstOrDefaultAsync(i =>
|
||||
@@ -204,12 +207,10 @@ public class UserRepository : IUserRepository
|
||||
i.IsPrimary, ct);
|
||||
}
|
||||
|
||||
// ========= Password =========
|
||||
|
||||
public async Task ChangePasswordAsync(Guid userId, string newPasswordHash, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.TenantId == tid && u.Id == userId, ct);
|
||||
if (user is null) return;
|
||||
@@ -221,8 +222,8 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task AddPasswordHistoryAsync(Guid userId, string passwordHash, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var h = new UserPasswordHistory
|
||||
{
|
||||
@@ -236,12 +237,10 @@ public class UserRepository : IUserRepository
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
// ========= MFA =========
|
||||
|
||||
public async Task<UserMfaFactor> AddTotpFactorAsync(Guid userId, string label, string secret, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var f = new UserMfaFactor
|
||||
{
|
||||
@@ -265,8 +264,8 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task DisableMfaFactorAsync(Guid factorId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var f = await db.UserMfaFactors.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == factorId, ct);
|
||||
if (f is null) return;
|
||||
@@ -285,26 +284,45 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<bool> HasAnyMfaAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.UserMfaFactors.AnyAsync(x => x.TenantId == tid && x.UserId == userId && x.Enabled, ct);
|
||||
}
|
||||
|
||||
// ========= Sessions =========
|
||||
|
||||
public async Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
session.TenantId = TenantId();
|
||||
session.TenantId = tid;
|
||||
await db.UserSessions.AddAsync(session, ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return session;
|
||||
}
|
||||
|
||||
public async Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
|
||||
public async Task<UserSession?> FindSessionByRefreshHashAsync(Guid tenantId, string refreshTokenHash, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.UserSessions
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.RefreshTokenHash == refreshTokenHash, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> RotateSessionRefreshAsync(Guid tenantId, Guid sessionId, string newRefreshTokenHash, DateTimeOffset newIssuedAt, DateTimeOffset? newExpiresAt, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == sessionId, ct);
|
||||
if (s is null || s.RevokedAt.HasValue) return false;
|
||||
s.RefreshTokenHash = newRefreshTokenHash;
|
||||
s.IssuedAt = newIssuedAt;
|
||||
s.ExpiresAt = newExpiresAt;
|
||||
await db.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var s = await db.UserSessions.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == sessionId && x.UserId == userId, ct);
|
||||
if (s is null) return 0;
|
||||
@@ -315,8 +333,8 @@ public class UserRepository : IUserRepository
|
||||
|
||||
public async Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var sessions = await db.UserSessions
|
||||
.Where(x => x.TenantId == tid && x.UserId == userId && x.RevokedAt == null)
|
||||
@@ -326,4 +344,61 @@ public class UserRepository : IUserRepository
|
||||
|
||||
return await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<bool> IsSessionActiveAsync(Guid userId, Guid sessionId, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
return await db.UserSessions
|
||||
.AsNoTracking()
|
||||
.AnyAsync(x =>
|
||||
x.TenantId == tid &&
|
||||
x.Id == sessionId &&
|
||||
x.UserId == userId &&
|
||||
x.RevokedAt == null &&
|
||||
(!x.ExpiresAt.HasValue || x.ExpiresAt > now), ct);
|
||||
}
|
||||
|
||||
private static string TenantTokenVersionKey(Guid tenantId) => $"eop:{tenantId}:token:version";
|
||||
|
||||
public async Task<string> GetTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
if (r is null) return "1";
|
||||
var key = TenantTokenVersionKey(tenantId);
|
||||
var val = await r.StringGetAsync(key);
|
||||
if (val.HasValue) return val.ToString();
|
||||
await r.StringSetAsync(key, "1");
|
||||
return "1";
|
||||
}
|
||||
|
||||
public async Task BumpTenantTokenVersionAsync(Guid tenantId, CancellationToken ct = default)
|
||||
{
|
||||
var r = _redis?.GetDatabase();
|
||||
if (r is null) return;
|
||||
var key = TenantTokenVersionKey(tenantId);
|
||||
await r.StringIncrementAsync(key);
|
||||
}
|
||||
|
||||
public async Task<string?> GetUserSecurityStampAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
return await db.Users
|
||||
.AsNoTracking()
|
||||
.Where(x => x.TenantId == tid && x.Id == userId)
|
||||
.Select(x => x.SecurityStamp)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
}
|
||||
|
||||
public async Task BumpUserSecurityStampAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var tid = TenantId();
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
var u = await db.Users.FirstOrDefaultAsync(x => x.TenantId == tid && x.Id == userId, ct);
|
||||
if (u is null) return;
|
||||
u.SecurityStamp = Guid.NewGuid().ToString("N");
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
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 path = http.Request.Path.Value ?? string.Empty;
|
||||
|
||||
// ✅ ทางลัด: ขอ platform ตรง ๆ ด้วย hint
|
||||
if (hint is string hs && string.Equals(hs, "@platform", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (_map.TryGetBySlug(PlatformSlug, out var platform))
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace AMREZ.EOP.Infrastructures.Tenancy;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Bootstrap 'public' ถ้ายังไม่มี (กัน chicken-and-egg)
|
||||
if (!map.TryGetBySlug("public", out _))
|
||||
{
|
||||
map.UpsertTenant(
|
||||
|
||||
@@ -18,7 +18,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
||||
private readonly ITenantDbContextFactory _factory;
|
||||
private readonly IConnectionMultiplexer? _redis;
|
||||
|
||||
private AppDbContext? _db;
|
||||
private IDbContextTransaction? _tx;
|
||||
private ITenantContext? _tenant;
|
||||
|
||||
@@ -31,40 +30,38 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
||||
{
|
||||
_scope = scope;
|
||||
_factory = factory;
|
||||
_redis = redis; // optional; null ได้
|
||||
_redis = redis;
|
||||
}
|
||||
|
||||
public async Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default)
|
||||
{
|
||||
if (_db is not null) return;
|
||||
if (_tx is not null) return;
|
||||
_tenant = tenant ?? throw new ArgumentNullException(nameof(tenant));
|
||||
|
||||
_scope.EnsureForTenant(tenant);
|
||||
_db = _scope.Get<AppDbContext>();
|
||||
_tx = await _db.Database.BeginTransactionAsync(isolation, ct);
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
_tx = await db.Database.BeginTransactionAsync(isolation, ct);
|
||||
}
|
||||
|
||||
public async Task CommitAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_db is null) return; // ยังไม่ Begin
|
||||
if (_tx is null) return;
|
||||
|
||||
// track entities ที่เปลี่ยน (เพื่อ invalidate cache แบบแม่นขึ้น)
|
||||
var changedUsers = _db.ChangeTracker.Entries<User>()
|
||||
var db = _scope.Get<AppDbContext>();
|
||||
|
||||
var changedUsers = db.ChangeTracker.Entries<User>()
|
||||
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
|
||||
.Select(e => e.Entity)
|
||||
.ToList();
|
||||
|
||||
var changedIdentities = _db.ChangeTracker.Entries<UserIdentity>()
|
||||
var changedIdentities = db.ChangeTracker.Entries<UserIdentity>()
|
||||
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
|
||||
.Select(e => e.Entity)
|
||||
.ToList();
|
||||
|
||||
await _db.SaveChangesAsync(ct);
|
||||
await db.SaveChangesAsync(ct);
|
||||
await _tx.CommitAsync(ct);
|
||||
|
||||
if (_tx is not null)
|
||||
await _tx.CommitAsync(ct);
|
||||
|
||||
// optional: invalidate/refresh Redis (ล่มก็ไม่พัง UoW)
|
||||
if (_redis is not null && _tenant is not null && (changedUsers.Count > 0 || changedIdentities.Count > 0))
|
||||
{
|
||||
var r = _redis.GetDatabase();
|
||||
@@ -75,8 +72,6 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
||||
{
|
||||
var keyId = $"eop:{tenantId}:user:id:{u.Id:N}";
|
||||
tasks.Add(r.KeyDeleteAsync(keyId));
|
||||
|
||||
// refresh cache (ถ้าต้องการ)
|
||||
var payload = JsonSerializer.Serialize(u);
|
||||
var ttl = TimeSpan.FromMinutes(5);
|
||||
tasks.Add(r.StringSetAsync(keyId, payload, ttl));
|
||||
@@ -88,7 +83,7 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
||||
tasks.Add(r.KeyDeleteAsync(k));
|
||||
}
|
||||
|
||||
try { await Task.WhenAll(tasks); } catch { /* swallow */ }
|
||||
try { await Task.WhenAll(tasks); } catch { }
|
||||
}
|
||||
|
||||
await DisposeAsync();
|
||||
@@ -109,11 +104,9 @@ public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
try { _tx?.Dispose(); } catch { /* ignore */ }
|
||||
try { _db?.Dispose(); } catch { /* ignore */ }
|
||||
|
||||
_tx = null; _db = null; _tenant = null;
|
||||
|
||||
try { _tx?.Dispose(); } catch { }
|
||||
_tx = null;
|
||||
_tenant = null;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user