Files
Inboxintel/src/InboxIntel.Infrastructure/DependencyInjection.cs
T
cesnimda 074d39d9a2
CI / backend (push) Successful in 1m4s
CI / frontend (push) Successful in 18s
CI / format (push) Successful in 1m1s
CI / db-tests (push) Successful in 1m4s
Deploy Staging / deploy (push) Successful in 37s
CI / backend (pull_request) Successful in 1m7s
CI / frontend (pull_request) Successful in 18s
CI / format (pull_request) Successful in 59s
CI / db-tests (pull_request) Successful in 1m5s
Security / secrets (push) Successful in 4s
Security / dependencies (push) Successful in 1m4s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 1m4s
feat(platform): feature flags + user settings foundation (#35)
2026-07-02 18:04:14 +02:00

110 lines
5.4 KiB
C#

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<AppDbContext>(opt =>
opt.UseNpgsql(config.GetConnectionString("Postgres"),
npg => npg.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName).UseVector()));
services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());
// Options
services.Configure<GoogleOAuthOptions>(config.GetSection(GoogleOAuthOptions.SectionName));
services.Configure<GmailSyncOptions>(config.GetSection(GmailSyncOptions.SectionName));
services.Configure<AiOptions>(config.GetSection(AiOptions.SectionName));
services.Configure<SmtpOptions>(config.GetSection(SmtpOptions.SectionName));
services.Configure<DigestOptions>(config.GetSection(DigestOptions.SectionName));
// Security
services.AddSingleton<ITokenProtector, DataProtectionTokenProtector>();
// Gmail
services.AddScoped<GmailClientFactory>();
services.AddScoped<IGmailService, GmailApiService>();
// Background sync queue (singleton) + its worker.
services.AddSingleton<SyncQueue>();
services.AddSingleton<ISyncQueue>(sp => sp.GetRequiredService<SyncQueue>());
services.AddHostedService<SyncQueueWorker>();
// Core services
services.AddScoped<ISyncService, SyncService>();
services.AddScoped<IAnalyticsService, AnalyticsService>();
services.AddScoped<ISearchService, SearchService>();
services.AddScoped<ICleanupService, CleanupService>();
services.AddScoped<IUnsubscribeService, UnsubscribeService>();
services.AddScoped<IExportService, ExportService>();
// Notifications (digest emails)
services.AddScoped<IEmailSender, SmtpEmailSender>();
services.AddScoped<IDigestService, DigestService>();
services.AddHostedService<DigestWorker>();
// AUDIT H-3: opt-in local data retention (worker no-ops while disabled).
services.Configure<DataRetentionOptions>(config.GetSection(DataRetentionOptions.SectionName));
services.AddScoped<Retention.RetentionService>();
services.AddHostedService<Retention.RetentionWorker>();
// 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<AiProviderMode>("Mode");
switch (aiMode)
{
case AiProviderMode.LocalOllama:
services.AddScoped<IAiProvider, OllamaProvider>();
services.AddScoped<IEmbeddingProvider, OllamaEmbeddingProvider>();
break;
case AiProviderMode.CloudOpenAi:
services.AddScoped<IAiProvider, OpenAiProvider>();
services.AddScoped<IEmbeddingProvider, NullEmbeddingProvider>(); // OpenAI embeddings: future
break;
default:
services.AddScoped<IAiProvider, NullAiProvider>();
services.AddScoped<IEmbeddingProvider, NullEmbeddingProvider>();
break;
}
services.AddScoped<IAiService, AiService>();
// 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<IFeatureFlags, Features.FeatureFlagService>();
services.AddScoped<IAiGate, Features.AiGate>();
// 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<EmbeddingBackfillWorker>();
// Background worker (daily incremental sync + aggregate refresh)
services.AddHostedService<GmailSyncWorker>();
return services;
}
}