using System.IO.Compression; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection.Extensions; using Serilog; using Tiku.Api.Caching; using Tiku.Api.Options; using Tiku.Application; using Tiku.Application.Security; using Tiku.Application.Tenancy; using Tiku.Infrastructure; using Tiku.Infrastructure.Caching; using Tiku.Infrastructure.Security; namespace Tiku.Api.Configuration; public static class DependencyInjection { public static WebApplicationBuilder AddApiServices(this WebApplicationBuilder builder) { builder.Services.AddSerilog((services, configuration) => configuration .ReadFrom.Configuration(builder.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext(), true); builder.Services.AddApiPresentation(); builder.Services.AddExceptionProblemDetailsMappers(); builder.Services.AddHealthChecks(); builder.Services.AddApiObservability(builder.Configuration, builder.Environment); builder.Services.AddApplication(); builder.Services.AddNetworkConfiguration(builder.Configuration, builder.Environment); builder.Services.AddApiRateLimiting(builder.Configuration); var connectionString = OptionsValidation.ResolveDatabaseConnectionString( builder.Configuration, builder.Environment.IsDevelopment()); builder.Services.AddInfrastructure(connectionString); var redisConnectionString = builder.Configuration.GetConnectionString("Redis") ?? builder.Configuration["REDIS_URL"]; builder.Services.AddOptions() .Configure(options => options.ConnectionString = redisConnectionString ?? string.Empty) .Validate( options => !builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(options.ConnectionString), "Redis is required in Production.") .ValidateOnStart(); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection(AuthorizationCacheOptions.SectionName)) .Validate(options => options.LocalSnapshotSeconds > 0 && options.DistributedStateSeconds > 0 && options.DistributedSnapshotSeconds > 0 && options.JitterPercent is >= 0 and <= 50, "Authorization cache durations must be positive and jitter must be between 0 and 50 percent.") .ValidateOnStart(); if (!string.IsNullOrWhiteSpace(redisConnectionString)) builder.Services.AddRedisSecurity(redisConnectionString, builder.Environment.EnvironmentName); else if (builder.Environment.IsProduction()) throw new InvalidOperationException( "Redis is required in Production. Configure ConnectionStrings:Redis or REDIS_URL."); builder.Services.AddBusinessCaching( builder.Environment.EnvironmentName, !string.IsNullOrWhiteSpace(redisConnectionString)); builder.Services.AddOutputCache(options => { options.AddPolicy("TenantPublic", new TenantPublicOutputCachePolicy()); options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(60); }); builder.Services.RemoveAll(); builder.Services.AddSingleton(); if (builder.Environment.IsProduction() && !string.IsNullOrWhiteSpace(redisConnectionString)) builder.Services.AddStackExchangeRedisOutputCache(options => { options.Configuration = redisConnectionString; options.InstanceName = $"tiku:{builder.Environment.EnvironmentName.ToLowerInvariant()}:output:"; }); builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add(); options.Providers.Add(); options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/json"]); }); builder.Services.Configure(options => options.Level = CompressionLevel.Fastest); builder.Services.Configure(options => options.Level = CompressionLevel.Fastest); builder.Services.AddApiDataProtection(builder.Configuration, builder.Environment); builder.Services.AddExternalServiceOptions(builder.Configuration, builder.Environment); builder.Services.AddApiAuthenticationAndAuthorization(builder.Configuration, builder.Environment); return builder; } }