using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using GeminiDotnet; using GeminiDotnet.Extensions.AI; using NexusReader.Infrastructure.Persistence; using NexusReader.Application.Abstractions.Services; using NexusReader.Infrastructure.Services; using NexusReader.Infrastructure.Configuration; using Polly; using Polly.Retry; using NexusReader.Domain.Entities; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Authorization; using NexusReader.Application.Security.Authorization; namespace NexusReader.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { var pgConnectionString = configuration.GetConnectionString("PostgresConnection"); if (!string.IsNullOrEmpty(pgConnectionString)) { services.AddDbContext(options => options.UseNpgsql(pgConnectionString)); } else { var sqliteConnectionString = configuration.GetConnectionString("SqliteConnection") ?? "Data Source=nexus.db"; services.AddDbContext(options => options.UseSqlite(sqliteConnectionString)); } services.Configure(configuration.GetSection(AiSettings.SectionName)); services.Configure(configuration.GetSection(StripeSettings.SectionName)); var aiSettings = configuration.GetSection(AiSettings.SectionName).Get() ?? new AiSettings(); Console.WriteLine($"[Infrastructure] AI Configured: Model={aiSettings.Model}, KeyPresent={!string.IsNullOrWhiteSpace(aiSettings.ApiKey) && aiSettings.ApiKey != "PLACEHOLDER"}"); if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER") { Console.WriteLine("[Infrastructure] WARNING: AI API Key is missing or placeholder!"); } services.AddResiliencePipeline("ai-retry", builder => { builder.AddRetry(new RetryStrategyOptions { ShouldHandle = new PredicateBuilder().Handle(ex => ex.Message.Contains("429") || ex.Message.Contains("Too Many Requests") || ex.Message.Contains("quota")), BackoffType = DelayBackoffType.Exponential, UseJitter = true, MaxRetryAttempts = aiSettings.RetryAttempts, Delay = TimeSpan.FromSeconds(2) }); }); services.AddChatClient(new GeminiChatClient(new GeminiClientOptions { ApiKey = aiSettings.ApiKey, ModelId = aiSettings.Model })); services.AddScoped(); services.AddTransient(); services.AddAuthorizationCore(options => { options.AddPolicy("ProUser", policy => policy.Requirements.Add(new ProUserRequirement())); }); services.AddScoped(); services.AddMediatR(config => { config.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly); }); return services; } }