From 59bd743c8745531e37b4be68b754823dbef0ce15 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 10 Oct 2026 19:18:01 +0300 Subject: [PATCH] =?UTF-8?q?feat(iot):=20PATCH=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20metadata=20(Terraform)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iotDeviceUpdateRequest получил поле metadata; UpdateIoTDevice обновляет переданные поля (enabled и/или metadata). Добавлен store.UpdateMetadata. Хотя бы одно поле обязательно. Раньше metadata нельзя было изменить (только enabled). --- internal/service/api/handler/devices.go | 37 +++++++++++++++++-------- internal/service/store/devices.go | 18 ++++++++++++ 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index 467e3da..5a26b97 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -29,9 +29,11 @@ type iotDeviceCreateRequest struct { Metadata map[string]string `json:"metadata,omitempty"` } -// iotDeviceUpdateRequest — тело PATCH. +// iotDeviceUpdateRequest — тело PATCH. Хотя бы одно поле обязательно: +// enabled и/или metadata (metadata раньше нельзя было обновить — ревью 2026-10-10). type iotDeviceUpdateRequest struct { - Enabled *bool `json:"enabled"` + Enabled *bool `json:"enabled,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` } // iotDeviceResponse — ответ API. MQTTPassword заполняется ТОЛЬКО при создании @@ -314,20 +316,33 @@ func (h *Handler) UpdateIoTDevice(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, errResp("invalid JSON: "+err.Error())) return } - if req.Enabled == nil { - writeJSON(w, http.StatusBadRequest, errResp("enabled field is required")) + if req.Enabled == nil && req.Metadata == nil { + writeJSON(w, http.StatusBadRequest, errResp("enabled или metadata обязательны")) return } - err := h.Devices.UpdateEnabled(r.Context(), ns, name, *req.Enabled) - if err != nil { - if errors.Is(err, store.ErrNotFound) { - writeJSON(w, http.StatusNotFound, errResp("iot device not found")) + // Обновляем только переданные поля (enabled и/или metadata). + if req.Enabled != nil { + if err := h.Devices.UpdateEnabled(r.Context(), ns, name, *req.Enabled); err != nil { + if errors.Is(err, store.ErrNotFound) { + writeJSON(w, http.StatusNotFound, errResp("iot device not found")) + return + } + h.Log.Error("update device enabled", "namespace", ns, "name", name, "err", err) + writeJSON(w, http.StatusInternalServerError, errResp("failed to update device")) + return + } + } + if req.Metadata != nil { + if err := h.Devices.UpdateMetadata(r.Context(), ns, name, req.Metadata); err != nil { + if errors.Is(err, store.ErrNotFound) { + writeJSON(w, http.StatusNotFound, errResp("iot device not found")) + return + } + h.Log.Error("update device metadata", "namespace", ns, "name", name, "err", err) + writeJSON(w, http.StatusInternalServerError, errResp("failed to update device")) return } - h.Log.Error("update device", "namespace", ns, "name", name, "err", err) - writeJSON(w, http.StatusInternalServerError, errResp("failed to update device")) - return } device, err := h.Devices.GetByName(r.Context(), ns, name) diff --git a/internal/service/store/devices.go b/internal/service/store/devices.go index da8bb16..73cd358 100644 --- a/internal/service/store/devices.go +++ b/internal/service/store/devices.go @@ -97,6 +97,24 @@ func (s *DeviceStore) UpdateEnabled(ctx context.Context, namespace, name string, return nil } +// UpdateMetadata обновляет metadata устройства. +func (s *DeviceStore) UpdateMetadata(ctx context.Context, namespace, name string, metadata map[string]string) error { + b, err := json.Marshal(metadata) + if err != nil { + return fmt.Errorf("store: marshal metadata: %w", err) + } + res, err := s.db.ExecContext(ctx, + `UPDATE iot_devices SET metadata = $3 WHERE namespace = $1 AND name = $2`, + namespace, name, b) + if err != nil { + return fmt.Errorf("store: update metadata: %w", err) + } + if n, _ := res.RowsAffected(); n == 0 { + return ErrNotFound + } + return nil +} + // Delete удаляет устройство. func (s *DeviceStore) Delete(ctx context.Context, namespace, name string) error { res, err := s.db.ExecContext(ctx,