fix(iot): telemetry_topic в ответе + нормализация metadata

- iotDeviceResponse: добавлено telemetry_topic = «ns/telemetry/device» (явный
  топик для Terraform; topic_prefix = «ns/» был неполным).
- metadata теперь без omitempty и всегда «{}» при пустом — убран drift
  «{}» против null (ревью 2026-10-10, «Что стабилизировать для Terraform»).
This commit is contained in:
Repinoid
2026-10-10 18:49:40 +03:00
parent e2bb642df2
commit 2aef884d02
+27 -12
View File
@@ -46,25 +46,40 @@ type iotDeviceResponse struct {
MQTTUsername string `json:"mqtt_username,omitempty"` MQTTUsername string `json:"mqtt_username,omitempty"`
MQTTPassword string `json:"mqtt_password,omitempty"` MQTTPassword string `json:"mqtt_password,omitempty"`
TopicPrefix string `json:"topic_prefix,omitempty"` TopicPrefix string `json:"topic_prefix,omitempty"`
LastConnected string `json:"last_connected,omitempty"` // TelemetryTopic — полный MQTT-топик телеметрии устройства «ns/telemetry/device».
Metadata map[string]string `json:"metadata,omitempty"` // Явное поле (в отличие от topic_prefix = «ns/»): Terraform должен знать
CreatedAt string `json:"created_at,omitempty"` // точный топик для публикации/подписки (ревью 2026-10-10).
TelemetryTopic string `json:"telemetry_topic"`
LastConnected string `json:"last_connected,omitempty"`
// Metadata — без omitempty: всегда присутствует в ответе. Пустой → «{}»,
// иначе Terraform видел бы drift «{}» против null (ревью 2026-10-10).
Metadata map[string]string `json:"metadata"`
CreatedAt string `json:"created_at,omitempty"`
} }
// deviceToResponse конвертирует устройство из БД в ответ API. // deviceToResponse конвертирует устройство из БД в ответ API.
// password отдаётся только если непустая — только на создании, при чтении // password отдаётся только если непустая — только на создании, при чтении
// передаётся пустая строка (пароль повторно не показывается). // передаётся пустая строка (пароль повторно не показывается).
func deviceToResponse(d *store.Device, password string) iotDeviceResponse { func deviceToResponse(d *store.Device, password string) iotDeviceResponse {
// Пустой metadata нормализуем в «{}» (а не null/omit): иначе Terraform видел
// бы drift между «{}» и отсутствующим полем. nil-карту не мутируем — подменяем
// только для сериализации ответа.
metadata := d.Metadata
if metadata == nil {
metadata = map[string]string{}
}
resp := iotDeviceResponse{ resp := iotDeviceResponse{
Name: d.Name, Name: d.Name,
Namespace: d.Namespace, Namespace: d.Namespace,
DeviceID: d.DeviceID, DeviceID: d.DeviceID,
Enabled: d.Enabled, Enabled: d.Enabled,
Phase: d.Phase, Phase: d.Phase,
MQTTUsername: d.Namespace + "_" + d.DeviceID, MQTTUsername: d.Namespace + "_" + d.DeviceID,
MQTTPassword: password, MQTTPassword: password,
TopicPrefix: d.Namespace + "/", TopicPrefix: d.Namespace + "/",
Metadata: d.Metadata, TelemetryTopic: d.Namespace + "/telemetry/" + d.DeviceID,
Metadata: metadata,
} }
if d.LastConnected != nil && !d.LastConnected.IsZero() { if d.LastConnected != nil && !d.LastConnected.IsZero() {
resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339) resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339)