using MeterVault.App.Api; using MeterVault.App.Components; using MeterVault.Infrastructure; using MeterVault.Infrastructure.Options; using MeterVault.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using Serilog; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateBootstrapLogger(); try { var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((context, services, configuration) => configuration .ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext() .WriteTo.Console()); builder.Services.Configure( builder.Configuration.GetSection(MeterVaultOptions.SectionName)); var connectionString = builder.Configuration.GetConnectionString("Default") ?? "Host=localhost;Port=5432;Database=metervault;Username=metervault;Password=metervault"; builder.Services.AddMeterVaultInfrastructure(connectionString); var options = builder.Configuration.GetSection(MeterVaultOptions.SectionName).Get() ?? new MeterVaultOptions(); if (options.EnableLiveIngestion) { builder.Services.AddMeterVaultIngestion(); } builder.Services.AddMudServices(); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // Serialize/accept enums as strings on the REST API (e.g. event Type "Delivery"). builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter())); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => c.SwaggerDoc("v1", new() { Title = "MeterVault API", Version = "v1" })); var app = builder.Build(); await MigrateDatabaseAsync(app).ConfigureAwait(false); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseSerilogRequestLogging(); app.UseReverseProxyTrust(); // No HTTPS redirection: the app serves plain HTTP (port 8760) behind a reverse proxy // that terminates TLS (SDD §10). HTTPS redirection here would break the container and proxy. app.UseAntiforgery(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "MeterVault API v1")); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); app.MapMeterVaultApi(); // Liveness/readiness probe for Gatus/Compose healthchecks (SDD §9). app.MapGet("/healthz", () => Results.Ok(new { status = "ok" })); await app.RunAsync().ConfigureAwait(false); } catch (Exception ex) { Log.Fatal(ex, "MeterVault terminated unexpectedly"); throw; } finally { await Log.CloseAndFlushAsync().ConfigureAwait(false); } static async Task MigrateDatabaseAsync(WebApplication app) { var options = app.Configuration .GetSection(MeterVaultOptions.SectionName) .Get() ?? new MeterVaultOptions(); if (!options.RunMigrationsAtStartup) { return; } await using var scope = app.Services.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService(); await db.Database.MigrateAsync().ConfigureAwait(false); await DatabaseSeeder.SeedAsync(db).ConfigureAwait(false); Log.Information("Database migrations applied and defaults seeded"); if (options.SeedReferenceData) { var importer = scope.ServiceProvider.GetRequiredService(); var dir = Path.Combine(AppContext.BaseDirectory, "sampledata"); await importer.LoadAsync(dir).ConfigureAwait(false); Log.Information("Reference dataset ensured (SeedReferenceData=true)"); } } /// Exposed for WebApplicationFactory-based integration tests. public partial class Program;