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,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AMREZ.EOP.Contracts\AMREZ.EOP.Contracts.csproj" />
<ProjectReference Include="..\AMREZ.EOP.Domain\AMREZ.EOP.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
namespace AMREZ.EOP.Abstractions.Applications.Tenancy;
public interface ITenantContext
{
string TenantKey { get; }
string Id { get; }
string? Schema { get; }
string? ConnectionString { get; }
}

View File

@@ -0,0 +1,6 @@
namespace AMREZ.EOP.Abstractions.Applications.Tenancy;
public interface ITenantDbContextFactory
{
TContext Create<TContext>(ITenantContext tenant) where TContext : class;
}

View File

@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Http;
namespace AMREZ.EOP.Abstractions.Applications.Tenancy;
public interface ITenantResolver
{
ITenantContext? Resolve(HttpContext http, object? hint = null);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.AddEmailIdentity;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IAddEmailIdentityUseCase
{
Task<bool> ExecuteAsync(AddEmailIdentityRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.ChangePassword;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IChangePasswordUseCase
{
Task<bool> ExecuteAsync(ChangePasswordRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.DisableMfa;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IDisableMfaUseCase
{
Task<bool> ExecuteAsync(DisableMfaRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.EnableTotp;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IEnableTotpUseCase
{
Task<EnableTotpResponse?> ExecuteAsync(EnableTotpRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.Login;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface ILoginUseCase
{
Task<LoginResponse?> ExecuteAsync(LoginRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.LogoutAll;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface ILogoutAllUseCase
{
Task<int> ExecuteAsync(LogoutAllRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.Logout;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface ILogoutUseCase
{
Task<bool> ExecuteAsync(LogoutRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.Register;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IRegisterUseCase
{
Task<RegisterResponse?> ExecuteAsync(RegisterRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Authentications.VerifyEmail;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
public interface IVerifyEmailUseCase
{
Task<bool> ExecuteAsync(VerifyEmailRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmergencyContact;
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmergencyContactAdd;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IAddEmergencyContactUseCase
{
Task<EmergencyContactResponse?> ExecuteAsync(EmergencyContactAddRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmployeeAddress;
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmployeeAddressAdd;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IAddEmployeeAddressUseCase
{
Task<EmployeeAddressResponse?> ExecuteAsync(EmployeeAddressAddRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmployeeBankAccount;
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmployeeBankAccountAdd;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IAddEmployeeBankAccountUseCase
{
Task<EmployeeBankAccountResponse?> ExecuteAsync(EmployeeBankAccountAddRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.Employment;
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmploymentAdd;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IAddEmploymentUseCase
{
Task<EmploymentResponse?> ExecuteAsync(EmploymentAddRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.EmploymentEnd;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IEndEmploymentUseCase
{
Task<bool> ExecuteAsync(EmploymentEndRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.SetPrimaryAddress;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface ISetPrimaryAddressUseCase
{
Task<bool> ExecuteAsync(SetPrimaryAddressRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.SetPrimaryBankAccount;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface ISetPrimaryBankAccountUseCase
{
Task<bool> ExecuteAsync(SetPrimaryBankAccountRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.SetPrimaryEmergencyContact;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface ISetPrimaryEmergencyContactUseCase
{
Task<bool> ExecuteAsync(SetPrimaryEmergencyContactRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Contracts.DTOs.HumanResources.UserProfile;
using AMREZ.EOP.Contracts.DTOs.HumanResources.UserProfileUpsert;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
public interface IUpsertUserProfileUseCase
{
Task<UserProfileResponse?> ExecuteAsync(UserProfileUpsertRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.AddBaseDomain;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IAddBaseDomainUseCase
{
Task<AddBaseDomainResponse?> ExecuteAsync(AddBaseDomainRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.CreateTenant;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface ICreateTenantUseCase
{
Task<CreateTenantResponse?> ExecuteAsync(CreateTenantRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,6 @@
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IDeleteTenantUseCase
{
Task<bool> ExecuteAsync(string tenantKey, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.ListDomains;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IListDomainsUseCase
{
Task<ListDomainsResponse?> ExecuteAsync(ListDomainsRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.ListTenants;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IListTenantsUseCase
{
Task<ListTenantsResponse?> ExecuteAsync(ListTenantsRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.MapDomain;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IMapDomainUseCase
{
Task<MapDomainResponse?> ExecuteAsync(MapDomainRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.RemoveBaseDomain;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IRemoveBaseDomainUseCase
{
Task<RemoveBaseDomainResponse?> ExecuteAsync(RemoveBaseDomainRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,6 @@
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface ITenantProvisioner
{
Task ProvisionAsync(string tenantKey, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.UnmapDomain;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IUnmapDomainUseCase
{
Task<UnmapDomainResponse?> ExecuteAsync(UnmapDomainRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,8 @@
using AMREZ.EOP.Contracts.DTOs.Tenancy.UpdateTenant;
namespace AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
public interface IUpdateTenantUseCase
{
Task<UpdateTenantResponse?> ExecuteAsync(UpdateTenantRequest request, CancellationToken ct = default);
}

View File

@@ -0,0 +1,12 @@
using System.Data;
using AMREZ.EOP.Abstractions.Applications.Tenancy;
namespace AMREZ.EOP.Abstractions.Infrastructures.Common;
public interface IUnitOfWork
{
Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default);
Task CommitAsync(CancellationToken ct = default);
Task RollbackAsync(CancellationToken ct = default);
string Backend { get; } // "ef" | "redis"
}

View File

@@ -0,0 +1,21 @@
using AMREZ.EOP.Domain.Entities.Tenancy;
namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories;
public interface ITenantRepository
{
Task<bool> TenantExistsAsync(string tenantKey, CancellationToken ct = default);
Task<TenantConfig?> GetAsync(string tenantKey, CancellationToken ct = default);
Task<IReadOnlyList<TenantConfig>> ListTenantsAsync(CancellationToken ct = default);
Task<bool> CreateAsync(TenantConfig row, CancellationToken ct = default);
Task<bool> UpdateAsync(TenantConfig row, DateTimeOffset? ifUnmodifiedSince, CancellationToken ct = default);
Task<bool> DeleteAsync(string tenantKey, CancellationToken ct = default);
Task<bool> MapDomainAsync(string domain, string tenantKey, CancellationToken ct = default);
Task<bool> UnmapDomainAsync(string domain, CancellationToken ct = default);
Task<IReadOnlyList<TenantDomain>> ListDomainsAsync(string? tenantKey, CancellationToken ct = default);
Task<bool> AddBaseDomainAsync(string baseDomain, CancellationToken ct = default);
Task<bool> RemoveBaseDomainAsync(string baseDomain, CancellationToken ct = default);
}

View File

@@ -0,0 +1,21 @@
using AMREZ.EOP.Domain.Entities.HumanResources;
namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories;
public interface IUserProfileRepository
{
Task<UserProfile?> GetByUserIdAsync(Guid userId, CancellationToken ct = default);
Task UpsertAsync(UserProfile profile, CancellationToken ct = default);
Task<Employment> AddEmploymentAsync(Employment e, CancellationToken ct = default);
Task EndEmploymentAsync(Guid employmentId, DateTime endDate, CancellationToken ct = default);
Task<EmployeeAddress> AddAddressAsync(EmployeeAddress a, CancellationToken ct = default);
Task SetPrimaryAddressAsync(Guid userProfileId, Guid addressId, CancellationToken ct = default);
Task<EmergencyContact> AddEmergencyContactAsync(EmergencyContact c, CancellationToken ct = default);
Task SetPrimaryEmergencyContactAsync(Guid userProfileId, Guid contactId, CancellationToken ct = default);
Task<EmployeeBankAccount> AddBankAccountAsync(EmployeeBankAccount b, CancellationToken ct = default);
Task SetPrimaryBankAccountAsync(Guid userProfileId, Guid bankAccountId, CancellationToken ct = default);
}

View File

@@ -0,0 +1,31 @@
using AMREZ.EOP.Domain.Entities.Authentications;
using AMREZ.EOP.Domain.Shared._Users;
namespace AMREZ.EOP.Abstractions.Infrastructures.Repositories;
public interface IUserRepository
{
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
Task<UserSession> CreateSessionAsync(UserSession session, CancellationToken ct = default);
Task<int> RevokeSessionAsync(Guid userId, Guid sessionId, CancellationToken ct = default);
Task<int> RevokeAllSessionsAsync(Guid userId, CancellationToken ct = default);
}

View File

@@ -0,0 +1,7 @@
namespace AMREZ.EOP.Abstractions.Security;
public interface IPasswordHasher
{
bool Verify(string plain, string hash);
string Hash(string plain);
}

View File

@@ -0,0 +1,9 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
namespace AMREZ.EOP.Abstractions.Storage;
public interface IDbScope
{
void EnsureForTenant(ITenantContext tenant);
TContext Get<TContext>() where TContext : class;
}