2026-01-18 19:20:50 +01:00
|
|
|
using Hangfire;
|
|
|
|
|
using Hangfire.SqlServer;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Polly;
|
|
|
|
|
using Polly.Extensions.Http;
|
2026-01-21 12:03:24 +00:00
|
|
|
using Stripe;
|
2026-01-21 15:07:20 +00:00
|
|
|
using RealCV.Application.Interfaces;
|
|
|
|
|
using RealCV.Infrastructure.Configuration;
|
|
|
|
|
using RealCV.Infrastructure.Data;
|
|
|
|
|
using RealCV.Infrastructure.ExternalApis;
|
|
|
|
|
using RealCV.Infrastructure.Jobs;
|
|
|
|
|
using RealCV.Infrastructure.Services;
|
2026-01-18 19:20:50 +01:00
|
|
|
|
2026-01-21 15:07:20 +00:00
|
|
|
namespace RealCV.Infrastructure;
|
2026-01-18 19:20:50 +01:00
|
|
|
|
|
|
|
|
public static class DependencyInjection
|
|
|
|
|
{
|
|
|
|
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
|
|
|
|
{
|
|
|
|
|
// Configure DbContext with SQL Server
|
2026-01-20 16:54:58 +01:00
|
|
|
// AddDbContextFactory enables thread-safe parallel operations
|
|
|
|
|
services.AddDbContextFactory<ApplicationDbContext>(options =>
|
|
|
|
|
options.UseSqlServer(
|
|
|
|
|
configuration.GetConnectionString("DefaultConnection"),
|
|
|
|
|
sqlOptions =>
|
|
|
|
|
{
|
|
|
|
|
sqlOptions.EnableRetryOnFailure(
|
|
|
|
|
maxRetryCount: 3,
|
|
|
|
|
maxRetryDelay: TimeSpan.FromSeconds(30),
|
|
|
|
|
errorNumbersToAdd: null);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Also register DbContext for scoped injection (non-parallel scenarios)
|
2026-01-18 19:20:50 +01:00
|
|
|
services.AddDbContext<ApplicationDbContext>(options =>
|
|
|
|
|
options.UseSqlServer(
|
|
|
|
|
configuration.GetConnectionString("DefaultConnection"),
|
|
|
|
|
sqlOptions =>
|
|
|
|
|
{
|
|
|
|
|
sqlOptions.EnableRetryOnFailure(
|
|
|
|
|
maxRetryCount: 3,
|
|
|
|
|
maxRetryDelay: TimeSpan.FromSeconds(30),
|
|
|
|
|
errorNumbersToAdd: null);
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Configure Hangfire with SQL Server storage
|
|
|
|
|
services.AddHangfire(config => config
|
|
|
|
|
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
|
|
|
|
.UseSimpleAssemblyNameTypeSerializer()
|
|
|
|
|
.UseRecommendedSerializerSettings()
|
|
|
|
|
.UseSqlServerStorage(
|
|
|
|
|
configuration.GetConnectionString("HangfireConnection"),
|
|
|
|
|
new SqlServerStorageOptions
|
|
|
|
|
{
|
|
|
|
|
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
|
|
|
|
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
|
|
|
|
|
QueuePollInterval = TimeSpan.Zero,
|
|
|
|
|
UseRecommendedIsolationLevel = true,
|
|
|
|
|
DisableGlobalLocks = true
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
services.AddHangfireServer();
|
|
|
|
|
|
|
|
|
|
// Configure options
|
|
|
|
|
services.Configure<CompaniesHouseSettings>(
|
|
|
|
|
configuration.GetSection(CompaniesHouseSettings.SectionName));
|
|
|
|
|
|
|
|
|
|
services.Configure<AnthropicSettings>(
|
|
|
|
|
configuration.GetSection(AnthropicSettings.SectionName));
|
|
|
|
|
|
|
|
|
|
services.Configure<AzureBlobSettings>(
|
|
|
|
|
configuration.GetSection(AzureBlobSettings.SectionName));
|
|
|
|
|
|
2026-01-20 16:45:43 +01:00
|
|
|
services.Configure<LocalStorageSettings>(
|
|
|
|
|
configuration.GetSection(LocalStorageSettings.SectionName));
|
|
|
|
|
|
2026-01-21 12:03:24 +00:00
|
|
|
services.Configure<StripeSettings>(
|
|
|
|
|
configuration.GetSection(StripeSettings.SectionName));
|
|
|
|
|
|
|
|
|
|
// Configure Stripe API key
|
|
|
|
|
var stripeSettings = configuration.GetSection(StripeSettings.SectionName).Get<StripeSettings>();
|
|
|
|
|
if (!string.IsNullOrEmpty(stripeSettings?.SecretKey))
|
|
|
|
|
{
|
|
|
|
|
StripeConfiguration.ApiKey = stripeSettings.SecretKey;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 19:20:50 +01:00
|
|
|
// Configure HttpClient for CompaniesHouseClient with retry policy
|
|
|
|
|
services.AddHttpClient<CompaniesHouseClient>((serviceProvider, client) =>
|
|
|
|
|
{
|
|
|
|
|
var settings = configuration
|
|
|
|
|
.GetSection(CompaniesHouseSettings.SectionName)
|
|
|
|
|
.Get<CompaniesHouseSettings>();
|
|
|
|
|
|
|
|
|
|
if (settings is not null)
|
|
|
|
|
{
|
|
|
|
|
client.BaseAddress = new Uri(settings.BaseUrl);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.AddPolicyHandler(GetRetryPolicy());
|
|
|
|
|
|
|
|
|
|
// Register services
|
|
|
|
|
services.AddScoped<ICVParserService, CVParserService>();
|
2026-01-21 00:51:24 +01:00
|
|
|
services.AddScoped<ICompanyNameMatcherService, AICompanyNameMatcherService>();
|
2026-01-18 19:20:50 +01:00
|
|
|
services.AddScoped<ICompanyVerifierService, CompanyVerifierService>();
|
2026-01-20 16:45:43 +01:00
|
|
|
services.AddScoped<IEducationVerifierService, EducationVerifierService>();
|
2026-01-18 19:20:50 +01:00
|
|
|
services.AddScoped<ITimelineAnalyserService, TimelineAnalyserService>();
|
|
|
|
|
services.AddScoped<ICVCheckService, CVCheckService>();
|
2026-01-20 16:45:43 +01:00
|
|
|
services.AddScoped<IUserContextService, UserContextService>();
|
2026-01-20 20:58:12 +01:00
|
|
|
services.AddScoped<IAuditService, AuditService>();
|
2026-01-21 12:03:24 +00:00
|
|
|
services.AddScoped<IStripeService, StripeService>();
|
|
|
|
|
services.AddScoped<ISubscriptionService, Services.SubscriptionService>();
|
2026-01-20 16:45:43 +01:00
|
|
|
|
|
|
|
|
// Register file storage - use local storage if configured, otherwise Azure
|
|
|
|
|
var useLocalStorage = configuration.GetValue<bool>("UseLocalStorage");
|
|
|
|
|
if (useLocalStorage)
|
|
|
|
|
{
|
|
|
|
|
services.AddScoped<IFileStorageService, LocalFileStorageService>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
services.AddScoped<IFileStorageService, FileStorageService>();
|
|
|
|
|
}
|
2026-01-18 19:20:50 +01:00
|
|
|
|
|
|
|
|
// Register Hangfire jobs
|
|
|
|
|
services.AddTransient<ProcessCVCheckJob>();
|
2026-01-21 12:03:24 +00:00
|
|
|
services.AddTransient<ResetMonthlyUsageJob>();
|
2026-01-18 19:20:50 +01:00
|
|
|
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
|
|
|
|
|
{
|
|
|
|
|
return HttpPolicyExtensions
|
|
|
|
|
.HandleTransientHttpError()
|
|
|
|
|
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
|
|
|
|
|
.WaitAndRetryAsync(
|
|
|
|
|
retryCount: 3,
|
|
|
|
|
sleepDurationProvider: retryAttempt =>
|
|
|
|
|
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
|
|
|
|
onRetry: (outcome, timespan, retryAttempt, context) =>
|
|
|
|
|
{
|
|
|
|
|
// Logging could be added here via ILogger if injected
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|