diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index be3094f..a052f6f 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -2,9 +2,9 @@ // // Endpoints: // -// POST /v1/namespaces/{ns}/iot/devices — создать +// POST /v1/namespaces/{ns}/iot/devices — создать (отдаёт mqtt_password ОДИН раз) // GET /v1/namespaces/{ns}/iot/devices — список (без паролей) -// GET /v1/namespaces/{ns}/iot/devices/{name} — одно (с паролем) +// GET /v1/namespaces/{ns}/iot/devices/{name} — одно (без пароля) // DELETE /v1/namespaces/{ns}/iot/devices/{name} — удалить // PATCH /v1/namespaces/{ns}/iot/devices/{name} — enabled package handler @@ -149,7 +149,13 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) { return } - writeJSON(w, http.StatusCreated, deviceToResponse(device, "")) + // Пароль отдаётся ТОЛЬКО здесь, один раз в ответе 201. Дальше (GET/PATCH/ + // LIST) он не возвращается — иначе пароль утекал бы через чтение и в + // историю/логи. Это блокер для Terraform: без пароля устройство нельзя + // подключить к MQTT, значит ресурс не создать. Именно поэтому пароль + // пробрасывается в deviceToResponse только в этой точке, а не хранится + // в response-структуре навсегда. + writeJSON(w, http.StatusCreated, deviceToResponse(device, password)) } // ListIoTDevices — GET /v1/namespaces/{ns}/iot/devices (без паролей). diff --git a/internal/service/store/devices.go b/internal/service/store/devices.go index 8736230..da8bb16 100644 --- a/internal/service/store/devices.go +++ b/internal/service/store/devices.go @@ -15,10 +15,14 @@ func (s *DeviceStore) Create(ctx context.Context, d *Device) error { if err != nil { return fmt.Errorf("store: marshal metadata: %w", err) } + // phase пишем ЯВНО через phaseOf(enabled): колонка имеет DEFAULT 'Active', + // поэтому без явной записи устройство с enabled=false лежало бы в БД как + // 'Active', а POST-ответ считал бы 'Disabled' → GET отдал бы другое значение + // (ревью 2026-10-10, 🟡 store). Согласовано с UpdateEnabled (тот же phaseOf). _, err = s.db.ExecContext(ctx, ` -INSERT INTO iot_devices (namespace, name, device_id, enabled, mqtt_password, metadata) -VALUES ($1, $2, $3, $4, $5, $6)`, - d.Namespace, d.Name, d.DeviceID, d.Enabled, d.MQTTPassword, metadata, +INSERT INTO iot_devices (namespace, name, device_id, enabled, mqtt_password, metadata, phase) +VALUES ($1, $2, $3, $4, $5, $6, $7)`, + d.Namespace, d.Name, d.DeviceID, d.Enabled, d.MQTTPassword, metadata, phaseOf(d.Enabled), ) if err != nil { if isUniqueViolation(err) {