e307a54bec
Review follow-ups: advance the dispatcher's "since" marker after each delivered alert (not once per batch) so a future throwing sink can't re-deliver already-sent alerts; give the Telegram HttpClient a 15s timeout so a hung connection can't stall the dispatch loop.
75 lines
3.0 KiB
C#
75 lines
3.0 KiB
C#
using Marathon.Application.Abstractions;
|
|
using Marathon.Application.Configuration;
|
|
using Marathon.Infrastructure.Configuration;
|
|
using Marathon.Infrastructure.Notifications;
|
|
using Marathon.Infrastructure.Persistence;
|
|
using Marathon.Infrastructure.Scraping;
|
|
using Marathon.Infrastructure.Workers;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Marathon.Infrastructure;
|
|
|
|
/// <summary>
|
|
/// Top-level DI composition entry-point for all Infrastructure sub-modules
|
|
/// (Persistence, Scraping, Workers).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Call <see cref="AddMarathonInfrastructure"/> once from the host's DI
|
|
/// setup. This replaces the previous reflection-based wiring in
|
|
/// <c>App.xaml.cs::TryAddApplicationAndInfrastructure</c>.
|
|
/// </remarks>
|
|
public static class InfrastructureModule
|
|
{
|
|
/// <summary>
|
|
/// Registers the complete Infrastructure layer:
|
|
/// <list type="bullet">
|
|
/// <item>EF Core / SQLite persistence (<see cref="PersistenceModule.AddMarathonPersistence"/>).</item>
|
|
/// <item>HttpClient + AngleSharp + Polly scraping (<see cref="ScrapingModule.AddMarathonScraping"/>).</item>
|
|
/// <item><see cref="WorkerOptions"/> bound to the <c>Workers</c> config section.</item>
|
|
/// <item>Three <see cref="Microsoft.Extensions.Hosting.BackgroundService"/> pollers.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public static IServiceCollection AddMarathonInfrastructure(
|
|
this IServiceCollection services,
|
|
IConfiguration config)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
ArgumentNullException.ThrowIfNull(config);
|
|
|
|
services.AddMarathonPersistence(config);
|
|
services.AddMarathonScraping(config);
|
|
|
|
services
|
|
.AddOptions<WorkerOptions>()
|
|
.Bind(config.GetSection(WorkerOptions.SectionName));
|
|
|
|
services
|
|
.AddOptions<AnomalyOptions>()
|
|
.Bind(config.GetSection(AnomalyOptions.SectionName));
|
|
|
|
services
|
|
.AddOptions<ScrapingThrottle>()
|
|
.Bind(config.GetSection(ScrapingThrottle.SectionName));
|
|
|
|
services
|
|
.AddOptions<NotificationOptions>()
|
|
.Bind(config.GetSection(NotificationOptions.SectionName));
|
|
|
|
services.AddHostedService<UpcomingEventsPoller>();
|
|
services.AddHostedService<LiveOddsPoller>();
|
|
services.AddHostedService<ResultsWatchListPoller>();
|
|
services.AddHostedService<AnomalyDetectionPoller>();
|
|
|
|
// Outbound anomaly notifications (Telegram). Sink + dispatcher are always
|
|
// registered; the dispatcher idles until Notifications:Enabled is true and
|
|
// the sink no-ops until a bot token + chat id are configured.
|
|
services.AddHttpClient(TelegramNotificationSink.HttpClientName, client =>
|
|
client.Timeout = TimeSpan.FromSeconds(15));
|
|
services.AddSingleton<INotificationSink, TelegramNotificationSink>();
|
|
services.AddHostedService<AnomalyNotificationDispatcher>();
|
|
|
|
return services;
|
|
}
|
|
}
|