using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Pgvector.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using GeminiDotnet; using GeminiDotnet.Extensions.AI; using NexusReader.Data.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.AddDbContextFactory(options => options.UseNpgsql(pgConnectionString, x => x.UseVector())); } else { var sqliteConnectionString = configuration.GetConnectionString("SqliteConnection") ?? "Data Source=nexus.db"; services.AddDbContextFactory(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.AddEmbeddingGenerator(new GeminiEmbeddingGenerator(new GeminiClientOptions { ApiKey = aiSettings.ApiKey, ModelId = aiSettings.EmbeddingModel ?? "text-embedding-004" })); services.AddScoped(); services.AddTransient(); services.AddAuthorizationCore(options => { options.AddPolicy("ProUser", policy => policy.Requirements.Add(new ProUserRequirement())); }); services.AddScoped(); services.AddScoped(); return services; } public static System.Reflection.Assembly Assembly => typeof(DependencyInjection).Assembly; } public interface IInfrastructureMarker { } internal class InfrastructureMarker : IInfrastructureMarker { }