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:
2026-07-13 11:02:21 +02:00
commit 48a7f5a825
65 changed files with 5896 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# syntax=docker/dockerfile:1
# Build context is the repository root (see docker-compose.yml: context: ..).
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Restore first (layer-cached on lockfile-ish inputs).
COPY Directory.Build.props Directory.Packages.props global.json ./
COPY src/Core/MeterVault.Core.csproj src/Core/
COPY src/Infrastructure/MeterVault.Infrastructure.csproj src/Infrastructure/
COPY src/App/MeterVault.App.csproj src/App/
RUN dotnet restore src/App/MeterVault.App.csproj
# Build & publish.
COPY src/ ./src/
RUN dotnet publish src/App/MeterVault.App.csproj -c Release -o /app/publish /p:UseAppHost=false
# Debian-based runtime (keeps full ICU — required by the de-DE CSV importer; do not use
# an invariant-globalization or chiselled image that strips ICU).
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish ./
ENV ASPNETCORE_URLS=http://+:8080 \
ASPNETCORE_ENVIRONMENT=Production \
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
EXPOSE 8080
ENTRYPOINT ["dotnet", "MeterVault.App.dll"]
+47
View File
@@ -0,0 +1,47 @@
# MeterVault self-host stack: the Blazor app + TimescaleDB.
# docker compose -f deploy/docker-compose.yml up -d
# Secrets are supplied via environment variables (Section__Key double-underscore mapping)
# with ${VAR:-default} fallbacks — never baked into the image.
services:
db:
image: timescale/timescaledb:2.17.2-pg16
environment:
POSTGRES_DB: metervault
POSTGRES_USER: metervault
POSTGRES_PASSWORD: ${METERVAULT_DB_PASSWORD:-metervault}
volumes:
- metervault_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U metervault -d metervault"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
app:
build:
context: ..
dockerfile: deploy/Dockerfile
image: ${METERVAULT_IMAGE:-metervault/metervault:local}
depends_on:
db:
condition: service_healthy
environment:
ConnectionStrings__Default: "Host=db;Port=5432;Database=metervault;Username=metervault;Password=${METERVAULT_DB_PASSWORD:-metervault}"
ASPNETCORE_ENVIRONMENT: Production
MeterVault__TimeZone: ${METERVAULT_TIMEZONE:-Europe/Berlin}
MeterVault__Currency: ${METERVAULT_CURRENCY:-EUR}
MeterVault__Locale: ${METERVAULT_LOCALE:-en}
ports:
- "${METERVAULT_PORT:-8080}:8080"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/healthz || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
volumes:
metervault_db: