This commit is contained in:
Thanakarn Klangkasame
2025-09-30 11:01:02 +07:00
commit 92e614674c
182 changed files with 9596 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,12 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
}

View File

@@ -0,0 +1,13 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
public ICollection<RolePermission> RolePermissions { get; set; } = new List<RolePermission>();
}

View File

@@ -0,0 +1,13 @@
using AMREZ.EOP.Domain.Entities.Common;
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; }
public Role Role { get; set; } = default!;
public Permission Permission { get; set; } = default!;
}

View File

@@ -0,0 +1,24 @@
using AMREZ.EOP.Domain.Entities.Common;
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;
public int AccessFailedCount { get; set; }
public DateTimeOffset? LockoutEndUtc { get; set; }
public bool MfaEnabled { get; set; }
public string? SecurityStamp { get; set; }
public ICollection<UserIdentity> Identities { get; set; } = new List<UserIdentity>();
public ICollection<UserMfaFactor> MfaFactors { get; set; } = new List<UserMfaFactor>();
public ICollection<UserSession> Sessions { get; set; } = new List<UserSession>();
public ICollection<UserPasswordHistory> PasswordHistories { get; set; } = new List<UserPasswordHistory>();
public ICollection<UserExternalAccount> ExternalAccounts { get; set; } = new List<UserExternalAccount>();
public ICollection<UserRole> UserRoles { get; set; } = new List<UserRole>();
}

View File

@@ -0,0 +1,17 @@
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared._Users;
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; }
public string Subject { get; set; } = default!; // provider UID/sub
public string? Email { get; set; }
public DateTimeOffset LinkedAt { get; set; } = DateTimeOffset.UtcNow;
public User User { get; set; } = default!;
}

View File

@@ -0,0 +1,17 @@
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared._Users;
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; }
public string Identifier { get; set; } = default!;
public bool IsPrimary { get; set; }
public DateTimeOffset? VerifiedAt { get; set; }
public User User { get; set; } = default!;
}

View File

@@ -0,0 +1,25 @@
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared._Users;
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; }
public string? Label { get; set; }
public string? Secret { get; set; } // TOTP secret (encrypt at rest)
public string? PhoneE164 { get; set; }
public string? Email { get; set; }
public string? PublicKey { get; set; } // WebAuthn
public string? CredentialId { get; set; } // WebAuthn
public bool Enabled { get; set; } = true;
public DateTimeOffset AddedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? LastUsedAt { get; set; }
public User User { get; set; } = default!;
}

View File

@@ -0,0 +1,14 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public DateTimeOffset ChangedAt { get; set; } = DateTimeOffset.UtcNow;
public User User { get; set; } = default!;
}

View File

@@ -0,0 +1,13 @@
using AMREZ.EOP.Domain.Entities.Common;
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; }
public User User { get; set; } = default!;
public Role Role { get; set; } = default!;
}

View File

@@ -0,0 +1,20 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public DateTimeOffset IssuedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? ExpiresAt { get; set; }
public DateTimeOffset? RevokedAt { get; set; }
public string? DeviceId { get; set; }
public string? UserAgent { get; set; }
public string? IpAddress { get; set; }
public User User { get; set; } = default!;
}

View File

@@ -0,0 +1,13 @@
namespace AMREZ.EOP.Domain.Entities.Common;
public abstract class BaseEntity
{
public Guid Id { get; set; }
public string TenantId { get; set; } = default!;
public DateTimeOffset CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTimeOffset?UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
public bool IsDeleted { get; set; } = false;
}

View File

@@ -0,0 +1,16 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public Guid? ParentDepartmentId { get; set; }
public Department? Parent { get; set; }
public ICollection<Department> Children { get; set; } = new List<Department>();
public ICollection<UserProfile> Profiles { get; set; } = new List<UserProfile>();
}

View File

@@ -0,0 +1,18 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public string Relationship { get; set; } = default!;
public string? Phone { get; set; }
public string? Email { get; set; }
public bool IsPrimary { get; set; }
public UserProfile UserProfile { get; set; } = default!;
}

View File

@@ -0,0 +1,23 @@
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared.HumanResources;
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;
public string Line1 { get; set; } = default!;
public string? Line2 { get; set; }
public string City { get; set; } = default!;
public string? State { get; set; }
public string PostalCode { get; set; } = default!;
public string Country { get; set; } = default!;
public bool IsPrimary { get; set; }
public UserProfile UserProfile { get; set; } = default!;
}

View File

