M7: release polish — export/import, docs, easy docker push

- Easy docker push (MQTTower ergonomics + the image push it lacks): VERSION file →
  version-tag.yml (semver-guard auto-tag) → docker-publish.yml (buildx multi-arch → GHCR,
  registry centralized for one-line retarget to git.finalfactory.de) + ci.yml + build-and-push.ps1.
- Verified end to end: deploy/Dockerfile builds; docker compose stack (app + timescaledb) comes
  up healthy; /healthz and the dashboard respond in-container.
- JSON config export/import (ExportService) with id remapping on restore + GET /export, POST
  /import endpoints; round-trip test preserves meter→type, meter-scoped tariff, category links.
- README, HA/Tasmota/MQTT wiring guide (docs/wiring.md), Unraid template.
- CLAUDE.md updated to reflect the built codebase.
- i18n: locale-aware number/currency formatting (de-DE); full de UI string localization deferred.

95 tests green (56 Core + 39 integration).

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
This commit is contained in:
2026-07-13 12:25:03 +02:00
parent 9abc2937c2
commit e223278771
14 changed files with 634 additions and 12 deletions
+11
View File
@@ -112,6 +112,17 @@ public static class ApiEndpoints
.Select(s => new { s.Id, s.MeterId, Type = s.SourceType.ToString(), s.IsEnabled, s.LastSeenAt, s.LastValue, s.LastStatus })
.ToListAsync(ct)));
api.MapGet("/export", async (MeterVault.Infrastructure.Backup.ExportService export, CancellationToken ct) =>
Results.Text(await export.ExportJsonAsync(ct), "application/json"))
.WithSummary("Export configuration + hand-entered data as portable JSON (SDD §10).");
api.MapPost("/import", async (HttpRequest request, MeterVault.Infrastructure.Backup.ExportService export, CancellationToken ct) =>
{
using var reader = new StreamReader(request.Body);
await export.ImportJsonAsync(await reader.ReadToEndAsync(ct), ct);
return Results.Ok();
}).WithSummary("Restore a JSON export into an empty instance (remaps ids).");
return app;
}
}