M0: scaffold solution, EF+Timescale schema, /healthz
Five-project Clean Architecture solution (Core/Infrastructure/App + Core.Tests/ Integration.Tests) on .NET 10 with central package management, snake_case EF mapping, and shared build/style config. - Full domain entity set + EF DbContext for the SDD §5.3 schema (singular table names). - InitialSchema migration (relational) + TimescaleHypertables migration (raw SQL: create_hypertable + compression on reading, hypertable on consumption). - App wiring: Serilog (actually wired, unlike MQTTower), DbContext, migrate-on-startup, /healthz. Serves plain HTTP behind a reverse proxy (no HTTPS redirect). - deploy/Dockerfile (2-stage, ICU-capable) + docker-compose (app + timescaledb). - Integration.Tests: shared TimescaleFixture (Testcontainers) — migrations, hypertables, compression policy, and /healthz all verified green (4/4). Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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);
|
||||
Log.Information("Database migrations applied");
|
||||
}
|
||||
|
||||
/// <summary>Exposed for WebApplicationFactory-based integration tests.</summary>
|
||||
public partial class Program;
|
||||
Reference in New Issue
Block a user