Commit Graph
23 Commits
Author SHA1 Message Date
Repinoid 8335afb617 fix(iot): валидация namespace/name/device_id (запрет _, +, #, /)
- Добавлен identRe ^[a-zA-Z0-9][a-zA-Z0-9-]*$ и validateIdentifier.
- CreateIoTDevice теперь проверяет namespace (≤56), name (≤64), device_id (≤64).
- Причины: _ ломает username «namespace_device_id» (auth EMQX LIMIT 1 без
  ORDER BY), +/# — wildcard в ACL-топике, / — лишний уровень топика.
2026-10-10 18:50:56 +03:00
Repinoid 2aef884d02 fix(iot): telemetry_topic в ответе + нормализация metadata
- iotDeviceResponse: добавлено telemetry_topic = «ns/telemetry/device» (явный
  топик для Terraform; topic_prefix = «ns/» был неполным).
- metadata теперь без omitempty и всегда «{}» при пустом — убран drift
  «{}» против null (ревью 2026-10-10, «Что стабилизировать для Terraform»).
2026-10-10 18:49:40 +03:00
Repinoid e2bb642df2 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 структура его не имеет).
2026-10-10 18:48:52 +03:00
Repinoid 76f0230f5d 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.
2026-10-10 18:48:07 +03:00
Repinoid 56fadf6ca3 style(iot-admin): переформатирование HTML (форматтер) 2026-10-10 18:38:12 +03:00
Repinoid a510522660 chore(iot): убрать старое (Kafka/K8s) из живого админ-UI и .gitignore
- iot-admin.html: секции Kafka/Pods заменены на Очередь shared-SQS и Рантайм
  (бэкенд admin.go давно отдаёт sqs/runtime, а фронт рисовал мёртвые секции);
  удалены CSS .pod-*/.badge-*/.dot-*/.lag-bar*, исправлен заголовок.
- .gitignore: удалены сталые пути бинарников старого IoT (/iot-operator,
  /mqtt-bridge, /kafka-consumer, /sqs-consumer).
