using Application.Abstractions.Authentication; using Application.Abstractions.Cache; using Application.Abstractions.Chat; using Application.Abstractions.Crypto; using Application.Abstractions.Data; using Application.Abstractions.Identity; using Application.Abstractions.Messaging.Email; using Infrastructure.Authentication; using Infrastructure.Cache; using Infrastructure.Chat; using Infrastructure.Crypto; using Infrastructure.Messaging.Email; using Infrastructure.Persistence; using Infrastructure.Persistence.Identity; using Infrastructure.Storage; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using SharedKernel; using SharedKernel.Storage; using StackExchange.Redis; using System.Text; namespace Infrastructure { public static class DependencyInjection { // SQL Server private static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration) { var dbConn = configuration.GetConnectionString("DefaultConnection"); if (string.IsNullOrWhiteSpace(dbConn)) { throw new InvalidOperationException("Connection string 'DefaultConnection' is not configured."); } services.AddDbContext(options => options.UseSqlServer(dbConn)); services.AddDbContext(options => options.UseSqlServer(dbConn)); services.AddScoped(sp => sp.GetRequiredService()); return services; } // Redis Server public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration) { var settings = configuration.Get()!; var redis = ConnectionMultiplexer.Connect(settings.Redis.DefaultConnection); services.AddSingleton(redis); services.AddDataProtection().SetApplicationName(settings.App.Name).PersistKeysToStackExchangeRedis(redis, settings.Redis.DataProtectionKey).SetDefaultKeyLifetime(settings.Redis.DefaultKeyLifetime); // 기본 90일 주기 // Distributed Cache 설정 services.AddStackExchangeRedisCache(options => { options.Configuration = settings.Redis.DefaultConnection; options.InstanceName = settings.Redis.CachePrefix; }); return services; } private static IServiceCollection AddHealthChecks(this IServiceCollection services, IConfiguration configuration) { var settings = configuration.Get()!; services.AddHealthChecks().AddSqlServer(settings.ConnectionStrings.DefaultConnection).AddRedis(settings.Redis.DefaultConnection); return services; } private static IServiceCollection AddServices(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddHttpClient(client => { client.BaseAddress = new Uri("https://api.upbit.com/v1/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.Timeout = TimeSpan.FromSeconds(10); }); services.AddSingleton(); return services; } /** * ======================================================================================================================================================================================================== * ======================================================================================================================================================================================================== */ // Admin 전용 public static IServiceCollection AddAdminInfrastructure(this IServiceCollection services, IConfiguration configuration) { return services.AddDatabase(configuration).AddRedis(configuration).AddServices().AddHealthChecks(configuration); } /** * ======================================================================================================================================================================================================== * ======================================================================================================================================================================================================== */ // API 전용 public static IServiceCollection AddApiInfrastructure(this IServiceCollection services, IConfiguration configuration) { return services.AddDatabase(configuration).AddRedis(configuration).AddApiAuthentication(configuration).AddServices().AddUpbit().AddHealthChecks(configuration); } private static IServiceCollection AddUpbit(this IServiceCollection services) { services.AddHostedService(); return services; } private static IServiceCollection AddApiAuthentication(this IServiceCollection services, IConfiguration configuration) { var settings = configuration.Get()!; // 인증 정책- JWT Bearer services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.MapInboundClaims = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = settings.JWT.Issuer, ValidAudience = settings.JWT.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.JWT.SecretKey)), ClockSkew = TimeSpan.Zero }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) { context.Token = accessToken; } return Task.CompletedTask; } }; }); services.AddAuthorization(); // Identity Core (Admin Handler의 UserManager/RoleManager 의존 해소용) services.AddIdentityCore().AddRoles().AddEntityFrameworkStores(); return services; } } }