fix(iot): created_at в RFC3339 + перечитывание после Create
- deviceToResponse: created_at теперь RFC3339 (был «2006-01-02 15:04:05 UTC») — единый формат с last_connected, без drift для Terraform. - CreateIoTDevice: после INSERT перечитываем устройство из БД (GetByName), чтобы в 201 отдавался реальный created_at (in-memory структура его не имеет).
This commit is contained in:
@@ -70,7 +70,9 @@ func deviceToResponse(d *store.Device, password string) iotDeviceResponse {
|
|||||||
resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339)
|
resp.LastConnected = d.LastConnected.UTC().Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
if !d.CreatedAt.IsZero() {
|
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
|
return resp
|
||||||
}
|
}
|
||||||
@@ -149,13 +151,24 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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/
|
// Пароль отдаётся ТОЛЬКО здесь, один раз в ответе 201. Дальше (GET/PATCH/
|
||||||
// LIST) он не возвращается — иначе пароль утекал бы через чтение и в
|
// LIST) он не возвращается — иначе пароль утекал бы через чтение и в
|
||||||
// историю/логи. Это блокер для Terraform: без пароля устройство нельзя
|
// историю/логи. Это блокер для Terraform: без пароля устройство нельзя
|
||||||
// подключить к MQTT, значит ресурс не создать. Именно поэтому пароль
|
// подключить к MQTT, значит ресурс не создать. Именно поэтому пароль
|
||||||
// пробрасывается в deviceToResponse только в этой точке, а не хранится
|
// пробрасывается в deviceToResponse только в этой точке (GetByName его
|
||||||
// в response-структуре навсегда.
|
// не отдаёт — см. P0-3 ч.2).
|
||||||
writeJSON(w, http.StatusCreated, deviceToResponse(device, password))
|
writeJSON(w, http.StatusCreated, deviceToResponse(created, password))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListIoTDevices — GET /v1/namespaces/{ns}/iot/devices (без паролей).
|
// ListIoTDevices — GET /v1/namespaces/{ns}/iot/devices (без паролей).
|
||||||
|
|||||||
Reference in New Issue
Block a user