diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index a052f6f..680ce8b 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -70,7 +70,9 @@ func deviceToResponse(d *store.Device, password string) iotDeviceResponse { resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339) } if !d.CreatedAt.IsZero() { - resp.CreatedAt = d.CreatedAt.UTC().Format("2006-01-02 15:04:05 UTC") + // RFC3339 — как last_connected: единый формат для Terraform (drift между + // POST/GET из-за формата «2006-01-02 15:04:05 UTC» устранён). + resp.CreatedAt = d.CreatedAt.UTC().Format(time.RFC3339) } return resp } @@ -149,13 +151,24 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) { return } + // Перечитываем устройство из БД: после INSERT нужны БД-поля created_at + // (и phase из БД), которых нет в in-memory структуре. Иначе POST отдал бы + // пустой created_at → drift между POST и GET (ревью 2026-10-10, «Что + // стабилизировать в REST для Terraform»). + created, err := h.Devices.GetByName(r.Context(), ns, req.Name) + if err != nil { + h.Log.Error("reload device after create", "namespace", ns, "name", req.Name, "err", err) + writeJSON(w, http.StatusInternalServerError, errResp("failed to reload device")) + return + } + // Пароль отдаётся ТОЛЬКО здесь, один раз в ответе 201. Дальше (GET/PATCH/ // LIST) он не возвращается — иначе пароль утекал бы через чтение и в // историю/логи. Это блокер для Terraform: без пароля устройство нельзя // подключить к MQTT, значит ресурс не создать. Именно поэтому пароль - // пробрасывается в deviceToResponse только в этой точке, а не хранится - // в response-структуре навсегда. - writeJSON(w, http.StatusCreated, deviceToResponse(device, password)) + // пробрасывается в deviceToResponse только в этой точке (GetByName его + // не отдаёт — см. P0-3 ч.2). + writeJSON(w, http.StatusCreated, deviceToResponse(created, password)) } // ListIoTDevices — GET /v1/namespaces/{ns}/iot/devices (без паролей).