Compose stack with frame-processor (built from Dockerfile, frames.json
bind-mounted, /data/images on a named volume) and an eclipse-mosquitto
broker configured for anonymous access on a shared bridge network.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keyed per-frame SemaphoreSlim(1,1) over a ConcurrentDictionary with a
disposable releaser, so the next increment can serialize the upload
pipeline per FrameName.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Enforce X-Api-Key on /api/* requests with constant-time comparison.
/i/{mac}.png and /health remain unauthenticated. No-op when the
configured key is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add IImageUrlFetcher with HttpClient-backed implementation that enforces
UrlFetch.TimeoutSeconds, MaxRedirects, and MaxBytes. Surfaces failures as
ImageFetchException so the URL-ingestion endpoint can map them to 502.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On publish failure, enqueue the MAC in a dedup'd in-memory set and let a
background loop retry with the configured backoff sequence. Reconnects
signal an immediate drain so a transient broker outage no longer drops
the update.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inject MqttPublisher into HealthController and surface IsConnected as
mqttConnected, replacing the hardcoded false.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persistent MQTTnet v4 IMqttClient wrapped in a BackgroundService:
attempts to connect on StartAsync (logging and continuing on failure)
and reconnects on a 5s loop while running. Exposes IsConnected so
6.4 can wire it into /health later. Honors username/password and
UseTls from MqttOptions; clean session is disabled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FramesRegistry validates strictly on construction (fail-fast at startup) and
leniently on hot-reload (skip invalid frames with a warning, keep valid ones
serving). Exposes TryGetByName/TryGetByMac over the current valid set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Permissive parser (colon, hyphen, unseparated; case-insensitive; surrounding
whitespace) with canonical lowercase no-separator ToString. Implements
IParsable/ISpanParsable for ASP.NET route binding and ships a System.Text.Json
converter that normalizes on read.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up MVC controllers and add GET /health returning
{ status: "Healthy", mqttConnected: false } (mqttConnected hardcoded
until the MQTT publisher lands). Switch launchSettings.json to port
8080 so the DoD curl works out of the box.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scaffold FrameProcessor.slnx with src/FrameProcessor (net10.0 Web SDK) and
tests/FrameProcessor.Tests (xUnit) plus .gitignore and global.json pinning
the .NET 10 SDK. dotnet build, dotnet test, and dotnet format --verify-no-changes
all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>