09cd435c2b
ci / build-test (push) Successful in 1m19s
Three requested phases.
1) Admin section (SDD §8.7) — MudBlazor inline-dialog CRUD, consistent pattern,
delete guards, snackbar feedback, shared Confirm helper:
- Energy types: create/edit/delete (blocks delete when meters reference it).
- Meters: create/edit/delete; recomputes consumption when mode/baseline
changes (NormalizationService over a fresh factory context, in a tx);
delete cascades data (consumption+readings are Restrict → removed first).
- A meter's ingest sources: manage on the meter-detail Sources tab
(add/edit/delete MQTT/Tasmota/HA sources with typed config).
- Tariffs: full CRUD (scope/component/value/validity).
- Cost categories: CRUD + member management (meter or energy-type members).
- Connectors: ingestion_endpoint CRUD (MQTT broker + Home Assistant);
secrets referenced by env-var name only, never stored.
- Settings: read-only effective-config view (settings are env-driven and
reproducible, so an editable form would change nothing — kept honest).
PV role is now editable on meters (MeterMeta.SetRole can clear a role).
2) Read Home Assistant — extracted a shared public HaEndpointConfig (was a
private record in the worker), added HaConnectionTester (powers the connector
"Test connection": checks base URL + env-resolved token, optionally reads one
entity). Configuring an HA connector + an HA source on a meter drives the
existing REST-poll worker end to end. (WebSocket push stays a future
optimization; REST poll already reads HA.)
3) Wiring/placeholder audit — swept every OnClick/Href: all handlers are real,
all internal links resolve to real routes, no TODO/stub/placeholder code.
Fixed one genuine gap: MainLayout had no drawer toggle, so the nav was
unreachable on narrow screens — added a hamburger button.
Tests: +6 (MeterMeta.SetRole role-removal; HaConnectionTester fail-closed
guard branches with a throwing HttpClientFactory proving no network on bad
config); render test now covers all admin routes. 69 Core + 45 Integration =
114 green. Live-verified in Docker: all admin pages 200, drawer toggle present,
Settings shows real effective config.
Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
48 lines
1.6 KiB
C#
48 lines
1.6 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace MeterVault.Infrastructure.Ingestion;
|
|
|
|
/// <summary>
|
|
/// The parsed <see cref="Core.Domain.IngestionEndpoint.Config"/> JSON for a Home Assistant
|
|
/// connection (SDD §6.2). The long-lived token is stored by reference only: <see cref="TokenEnv"/>
|
|
/// names an environment variable resolved at runtime — never the token itself (SDD §6.4).
|
|
/// </summary>
|
|
public sealed record HaEndpointConfig
|
|
{
|
|
private static readonly JsonSerializerOptions Options = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
};
|
|
|
|
/// <summary>Base URL of the Home Assistant instance, e.g. <c>http://homeassistant.local:8123</c>.</summary>
|
|
public string? BaseUrl { get; init; }
|
|
|
|
/// <summary>Name of the environment variable holding the long-lived access token.</summary>
|
|
public string? TokenEnv { get; init; }
|
|
|
|
public static HaEndpointConfig Parse(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
{
|
|
return new HaEndpointConfig();
|
|
}
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<HaEndpointConfig>(json, Options) ?? new HaEndpointConfig();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return new HaEndpointConfig();
|
|
}
|
|
}
|
|
|
|
public string ToJson() => JsonSerializer.Serialize(this, Options);
|
|
|
|
/// <summary>Resolves the token from the referenced environment variable (null if unset).</summary>
|
|
public string? ResolveToken() =>
|
|
string.IsNullOrWhiteSpace(TokenEnv) ? null : Environment.GetEnvironmentVariable(TokenEnv);
|
|
}
|