using InboxIntel.Application.Abstractions; using InboxIntel.Domain.Enums; using InboxIntel.Infrastructure.Ai; using InboxIntel.Infrastructure.Analytics; using InboxIntel.Infrastructure.Cleanup; using InboxIntel.Infrastructure.Configuration; using InboxIntel.Infrastructure.Export; using InboxIntel.Infrastructure.Gmail; using InboxIntel.Infrastructure.Notifications; using InboxIntel.Infrastructure.Persistence; using InboxIntel.Infrastructure.Search; using InboxIntel.Infrastructure.Security; using InboxIntel.Infrastructure.Sync; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace InboxIntel.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration config) { // EF Core / PostgreSQL. UseVector() enables pgvector mapping for the semantic-search // embedding column (requires the 'vector' extension — added by the AddEmbeddingColumn migration). services.AddDbContext(opt => opt.UseNpgsql(config.GetConnectionString("Postgres"), npg => npg.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName).UseVector())); services.AddScoped(sp => sp.GetRequiredService()); // Options services.Configure(config.GetSection(GoogleOAuthOptions.SectionName)); services.Configure(config.GetSection(GmailSyncOptions.SectionName)); services.Configure(config.GetSection(AiOptions.SectionName)); services.Configure(config.GetSection(SmtpOptions.SectionName)); services.Configure(config.GetSection(DigestOptions.SectionName)); // Security services.AddSingleton(); // Gmail services.AddScoped(); services.AddScoped(); // Background sync queue (singleton) + its worker. services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddHostedService(); // Core services services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Notifications (digest emails) services.AddScoped(); services.AddScoped(); services.AddHostedService(); // AUDIT H-3: opt-in local data retention (worker no-ops while disabled). services.Configure(config.GetSection(DataRetentionOptions.SectionName)); services.AddScoped(); services.AddHostedService(); // HTTP clients // V-01: do NOT follow redirects — a validated external URL must not be able to // 3xx-redirect into an internal target after SafeHttpGuard has checked it. services.AddHttpClient("unsubscribe", c => c.Timeout = TimeSpan.FromSeconds(15)) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false }); services.AddHttpClient("ollama"); services.AddHttpClient("openai"); // AI provider selected by configured mode. Embeddings come from Ollama when local, // otherwise the Null provider (empty vectors) so semantic features degrade to lexical. var aiMode = config.GetSection(AiOptions.SectionName).GetValue("Mode"); switch (aiMode) { case AiProviderMode.LocalOllama: services.AddScoped(); services.AddScoped(); break; case AiProviderMode.CloudOpenAi: services.AddScoped(); services.AddScoped(); // OpenAI embeddings: future break; default: services.AddScoped(); services.AddScoped(); break; } services.AddScoped(); // Feature flags + AI policy gate (docs/discovery/multi-provider/04). Cached 15s, // fail-closed. Admin toggle surface arrives with the multi-provider admin phase. services.AddMemoryCache(); services.AddScoped(); services.AddScoped(); // Semantic search: fills Email.Embedding in the background; no-ops when the // embedding provider is unavailable (AI disabled), so lexical search is unaffected. services.AddHostedService(); // Background worker (daily incremental sync + aggregate refresh) services.AddHostedService(); return services; } }