v0.1.59: IoT telemetry pipeline — Postgres storage + REST API + UI table

This commit is contained in:
Naeel
2026-04-05 08:46:17 +03:00
parent d51e33d876
commit 2bdd753f4e
11 changed files with 630 additions and 21 deletions
+5 -2
View File
@@ -1,4 +1,4 @@
// Изменено: 2026-03-11
// Изменено: 2026-04-05 (добавлено поле IoTPG для IoT телеметрии)
// Handler — общий контейнер зависимостей для всех REST handlers.
// Все handlers получают доступ к k8s, S3 и Postgres через эту структуру.
// Логирование через slog, маршрутизация через gorilla/mux.
@@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/iotpg"
"gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/postgres"
"gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/s3"
)
@@ -43,7 +44,9 @@ type Handler struct {
Scheme *runtime.Scheme
S3 *s3.Client
PG *postgres.Store
Log *slog.Logger
// IoTPG — хранилище IoT телеметрии (per-tenant Postgres). nil если IOT_PG_DSN не задан.
IoTPG *iotpg.IoTPostgresStore
Log *slog.Logger
}
// writeJSON отправляет JSON-ответ с указанным статусом.
@@ -0,0 +1,55 @@
// Создано: 2026-04-05
// iot_telemetry_handler.go — REST handler для чтения IoT телеметрии.
//
// Endpoint:
// GET /v1/namespaces/{namespace}/iot/telemetry?device={id}&limit={n}
//
// Авторизация: Bearer JWT → namespace validation (как все /v1/ маршруты).
// Данные берутся из per-tenant Postgres DB через IoTPostgresStore.
// Если IoTPG не инициализирован (IOT_PG_DSN не задан) — возвращает 503.
package handler
import (
"net/http"
"strconv"
"github.com/gorilla/mux"
"gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/iotpg"
)
// ListIoTTelemetry обрабатывает GET /v1/namespaces/{namespace}/iot/telemetry.
// Параметры: device (опционально), limit (default 50, max 1000).
func (h *Handler) ListIoTTelemetry(w http.ResponseWriter, r *http.Request) {
if h.IoTPG == nil {
writeJSON(w, http.StatusServiceUnavailable, errResp("IoT telemetry storage not configured"))
return
}
ns := mux.Vars(r)["namespace"]
deviceID := r.URL.Query().Get("device")
limit := 50
if ls := r.URL.Query().Get("limit"); ls != "" {
if n, err := strconv.Atoi(ls); err == nil && n > 0 {
limit = n
}
}
rows, err := h.IoTPG.QueryTelemetry(r.Context(), ns, deviceID, limit)
if err != nil {
h.Log.Error("query IoT telemetry", "namespace", ns, "device", deviceID, "err", err)
writeJSON(w, http.StatusInternalServerError, errResp("failed to query telemetry"))
return
}
// Возвращаем пустой массив вместо null — удобнее для JS
if rows == nil {
rows = []iotpg.TelemetryRow{}
}
writeJSON(w, http.StatusOK, map[string]any{
"items": rows,
"count": len(rows),
})
}