using Serilog; using Tiku.Application; using Tiku.Infrastructure; using Tiku.Infrastructure.Security; using Tiku.Api.Caching; using Tiku.Api.BackgroundProcessing; using Microsoft.AspNetCore.ResponseCompression; using System.IO.Compression; using Tiku.Application.PlatformBilling; using Tiku.Application.Security; using Tiku.Application.Tenancy; using Microsoft.Extensions.DependencyInjection.Extensions; 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(), preserveStaticLogger: true); builder.Services.AddApiPresentation(); 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 = Options.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(); 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.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.AddOptions() .Bind(builder.Configuration.GetSection(BackgroundProcessingOptions.SectionName)) .Validate(BackgroundProcessingOptions.BeValid, "Background processing settings are invalid.") .ValidateOnStart(); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("TenantDomains")); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("SaasSubscriptions")); builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("FeatureUsageReconciliation")); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddApiDataProtection(builder.Configuration, builder.Environment); builder.Services.AddExternalServiceOptions(builder.Configuration, builder.Environment); builder.Services.AddApiAuthenticationAndAuthorization(builder.Configuration, builder.Environment); return builder; } }