fix(iot): отдавать MQTT-пароль при создании + писать phase в БД
- handler/devices.go: CreateIoTDevice отдаёт mqtt_password один раз в 201 (было deviceToResponse(device, "") — пароль терялся, устройство нельзя подключить; блокер для Terraform). Заголовок поправлен (GET — без пароля). - store/devices.go: Create пишет phase явно через phaseOf(enabled) — иначе enabled=false лежал в БД как 'Active' (DEFAULT), расходясь с POST/GET.
This commit is contained in:
@@ -2,9 +2,9 @@
|
|||||||
//
|
//
|
||||||
// Endpoints:
|
// 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 — список (без паролей)
|
||||||
// GET /v1/namespaces/{ns}/iot/devices/{name} — одно (с паролем)
|
// GET /v1/namespaces/{ns}/iot/devices/{name} — одно (без пароля)
|
||||||
// DELETE /v1/namespaces/{ns}/iot/devices/{name} — удалить
|
// DELETE /v1/namespaces/{ns}/iot/devices/{name} — удалить
|
||||||
// PATCH /v1/namespaces/{ns}/iot/devices/{name} — enabled
|
// PATCH /v1/namespaces/{ns}/iot/devices/{name} — enabled
|
||||||
package handler
|
package handler
|
||||||
@@ -149,7 +149,13 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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 (без паролей).
|
// ListIoTDevices — GET /v1/namespaces/{ns}/iot/devices (без паролей).
|
||||||
|
|||||||
@@ -15,10 +15,14 @@ func (s *DeviceStore) Create(ctx context.Context, d *Device) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("store: marshal metadata: %w", err)
|
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, `
|
_, err = s.db.ExecContext(ctx, `
|
||||||
INSERT INTO iot_devices (namespace, name, device_id, enabled, mqtt_password, metadata)
|
INSERT INTO iot_devices (namespace, name, device_id, enabled, mqtt_password, metadata, phase)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
||||||
d.Namespace, d.Name, d.DeviceID, d.Enabled, d.MQTTPassword, metadata,
|
d.Namespace, d.Name, d.DeviceID, d.Enabled, d.MQTTPassword, metadata, phaseOf(d.Enabled),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isUniqueViolation(err) {
|
if isUniqueViolation(err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user