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,137 @@
using AMREZ.EOP.Abstractions.Applications.Tenancy;
using AMREZ.EOP.Abstractions.Applications.UseCases.Authentications;
using AMREZ.EOP.Abstractions.Applications.UseCases.HumanResources;
using AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy;
using AMREZ.EOP.Abstractions.Infrastructures.Common;
using AMREZ.EOP.Abstractions.Infrastructures.Repositories;
using AMREZ.EOP.Abstractions.Security;
using AMREZ.EOP.Abstractions.Storage;
using AMREZ.EOP.Application.UseCases.Authentications;
using AMREZ.EOP.Application.UseCases.HumanResources;
using AMREZ.EOP.Application.UseCases.Tenancy;
using AMREZ.EOP.Domain.Shared.Tenancy;
using AMREZ.EOP.Infrastructures.Data;
using AMREZ.EOP.Infrastructures.Options;
using AMREZ.EOP.Infrastructures.Repositories;
using AMREZ.EOP.Infrastructures.Security;
using AMREZ.EOP.Infrastructures.Storage;
using AMREZ.EOP.Infrastructures.Tenancy;
using AMREZ.EOP.Infrastructures.UnitOfWork;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
namespace AMREZ.EOP.Infrastructures.DependencyInjections;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
services.AddHttpContextAccessor();
// Options
services.AddOptions<AuthOptions>()
.BindConfiguration("Connections")
.ValidateOnStart();
// Tenancy core (order matters for AddDbContext factory resolution)
services.AddSingleton(sp =>
{
var map = new TenantMap();
var opts = sp.GetRequiredService<IOptions<AuthOptions>>().Value;
map.UpsertTenant("public", new TenantContext
{
Id = "public",
Schema = "public",
ConnectionString = string.IsNullOrWhiteSpace(opts.DefaultConnection) ? null : opts.DefaultConnection,
Mode = TenantMode.Rls
});
return map;
});
services.AddHostedService<TenantMapLoader>();
services.AddScoped<SearchPathInterceptor>();
services.AddScoped<TenantRlsInterceptor>();
services.AddScoped<ITenantResolver, DefaultTenantResolver>();
services.AddScoped<ITenantDbContextFactory, TenantDbContextFactory>();
services.AddScoped<ITenantProvisioner, TenantProvisioner>();
// DbContext (attach RLS/search_path interceptors)
services.AddDbContext<AppDbContext>((sp, o) =>
{
var cfg = sp.GetRequiredService<IOptions<AuthOptions>>().Value;
o.UseNpgsql(cfg.DefaultConnection);
o.AddInterceptors(
sp.GetRequiredService<SearchPathInterceptor>(),
sp.GetRequiredService<TenantRlsInterceptor>()
);
});
// Db scope / UoW
services.AddScoped<IDbScope, DbScope>();
services.AddScoped<EFUnitOfWork>();
services.AddScoped<IUnitOfWork, EFUnitOfWork>();
// Security
services.AddScoped<IPasswordHasher, BcryptPasswordHasher>();
// Repositories — Auth & HR แยกกัน
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserProfileRepository, UserProfileRepository>();
services.AddScoped<ITenantRepository, TenantRepository>();
// UseCases — Authentication
services.AddScoped<ILoginUseCase, LoginUseCase>();
services.AddScoped<IRegisterUseCase, RegisterUseCase>();
services.AddScoped<IChangePasswordUseCase, ChangePasswordUseCase>();
services.AddScoped<IAddEmailIdentityUseCase, AddEmailIdentityUseCase>();
services.AddScoped<IVerifyEmailUseCase, VerifyEmailUseCase>();
services.AddScoped<IEnableTotpUseCase, EnableTotpUseCase>();
services.AddScoped<IDisableMfaUseCase, DisableMfaUseCase>();
services.AddScoped<ILogoutUseCase, LogoutUseCase>();
services.AddScoped<ILogoutAllUseCase, LogoutAllUseCase>();
// UseCases — HR
services.AddScoped<IUpsertUserProfileUseCase, UpsertUserProfileUseCase>();
services.AddScoped<IAddEmploymentUseCase, AddEmploymentUseCase>();
services.AddScoped<IEndEmploymentUseCase, EndEmploymentUseCase>();
services.AddScoped<IAddEmployeeAddressUseCase, AddEmployeeAddressUseCase>();
services.AddScoped<ISetPrimaryAddressUseCase, SetPrimaryAddressUseCase>();
services.AddScoped<IAddEmergencyContactUseCase, AddEmergencyContactUseCase>();
services.AddScoped<ISetPrimaryEmergencyContactUseCase, SetPrimaryEmergencyContactUseCase>();
services.AddScoped<IAddEmployeeBankAccountUseCase, AddEmployeeBankAccountUseCase>();
services.AddScoped<ISetPrimaryBankAccountUseCase, SetPrimaryBankAccountUseCase>();
// UseCases — Tenancy
services.AddScoped<ICreateTenantUseCase, CreateTenantUseCase>();
services.AddScoped<IUpdateTenantUseCase, UpdateTenantUseCase>();
services.AddScoped<IDeleteTenantUseCase, DeleteTenantUseCase>();
services.AddScoped<IMapDomainUseCase, MapDomainUseCase>();
services.AddScoped<IUnmapDomainUseCase, UnmapDomainUseCase>();
services.AddScoped<IAddBaseDomainUseCase, AddBaseDomainUseCase>();
services.AddScoped<IRemoveBaseDomainUseCase, RemoveBaseDomainUseCase>();
services.AddScoped<IListTenantsUseCase, ListTenantsUseCase>();
services.AddScoped<IListDomainsUseCase, ListDomainsUseCase>();
// Redis (optional)
services.AddSingleton<IConnectionMultiplexer?>(sp =>
{
var opts = sp.GetRequiredService<IOptions<AuthOptions>>().Value;
var conn = opts.RedisConnection;
if (string.IsNullOrWhiteSpace(conn)) return null;
try
{
var cfg = ConfigurationOptions.Parse(conn, true);
cfg.AbortOnConnectFail = false;
cfg.ConnectTimeout = 1000;
cfg.SyncTimeout = 1000;
var mux = ConnectionMultiplexer.Connect(cfg);
return mux.IsConnected ? mux : null;
}
catch { return null; }
});
return services;
}
}