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
110 lines
5.4 KiB
C#
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;
|
|
}
|
|
}
|