From 2aef884d0281baf5c03108084325e09f676e227e Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 10 Oct 2026 18:49:40 +0300 Subject: [PATCH] =?UTF-8?q?fix(iot):=20telemetry=5Ftopic=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B2=D0=B5=D1=82=D0=B5=20+=20=D0=BD=D0=BE=D1=80=D0=BC?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20metadata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iotDeviceResponse: добавлено telemetry_topic = «ns/telemetry/device» (явный топик для Terraform; topic_prefix = «ns/» был неполным). - metadata теперь без omitempty и всегда «{}» при пустом — убран drift «{}» против null (ревью 2026-10-10, «Что стабилизировать для Terraform»). --- internal/service/api/handler/devices.go | 39 +++++++++++++++++-------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index 680ce8b..1936689 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -46,25 +46,40 @@ type iotDeviceResponse struct { MQTTUsername string `json:"mqtt_username,omitempty"` MQTTPassword string `json:"mqtt_password,omitempty"` TopicPrefix string `json:"topic_prefix,omitempty"` - LastConnected string `json:"last_connected,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - CreatedAt string `json:"created_at,omitempty"` + // TelemetryTopic — полный MQTT-топик телеметрии устройства «ns/telemetry/device». + // Явное поле (в отличие от topic_prefix = «ns/»): Terraform должен знать + // точный топик для публикации/подписки (ревью 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. // password отдаётся только если непустая — только на создании, при чтении // передаётся пустая строка (пароль повторно не показывается). 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{ - Name: d.Name, - Namespace: d.Namespace, - DeviceID: d.DeviceID, - Enabled: d.Enabled, - Phase: d.Phase, - MQTTUsername: d.Namespace + "_" + d.DeviceID, - MQTTPassword: password, - TopicPrefix: d.Namespace + "/", - Metadata: d.Metadata, + Name: d.Name, + Namespace: d.Namespace, + DeviceID: d.DeviceID, + Enabled: d.Enabled, + Phase: d.Phase, + MQTTUsername: d.Namespace + "_" + d.DeviceID, + MQTTPassword: password, + TopicPrefix: d.Namespace + "/", + TelemetryTopic: d.Namespace + "/telemetry/" + d.DeviceID, + Metadata: metadata, } if d.LastConnected != nil && !d.LastConnected.IsZero() { resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339)