@@ -0,0 +1,18 @@
using AMREZ.EOP.Domain.Entities.Common;
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!;
public string AccountNumber { get; set; } = default!;
public string AccountHolder { get; set; } = default!;
public string? Branch { get; set; }
public string? Note { get; set; }
public bool IsPrimary { get; set; }
public UserProfile UserProfile { get; set; } = default!;
}

View File

@@ -0,0 +1,25 @@
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared.HumanResources;
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;
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public Guid? DepartmentId { get; set; }
public Guid? PositionId { get; set; }
public Guid? ManagerUserId { get; set; } // references Authentications.User
public string? WorkEmail { get; set; }
public string? WorkPhone { get; set; }
public UserProfile UserProfile { get; set; } = default!;
public Department? Department { get; set; }
public Position? Position { get; set; }
}

View File

@@ -0,0 +1,13 @@
using AMREZ.EOP.Domain.Entities.Common;
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; }
public ICollection<UserProfile> Profiles { get; set; } = new List<UserProfile>();
}

View File

@@ -0,0 +1,26 @@
using AMREZ.EOP.Domain.Entities.Authentications;
using AMREZ.EOP.Domain.Entities.Common;
using AMREZ.EOP.Domain.Shared.HumanResources;
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!;
public string LastName { get; set; } = default!;
public string? MiddleName { get; set; }
public string? Nickname { get; set; }
public DateTime? DateOfBirth { get; set; }
public Gender? Gender { get; set; }
public User User { get; set; } = default!;
public ICollection<Employment> Employments { get; set; } = new List<Employment>();
public ICollection<EmployeeAddress> Addresses { get; set; } = new List<EmployeeAddress>();
public ICollection<EmergencyContact> EmergencyContacts { get; set; } = new List<EmergencyContact>();
public ICollection<EmployeeBankAccount> BankAccounts { get; set; } = new List<EmployeeBankAccount>();
}

View File

@@ -0,0 +1,13 @@
using AMREZ.EOP.Domain.Shared.Tenancy;
namespace AMREZ.EOP.Domain.Entities.Tenancy;
public sealed class TenantConfig
{
public string TenantKey { get; set; } = default!;
public string? Schema { get; set; }
public string? ConnectionString { get; set; }
public TenantMode Mode { get; set; } = TenantMode.Rls;
public bool IsActive { get; set; } = true;
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
}

View File

@@ -0,0 +1,10 @@
namespace AMREZ.EOP.Domain.Entities.Tenancy;
public class TenantDomain
{
public string Domain { get; set; } = default!;
public string? TenantKey { get; set; }
public bool IsPlatformBaseDomain { get; set; } = false;
public bool IsActive { get; set; } = true;
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
}

View File

@@ -0,0 +1,6 @@
namespace AMREZ.EOP.Domain.Shared.Contracts;
public static class AuthPolicies
{
public const string Scheme = "AuthCookie";
}

View File

@@ -0,0 +1,8 @@
namespace AMREZ.EOP.Domain.Shared.HumanResources;
public enum AddressType
{
Home = 1,
Mailing = 2,
Other = 9
}

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Domain.Shared.HumanResources;
public enum EmploymentType
{
Permanent = 1,
Contract = 2,
PartTime = 3,
Intern = 4
}

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Domain.Shared.HumanResources;
public enum Gender
{
Unknown = 0,
Male = 1,
Female = 2,
Other = 3
}

View File

@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Domain.Shared.Tenancy;
public static class HttpContextTenantExtensions
{
public static string? GetTargetTenantKey(this HttpContext http)
{
return http.Items.TryGetValue("TargetTenantKey", out var v)
? v as string
: http.Request.Headers["X-Tenant"].ToString()?.Trim().ToLowerInvariant();
}
}

View File

@@ -0,0 +1,8 @@
namespace AMREZ.EOP.Domain.Shared.Tenancy;
public enum TenantMode
{
Rls,
Schema,
Database
}

View File

@@ -0,0 +1,11 @@
namespace AMREZ.EOP.Domain.Shared._Users;
public enum ExternalProvider
{
Google = 1,
Apple = 2,
Microsoft = 3,
Facebook = 4,
Line = 5,
Other = 9
}

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Domain.Shared._Users;
public enum IdentityType
{
Email = 1,
Phone = 2,
Username = 3,
Other = 9
}

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Domain.Shared._Users;
public enum MfaType
{
Totp = 1,
Sms = 2,
EmailOtp = 3,
WebAuthn = 4
}