148 lines
7.2 KiB
C#
148 lines
7.2 KiB
C#
using Predictalytics.Application.Interfaces;
|
|
using Predictalytics.Application.Services;
|
|
using Predictalytics.Infrastructure.Services;
|
|
using Predictalytics.Domain.Interfaces;
|
|
using Predictalytics.Infrastructure.Data;
|
|
using Predictalytics.Infrastructure.Data.Repositories;
|
|
using Predictalytics.Infrastructure.Providers.Azuro;
|
|
using Predictalytics.Infrastructure.Providers.Limitless;
|
|
using Predictalytics.Infrastructure.Providers.Polymarket;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Predictalytics.Infrastructure;
|
|
|
|
public static class DependencyInjection
|
|
{
|
|
public static IServiceCollection AddPredictalytics(this IServiceCollection services, IConfiguration configuration, string? connectionStringOverride = null, bool dbDebug = false)
|
|
{
|
|
if (dbDebug) Serilog.Log.Warning(">>> INFRASTRUCTURE: AddPredictalytics STARTING");
|
|
|
|
// MySQL / EF Core
|
|
var connectionString = connectionStringOverride;
|
|
if (string.IsNullOrWhiteSpace(connectionString))
|
|
{
|
|
connectionString = configuration.GetConnectionString("DefaultConnection")
|
|
?? "Server=localhost;Database=Predictalytics_dev;User=root;Password=;";
|
|
}
|
|
|
|
// Use MySqlConnectionStringBuilder to ensure valid format and parse components
|
|
var csBuilder = new MySqlConnector.MySqlConnectionStringBuilder(connectionString);
|
|
|
|
// Final safety check
|
|
if (string.IsNullOrWhiteSpace(csBuilder.Database))
|
|
{
|
|
Serilog.Log.Error("❌ INVALID CONNECTION STRING: Database name is empty! (Input length: {Length})", connectionString.Length);
|
|
throw new InvalidOperationException("The connection string is missing a valid 'Database' parameter.");
|
|
}
|
|
|
|
var maskedCs = csBuilder.ConnectionString.Replace(csBuilder.Password, "****");
|
|
if (dbDebug)
|
|
{
|
|
Serilog.Log.Warning("🗄️ Initializing database connection: {ConnectionString}", maskedCs);
|
|
Serilog.Log.Warning("🗄️ Target Server: {Server}, Database: {Database}", csBuilder.Server, csBuilder.Database);
|
|
}
|
|
|
|
// Explicitly register the connection string so we can use it elsewhere if needed
|
|
services.AddSingleton(csBuilder.ConnectionString);
|
|
|
|
services.AddDbContext<AppDbContext>(options =>
|
|
{
|
|
// Log exactly what is being used at the moment of configuration
|
|
if (dbDebug) Serilog.Log.Warning("🛠️ EF: Configuring AppDbContext. Target DB: '{Database}'", csBuilder.Database);
|
|
|
|
options.UseMySql(csBuilder.ConnectionString, new MySqlServerVersion(new Version(8, 0, 31)),
|
|
mysql =>
|
|
{
|
|
mysql.EnableRetryOnFailure(3, TimeSpan.FromSeconds(10), null);
|
|
mysql.CommandTimeout(600); // 10 minutes timeout for large migrations (e.g. ALTER TABLE Trades)
|
|
});
|
|
});
|
|
|
|
// Repositories
|
|
services.AddScoped<ITraderRepository, TraderRepository>();
|
|
services.AddScoped<ITradeRepository, TradeRepository>();
|
|
services.AddScoped<IMarketRepository, MarketRepository>();
|
|
services.AddScoped<IWatchlistRepository, WatchlistRepository>();
|
|
services.AddScoped<IAlertRepository, AlertRepository>();
|
|
services.AddScoped<IJobRepository, JobRepository>();
|
|
|
|
// Application Services
|
|
services.AddScoped<IPositionPnLEngine, PositionPnLEngine>();
|
|
services.AddScoped<IScoringService, ScoringService>();
|
|
services.AddScoped<IDiscoveryService, DiscoveryService>();
|
|
services.AddScoped<IAlertService, AlertService>();
|
|
services.AddScoped<IAnalyticsService, AnalyticsService>();
|
|
services.AddScoped<ICopytradingEstimator, CopytradingEstimator>();
|
|
services.AddScoped<ICopytradingBacktestHarness, CopytradingBacktestHarness>();
|
|
services.AddScoped<WatchlistService>();
|
|
services.AddSingleton<IRateLimiter, RateLimiterService>();
|
|
services.AddSingleton<IPlatformStatisticsService, PlatformStatisticsService>();
|
|
|
|
// Platform Providers
|
|
services.AddHttpClient();
|
|
services.AddHttpClient("LimitlessApi", c =>
|
|
{
|
|
c.BaseAddress = new Uri("https://api.limitless.exchange/");
|
|
c.DefaultRequestHeaders.Add("Accept", "application/json");
|
|
c.Timeout = TimeSpan.FromSeconds(60);
|
|
});
|
|
|
|
services.AddSingleton<PolymarketApiClient>();
|
|
services.AddSingleton<LimitlessApiClient>();
|
|
services.AddHttpClient<Predictalytics.Application.Interfaces.IOpenRouterApiClient, Predictalytics.Infrastructure.Providers.OpenRouter.OpenRouterApiClient>();
|
|
services.AddScoped<Predictalytics.Application.Interfaces.IAiStrategyAnalysisService, Predictalytics.Application.Services.AiStrategyAnalysisService>();
|
|
services.AddSingleton<IPlatformProvider, PolymarketProvider>();
|
|
services.AddSingleton<IPlatformProvider, LimitlessProvider>();
|
|
services.AddSingleton<IPlatformProvider, AzuroProvider>();
|
|
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies pending EF Core migrations and seeds default platform rows.
|
|
/// Requires the target database to already be "stamped" with the InitialBaseline
|
|
/// migration in __EFMigrationsHistory (see UMSETZUNGSPLAN.md, section B1) if it was
|
|
/// previously created via the old EnsureCreated + manual ALTER approach.
|
|
/// </summary>
|
|
public static async Task EnsureDatabaseAsync(IServiceProvider services, bool dbDebug = false)
|
|
{
|
|
using var scope = services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
|
|
|
try
|
|
{
|
|
if (dbDebug)
|
|
{
|
|
var maskedConnStr = System.Text.RegularExpressions.Regex.Replace(
|
|
db.Database.GetDbConnection().ConnectionString ?? "NULL", "Password=[^;]+", "Password=****");
|
|
Serilog.Log.Warning("🔍 DEBUG: Applying EF Core migrations. ConnectionString: {CS}", maskedConnStr);
|
|
}
|
|
|
|
await db.Database.MigrateAsync();
|
|
|
|
if (dbDebug) Serilog.Log.Warning("✅ DEBUG: Migrations applied successfully.");
|
|
|
|
// Seed default platform rows (idempotent)
|
|
var conn = db.Database.GetDbConnection();
|
|
if (conn.State != System.Data.ConnectionState.Open) await conn.OpenAsync();
|
|
|
|
using var seedPlatform = conn.CreateCommand();
|
|
seedPlatform.CommandText = @"
|
|
INSERT IGNORE INTO `PlatformConfigs` (`Id`, `Name`, `DisplayName`, `IsActive`, `CreatedAt`, `UpdatedAt`) VALUES
|
|
(0, 'Unknown', 'Unknown Platform', 1, UTC_TIMESTAMP(), UTC_TIMESTAMP()),
|
|
(1, 'Polymarket', 'Polymarket', 1, UTC_TIMESTAMP(), UTC_TIMESTAMP()),
|
|
(2, 'Azuro', 'Azuro', 1, UTC_TIMESTAMP(), UTC_TIMESTAMP()),
|
|
(3, 'Limitless', 'Limitless', 1, UTC_TIMESTAMP(), UTC_TIMESTAMP());";
|
|
await seedPlatform.ExecuteNonQueryAsync();
|
|
|
|
await conn.CloseAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Warning("⚠️ Could not connect to database or apply migrations: {Message}. Background workers will retry connection automatically.", ex.Message);
|
|
}
|
|
}
|
|
}
|