The /cameras/{id}/start and /stop handlers only wrote a recording_events
row — they never published the command, so the camera never recorded.
Add Subscriber.PublishCommand (publishes {"command":...} to
remoterig/cameras/<id>/command, which the XIAO forwards to the ESP-01S),
thread a CommandPublisher into the recording handlers, and wire mqttSub in
via apiRouter. Tests pass nil (publish skipped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
modernc/sqlite returns a COALESCE() expression as a raw string (no column
type affinity), which can't scan into *time.Time. Drop the COALESCE on the
timestamp and scan the plain DATETIME column (which modernc returns as
time.Time) through sql.NullTime, so a camera with no status row yet lists
with a zero time instead of erroring out the whole list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three bugs surfaced once the camera reported in:
- ListCameras LEFT JOIN returns NULL status columns for a camera with no
status rows yet, which failed scanning into non-nullable int/time fields
(recording_state, online, recorded_at) and emptied the whole list.
COALESCE them (recorded_at falls back to the camera's created_at).
- handleHeartbeat rejected every heartbeat ("cannot unmarshal number into
string") because the node sends a numeric millis() timestamp. The handler
doesn't use it, so drop the Timestamp field and let it be ignored.
- handleAnnounce kept a stale cam-NNN row registered by MAC under the old
(pre-self-id) scheme, so self-id status inserts hit a FOREIGN KEY error.
When a MAC is known under a different id than the node's self-id, migrate:
drop the old row and re-register under the self-id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add request validation: Content-Type check, body size limit (64KB)
- Add field length validation (camera_id: 64, friendly_name: 128, mode: 32, resolution: 32)
- Add FPS range validation (0-240)
- Add battery_pct range validation (0-100)
- Replace ad-hoc map[string]string errors with structured APIError {error, code, details}
- Fix isUniqueConstraintErr to catch both camera_id and mac_address constraint violations
- Fix MacAddress model field from string to *string for NULL handling
- Fix splitSQL to strip -- line comments before splitting (was causing migration failures with modernc.org/sqlite)
- Add 30 integration tests covering all endpoints
- All tests pass: ok github.com/cubecraft/remoterig/internal/api