diff --git a/internal/service/api/handler/devices.go b/internal/service/api/handler/devices.go index 1e88604..389b343 100644 --- a/internal/service/api/handler/devices.go +++ b/internal/service/api/handler/devices.go @@ -215,6 +215,10 @@ func (h *Handler) CreateIoTDevice(w http.ResponseWriter, r *http.Request) { } if err := h.Devices.Create(r.Context(), device); err != nil { + if errors.Is(err, store.ErrDeviceIDExists) { + writeJSON(w, http.StatusConflict, errResp("device_id already exists in this namespace")) + return + } if errors.Is(err, store.ErrAlreadyExists) { writeJSON(w, http.StatusConflict, errResp("iot device already exists")) return diff --git a/internal/service/store/device.go b/internal/service/store/device.go index bfd4b34..c0c049e 100644 --- a/internal/service/store/device.go +++ b/internal/service/store/device.go @@ -15,6 +15,10 @@ var ErrNotFound = errors.New("device not found") // ErrAlreadyExists — конфликт уникальности (namespace, name) или (namespace, device_id). var ErrAlreadyExists = errors.New("device already exists") +// ErrDeviceIDExists — конфликт именно по (namespace, device_id) (отличается от +// конфликта по name для понятной ошибки API/Terraform, ревью 2026-10-10). +var ErrDeviceIDExists = errors.New("device_id already exists") + // Device — устройство IoT из таблицы iot_devices. type Device struct { Namespace string diff --git a/internal/service/store/devices.go b/internal/service/store/devices.go index 73cd358..1a1ba87 100644 --- a/internal/service/store/devices.go +++ b/internal/service/store/devices.go @@ -6,6 +6,9 @@ import ( "encoding/json" "errors" "fmt" + "strings" + + "github.com/lib/pq" ) // Create вставляет новое устройство. Конфликт (namespace, name) или @@ -26,6 +29,13 @@ VALUES ($1, $2, $3, $4, $5, $6, $7)`, ) if err != nil { if isUniqueViolation(err) { + // Различаем конфликт name и device_id по имени constraint (ревью + // 2026-10-10): PK — iot_devices_pkey (namespace,name), уникальный + // индекс — idx_iot_devices_ns_device (namespace,device_id). + var pqErr *pq.Error + if errors.As(err, &pqErr) && strings.Contains(pqErr.Constraint, "ns_device") { + return ErrDeviceIDExists + } return ErrAlreadyExists } return fmt.Errorf("store: insert device: %w", err)