2026-10-10 18:15:13 +03:00
Repinoid e8873fa24d fix(telemetry): ts = время приёма (ReceivedAt), а не момент INSERT
Раньше ts проставлялся now() в БД при INSERT → при батчинге и параллельных
записях порядок телеметрии (ORDER BY ts DESC) ломался. Теперь в ts идёт
ReceivedAt из envelope (серверное время приёма, ставит bridge); битый/пустой
received_at → zero → стор подставляет now(). Схема не меняется (используется
существующая колонка ts). Обновлены callers: consumer/processor.go и legacy
cmd/sqs-consumer.
2026-10-07 07:47:10 +03:00
Repinoid 4031882cae feat(consumer): битые envelope → DLQ вместо потери
processTelemetry теперь возвращает sentinel errInvalidEnvelope при битом JSON
(раньше — молчаливый nil → mусор подтверждался и терялся). Consumer при этой
ошибке кладёт тело в DLQ (SQS_DLQ_NAME, дефолт iot-telemetry-dlq) и подтверждает
основное сообщение; без DLQ — только логирует, но НЕ подтверждает (оседает).
Добавлены SQSDLQName в config + DefaultSQSDLQName. Реальные ошибки PG (не invalid)
по-прежнему не подтверждаются и уходят в ретрай после visibility timeout.
2026-10-07 07:40:09 +03:00
Repinoid b6c083e80f fix(telemetry): не терять строки при ошибке flush (flushAll + InsertTelemetry)
flushAll и InsertTelemetry (путь заполнения батча) вынимали строки из буфера и
подменяли буфер ДО записи: при ошибке flushTenant строки исчезали. Хуже —
фоновый flusher мог забрать строки, которые consumer ждал в Flush, тогда Flush
видел пустой буфер, возвращал nil, и consumer подтверждал SQS-сообщение с
незаписанными данными (потеря). Теперь при ошибке строки возвращаются в буфер
в обоих путях. ctx проброшен в flushAll/flushTenant. Consumer хранит receipt
handle значением (string), не указателем SDK.
2026-10-07 07:32:00 +03:00
Repinoid 38602b7560 P0-8: идемпотентность телеметрии (dedup по message_id)
Envelope получил MessageID (UUID); таблица iot_telemetry — столбец message_id +
UNIQUE(device_id,message_id); батч-INSERT ON CONFLICT DO NOTHING. Дубли при
at-least-once повторе из SQS больше не вставляются. Пустой message_id → NULL
(без ложного дедупа).
2026-10-07 07:26:50 +03:00
Repinoid 3036572ccc P1-9: readiness-проба /ready отражает состояние bridge/consumer
/health остаётся liveness (процесс жив); /ready возвращает 503, если bridge или
consumer не в строю (горутина упала/не поднялась). Добавлен api.Readiness с
atomic-флагами, NewRouter принимает *Readiness, main поднимает/опускает флаги
вокруг фоновых горутин.
2026-10-07 07:25:09 +03:00
Repinoid 3175605931 P0-7: подтверждение из SQS только после успешного flush телеметрии в БД
InsertTelemetry лишь буферизует и возвращает nil до фактической записи. Consumer
удалял сообщение сразу после processTelemetry == nil — краш/flush-ошибка в этом
окне = безвозвратная потеря. Теперь: копим receipt handles, вызываем store.Flush,
и только при успехе DeleteMessage; при ошибке flush — не удаляем (сообщения
вернутся после visibility timeout). Добавлен iotpg.Flush().
2026-10-07 07:23:25 +03:00
Repinoid a4e786d03b P0-3 (часть 2): не отдавать пароль устройства повторно через GET
GetIoTDevice больше не возвращает mqtt_password (deviceToResponse получает ).
Пароль остаётся доступен только один раз — при создании устройства.
Хэширование паролей в БД + перевод EMQX на алгоритм хэша (часть 1) требует
изменения emqx.conf и миграции — отдельным шагом.
2026-10-06 19:06:02 +03:00
Repinoid c97b63b190 P1-4: убрать неаутентифицированные /internal/mqtt/auth|acl из роутера
EMQX ходит в PostgreSQL напрямую (backend=postgresql), эти HTTP-эндпоинты были
неиспользуемым fallback и висели на публичном порту без аутентификации (перебор
паролей устройств / выдача ACL). Удалены из роутера.
2026-10-06 19:05:12 +03:00
Repinoid 4afee5cdf9 P0-1/P0-2: tenant-граница по sub + обязательная подпись JWT
validateJWT теперь возвращает sub; middleware сверяет sub с {namespace} из URL
(несовпадение → 403) — закрывает горизонтальный доступ к чужим namespace.
Вне AUTH_TEST_MODE подпись HS256 обязательна: без JWT_HMAC_SECRET middleware
отказывает (fail-closed, 500), а config.Load требует секрет на старте.
2026-10-06 19:04:54 +03:00
“Naeel” ce9e20266c feat(landing): vertical large pipeline diagram; v0.1.13 2026-08-16 19:16:58 +04:00
“Naeel” 30e7da8cf8 feat(landing): hand-drawn pipeline SVG diagram; v0.1.12 2026-08-16 19:11:17 +04:00
“Naeel” 0f71e715be docs(landing): rewrite as clear how-to (4 steps with copy-paste commands); v0.1.9 2026-08-16 18:51:59 +04:00
“Naeel” ce01b6f9f8 feat(api): landing page with Nubes design (logo+favicon), /static; v0.1.8 2026-08-16 18:46:17 +04:00
“Naeel” 68becfd6bc fix: review findings (async SQS dispatcher, backoff, batch insert, shutdown order, pagination, HMAC) + loadtest fixes; v0.1.6 2026-08-16 18:24:09 +04:00
“Naeel” 8b3f3ab5df feat(emqx): PG-auth in EMQX v0.2.4 + EnsureBridgeDevice v0.1.2 (tail 3) 2026-08-16 16:18:49 +04:00
“Naeel” 12b43e7999 fix(bridge): resubscribe on every (re)connect; emqx v0.2.1 dashboard off; iot-service v0.1.1 2026-08-16 15:24:26 +04:00
“Naeel” 7647627052 feat: монолит iot-service (Фаза 1) — config/store/api/bridge/consumer без k8s, устройства в PG, новые Dockerfile/Makefile 2026-08-16 10:07:51 +04:00