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)
|
||||
}
|
||||
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 (без паролей).
|
||||
|
||||
Reference in New Issue
Block a user