90 lines
4.0 KiB
C#
90 lines
4.0 KiB
C#
using BlueLaminate.Core.Listings;
|
|
using BlueLaminate.Core.Options;
|
|
using BlueLaminate.Core.Skins;
|
|
using BlueLaminate.EFCore.DependencyInjection;
|
|
using BlueLaminate.Scraper.CsFloat;
|
|
using BlueLaminate.Scraper.Skins;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace BlueLaminate.Core.DependencyInjection;
|
|
|
|
/// <summary>
|
|
/// The single composition root for BlueLaminate's application logic. Any frontend
|
|
/// — the CLI today, a web UI later — wires up the whole stack with one call:
|
|
/// <c>services.AddBlueLaminateCore(configuration)</c>. Nothing about the database,
|
|
/// the CSFloat client, or the sweep/sync services is duplicated per host.
|
|
/// </summary>
|
|
public static class ServiceCollectionExtensions
|
|
{
|
|
private const string CsFloatHttpClient = "csfloat";
|
|
private const string CatalogHttpClient = "catalog";
|
|
|
|
public static IServiceCollection AddBlueLaminateCore(
|
|
this IServiceCollection services,
|
|
IConfiguration configuration)
|
|
{
|
|
var connectionString = configuration.GetConnectionString("SkinTracker")
|
|
?? throw new InvalidOperationException(
|
|
"Connection string 'SkinTracker' is not configured. Set it via user secrets (dev) "
|
|
+ "or the ConnectionStrings__SkinTracker environment variable (prod).");
|
|
|
|
// Database (DbContext is registered scoped by the EFCore extension).
|
|
services.AddSkinTrackerData(connectionString);
|
|
|
|
// Options bound from configuration. The CsFloat API key falls back to the
|
|
// legacy CSFLOAT_API_KEY environment variable so existing setups keep working.
|
|
services.AddOptions<CsFloatOptions>()
|
|
.Bind(configuration.GetSection(CsFloatOptions.SectionName))
|
|
.Configure(o =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(o.ApiKey))
|
|
{
|
|
o.ApiKey = configuration["CSFLOAT_API_KEY"];
|
|
}
|
|
})
|
|
.ValidateDataAnnotations()
|
|
.ValidateOnStart();
|
|
services.AddOptions<SkinCatalogOptions>()
|
|
.Bind(configuration.GetSection(SkinCatalogOptions.SectionName));
|
|
services.AddOptions<SweepOptions>()
|
|
.Bind(configuration.GetSection(SweepOptions.SectionName));
|
|
services.AddOptions<TradeupOptions>()
|
|
.Bind(configuration.GetSection(TradeupOptions.SectionName));
|
|
|
|
// Typed-handler pooling via IHttpClientFactory; clients are scoped so a
|
|
// command's handler and the service it drives share one instance (and thus
|
|
// the same LastRateLimit) within a single request scope.
|
|
services.AddHttpClient(CsFloatHttpClient, ConfigureHttpClient);
|
|
services.AddHttpClient(CatalogHttpClient, ConfigureHttpClient);
|
|
|
|
services.AddScoped(sp => new CsFloatListingsClient(
|
|
sp.GetRequiredService<IHttpClientFactory>().CreateClient(CsFloatHttpClient),
|
|
sp.GetRequiredService<IOptions<CsFloatOptions>>().Value,
|
|
sp.GetRequiredService<ILogger<CsFloatListingsClient>>()));
|
|
|
|
services.AddScoped(sp => new SkinCatalogClient(
|
|
sp.GetRequiredService<IHttpClientFactory>().CreateClient(CatalogHttpClient),
|
|
sp.GetRequiredService<IOptions<SkinCatalogOptions>>().Value));
|
|
|
|
// Application services (constructor injection; DbContext keeps them scoped).
|
|
services.AddScoped<ListingSweepService>();
|
|
services.AddScoped<SkinSyncService>();
|
|
services.AddScoped<CsMoney.CsMoneyIngestService>();
|
|
services.AddScoped<CsMoney.MarketPresenceService>();
|
|
services.AddScoped<SkinLand.SkinLandIngestService>();
|
|
services.AddScoped<Tradeups.TradeupGraphBuilder>();
|
|
services.AddScoped<Tradeups.TradeupFinder>();
|
|
|
|
return services;
|
|
}
|
|
|
|
private static void ConfigureHttpClient(HttpClient http)
|
|
{
|
|
http.Timeout = TimeSpan.FromMinutes(2);
|
|
http.DefaultRequestHeaders.UserAgent.ParseAdd("BlueLaminate");
|
|
}
|
|
}
|