d972f67bad
Infrastructure-free normalization engine in Core dispatching on MeterMode: - Cumulative/Generation counters: register deltas, first-reading baseline (Haus 411, Auto 3755), meter swaps (water …861→2 reconciles to 12 via boundary registers OR an explicit amount override), counter resets, anomaly-guarded decreases. - RuntimeCounter: Δhours × rate (fixed/empirical). - ConsumableBalance: tank level-Δ + deliveries → consumption, cm→litre calibration; delivery-only rows before the first dipstick emit nothing. - DirectDelta, and Virtual meters via a small safe arithmetic evaluator (Netz Einsparung = Haus − Netz, Eigenverbrauch = Erzeugung − Einsparung) — data-driven, not hardcoded. - DatabaseSeeder: default energy types, cost categories, base settings (idempotent). 24 Core unit tests + 4 integration tests green. Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
87 lines
2.8 KiB
C#
87 lines
2.8 KiB
C#
using MeterVault.App.Components;
|
|
using MeterVault.Infrastructure;
|
|
using MeterVault.Infrastructure.Options;
|
|
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
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<MeterVaultOptions>(
|
|
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);
|
|
|
|
builder.Services.AddRazorComponents()
|
|
.AddInteractiveServerComponents();
|
|
|
|
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();
|
|
// No HTTPS redirection: the app serves plain HTTP (port 8080) behind a reverse proxy
|
|
// that terminates TLS (SDD §10). HTTPS redirection here would break the container and proxy.
|
|
app.UseAntiforgery();
|
|
|
|
app.MapStaticAssets();
|
|
app.MapRazorComponents<App>()
|
|
.AddInteractiveServerRenderMode();
|
|
|
|
// 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<MeterVaultOptions>() ?? new MeterVaultOptions();
|
|
|
|
if (!options.RunMigrationsAtStartup)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await using var scope = app.Services.CreateAsyncScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<MeterVaultDbContext>();
|
|
await db.Database.MigrateAsync().ConfigureAwait(false);
|
|
await DatabaseSeeder.SeedAsync(db).ConfigureAwait(false);
|
|
Log.Information("Database migrations applied and defaults seeded");
|
|
}
|
|
|
|
/// <summary>Exposed for WebApplicationFactory-based integration tests.</summary>
|
|
public partial class Program;
|