85bc99cac5
Three fixes surfaced when launching the WPF host for the first time: 1. App.xaml.cs — call MarathonDbContextInitializer.InitializeAsync() between Host.Build() and Host.Start() so EF migrations + WAL pragma are applied BEFORE BackgroundServices race to query the DB. Without this, all pollers crashed on 'no such table: Events'. 2. wwwroot/index.html — added <script src='https://cdn.plot.ly/plotly-2.35.2.min.js'> before blazor.webview.js. Phase 6 reviewer flagged this for Phase 9, but charts are unrenderable without it; better to ship now. 3. Migrations/20260505000000_InitialCreate.cs — added [DbContext] and [Migration('20260505000000_InitialCreate')] attributes. Phase 2's hand-written migration was missing both, so EF saw 'no migrations to apply' even on a fresh DB. With the attributes, the migration runs on first launch and creates all tables (Events, Snapshots, Bets, EventResults, Anomalies, Sports, Leagues). Verified: clean DB → migration applied → all 7 tables created → pollers run with empty results (no data yet — UpcomingEventsPoller fires every 6h by default; first scrape will populate the DB).
138 lines
5.2 KiB
C#
138 lines
5.2 KiB
C#
using System.Globalization;
|
|
using System.IO;
|
|
using System.Windows;
|
|
using Marathon.Application;
|
|
using Marathon.Infrastructure;
|
|
using Marathon.UI.Services;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Options;
|
|
using Serilog;
|
|
|
|
namespace Marathon.Hosts.WpfBlazor;
|
|
|
|
/// <summary>
|
|
/// WPF application entry-point. Builds an <see cref="IHost"/> with Serilog,
|
|
/// configuration (appsettings.json + Local + env vars), and the Marathon UI
|
|
/// service collection. Composes Application + Infrastructure modules
|
|
/// optionally — those module entry points may not yet exist while parallel
|
|
/// Phase 2/3/4 work merges.
|
|
/// </summary>
|
|
public partial class App : System.Windows.Application
|
|
{
|
|
public IHost? Host { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Absolute path to the Local override settings file. Resolved from the
|
|
/// host's content root (the directory containing <c>appsettings.json</c>).
|
|
/// </summary>
|
|
public static string SettingsLocalFileName => "appsettings.Local.json";
|
|
|
|
protected override void OnStartup(StartupEventArgs e)
|
|
{
|
|
base.OnStartup(e);
|
|
|
|
var contentRoot = AppContext.BaseDirectory;
|
|
var localSettingsPath = Path.Combine(contentRoot, SettingsLocalFileName);
|
|
|
|
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();
|
|
builder.Environment.ContentRootPath = contentRoot;
|
|
|
|
builder.Configuration
|
|
.SetBasePath(contentRoot)
|
|
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
|
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true)
|
|
.AddJsonFile(SettingsLocalFileName, optional: true, reloadOnChange: true)
|
|
.AddEnvironmentVariables(prefix: "MARATHON_");
|
|
|
|
// Serilog — structured rolling-file + console.
|
|
// Minimum level honours the "Serilog:MinimumLevel:Default" key when
|
|
// present in configuration; otherwise defaults to Information.
|
|
var logsDir = Path.Combine(contentRoot, "logs");
|
|
Directory.CreateDirectory(logsDir);
|
|
|
|
var minimumLevel = ParseMinimumLevel(builder.Configuration["Serilog:MinimumLevel:Default"]);
|
|
Log.Logger = new LoggerConfiguration()
|
|
.MinimumLevel.Is(minimumLevel)
|
|
.Enrich.FromLogContext()
|
|
.WriteTo.Console()
|
|
.WriteTo.File(
|
|
path: Path.Combine(logsDir, "marathon-.log"),
|
|
rollingInterval: RollingInterval.Day,
|
|
retainedFileCountLimit: 14,
|
|
shared: true)
|
|
.CreateLogger();
|
|
|
|
builder.Services.AddSerilog();
|
|
|
|
// Marathon.UI services (Mud, localization, options, theme/locale state, settings writer).
|
|
builder.Services.AddMarathonUi(builder.Configuration, localSettingsPath);
|
|
|
|
// Blazor WebView root services.
|
|
builder.Services.AddWpfBlazorWebView();
|
|
#if DEBUG
|
|
builder.Services.AddBlazorWebViewDeveloperTools();
|
|
#endif
|
|
|
|
// Application use cases + Infrastructure (persistence, scraping, workers).
|
|
builder.Services.AddMarathonApplication();
|
|
builder.Services.AddMarathonInfrastructure(builder.Configuration);
|
|
|
|
// MainWindow needs the IServiceProvider for BlazorWebView.Services binding.
|
|
builder.Services.AddSingleton<MainWindow>();
|
|
|
|
Host = builder.Build();
|
|
|
|
// Apply EF migrations + WAL pragma BEFORE Host.Start() so the BackgroundServices
|
|
// (LiveOddsPoller, AnomalyDetectionPoller, etc.) don't race the DB schema creation.
|
|
// Resolved in a scope because MarathonDbContextInitializer is Scoped (DbContext lifetime).
|
|
using (var initScope = Host.Services.CreateScope())
|
|
{
|
|
var initializer = initScope.ServiceProvider
|
|
.GetRequiredService<Marathon.Infrastructure.Persistence.MarathonDbContextInitializer>();
|
|
initializer.InitializeAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
Host.Start();
|
|
|
|
// Apply default culture from configuration before any UI renders.
|
|
var localeOptions = Host.Services.GetRequiredService<IOptions<LocalizationOptions>>().Value;
|
|
var locale = Host.Services.GetRequiredService<LocaleState>();
|
|
try
|
|
{
|
|
locale.Set(localeOptions.DefaultCulture);
|
|
}
|
|
catch (CultureNotFoundException)
|
|
{
|
|
locale.Set(LocaleState.Russian);
|
|
}
|
|
|
|
var window = Host.Services.GetRequiredService<MainWindow>();
|
|
window.Show();
|
|
}
|
|
|
|
private static Serilog.Events.LogEventLevel ParseMinimumLevel(string? raw) =>
|
|
Enum.TryParse<Serilog.Events.LogEventLevel>(raw, ignoreCase: true, out var level)
|
|
? level
|
|
: Serilog.Events.LogEventLevel.Information;
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
Host?.StopAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Host shutdown failed");
|
|
}
|
|
finally
|
|
{
|
|
Host?.Dispose();
|
|
Log.CloseAndFlush();
|
|
base.OnExit(e);
|
|
}
|
|
}
|
|
}
|