// Изменено: 2026-04-05 (добавлен route GET /iot/telemetry) // router.go — регистрация всех REST-маршрутов через gorilla/mux. // Все маршруты /v1/ защищены Bearer-токеном (middleware.Auth). // /console — публичный маршрут (статический HTML без auth). // CORS включён для https://iot.kube5s.ru — там хостится IoT Консоль (UI). package api import ( "log/slog" "net/http" "github.com/gorilla/mux" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/api/handler" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/api/middleware" ) // corsMiddleware добавляет CORS-заголовки для IoT Консоли на https://iot.kube5s.ru. // Нужен потому что: консоль на https://iot.kube5s.ru, API на https://sless.kube5s.ru — разные origin. // Обрабатывает preflight OPTIONS запросы от браузера. func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "https://iot.kube5s.ru") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type") w.Header().Set("Access-Control-Max-Age", "600") // Preflight OPTIONS возвращаем немедленно без передачи дальше if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } // NewRouter собирает gorilla/mux роутер со всеми маршрутами. // /fn/{namespace}/{name} — публичный прокси для вызова функций, без auth. // /console — IoT Консоль (HTML SPA), без auth, с CORS. // /v1/ — защищён JWT-аутентификацией (middleware.Auth), с CORS. func NewRouter(h *handler.Handler, log *slog.Logger) http.Handler { r := mux.NewRouter() // IoT Консоль — статический HTML, публично доступен r.HandleFunc("/console", ServeIoTConsole).Methods(http.MethodGet) // Публичный прокси для вызова HTTP-триггеров — без auth токена // Все HTTP методы разрешены (GET/POST/PUT/... — решает сама функция) r.PathPrefix("/fn/{namespace}/{name}").HandlerFunc(h.InvokeFunction) // Суброутер для /v1 — все маршруты API v1 := r.PathPrefix("/v1").Subrouter() // Namespace lifecycle — вызывается провайдером ОДИН РАЗ при Configure() // до создания любых ресурсов; идемпотентен. v1.HandleFunc("/namespaces/{namespace}/ensure", h.EnsureNamespace).Methods(http.MethodPost) // Functions CRUD v1.HandleFunc("/namespaces/{namespace}/functions", h.ListFunctions).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/functions", h.CreateFunction).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/functions/{name}", h.GetFunction).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/functions/{name}", h.UpdateFunction).Methods(http.MethodPut) v1.HandleFunc("/namespaces/{namespace}/functions/{name}", h.DeleteFunction).Methods(http.MethodDelete) // Invocation logs v1.HandleFunc("/namespaces/{namespace}/functions/{name}/invocations", h.ListInvocations).Methods(http.MethodGet) // Upload code — принимает zip, генерирует Dockerfile, кладёт tar.gz в S3, запускает сборку v1.HandleFunc("/namespaces/{namespace}/functions/{name}/upload", h.UploadCode).Methods(http.MethodPost) // Source code — возвращает файлы из tar.gz контекста сборки (без Dockerfile) v1.HandleFunc("/namespaces/{namespace}/functions/{name}/source", h.GetSource).Methods(http.MethodGet) // Services CRUD — long-running Deployment + URL (sless_service) v1.HandleFunc("/namespaces/{namespace}/services", h.ListServices).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/services", h.CreateService).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.GetService).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.UpdateService).Methods(http.MethodPut) v1.HandleFunc("/namespaces/{namespace}/services/{name}", h.DeleteService).Methods(http.MethodDelete) v1.HandleFunc("/namespaces/{namespace}/services/{name}/upload", h.UploadServiceCode).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/services/{name}/source", h.GetServiceSource).Methods(http.MethodGet) // Triggers CRUD v1.HandleFunc("/namespaces/{namespace}/triggers", h.ListTriggers).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/triggers", h.CreateTrigger).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/triggers/{name}", h.GetTrigger).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/triggers/{name}", h.UpdateTrigger).Methods(http.MethodPatch) v1.HandleFunc("/namespaces/{namespace}/triggers/{name}", h.DeleteTrigger).Methods(http.MethodDelete) // Jobs CRUD — одноразовые запуски функций v1.HandleFunc("/namespaces/{namespace}/jobs", h.CreateJob).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/jobs/{name}", h.GetJob).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/jobs/{name}", h.DeleteJob).Methods(http.MethodDelete) v1.HandleFunc("/namespaces/{namespace}/jobs/{name}/upload", h.UploadJobCode).Methods(http.MethodPost) // IoT Devices CRUD — защищены JWT (как все /v1/ маршруты) v1.HandleFunc("/namespaces/{namespace}/iot/devices", h.ListIoTDevices).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/iot/devices", h.CreateIoTDevice).Methods(http.MethodPost) v1.HandleFunc("/namespaces/{namespace}/iot/devices/{name}", h.GetIoTDevice).Methods(http.MethodGet) v1.HandleFunc("/namespaces/{namespace}/iot/devices/{name}", h.DeleteIoTDevice).Methods(http.MethodDelete) v1.HandleFunc("/namespaces/{namespace}/iot/devices/{name}", h.UpdateIoTDevice).Methods(http.MethodPatch) // IoT Telemetry — чтение сырых данных от устройств (Postgres per-tenant) v1.HandleFunc("/namespaces/{namespace}/iot/telemetry", h.ListIoTTelemetry).Methods(http.MethodGet) // MQTT Auth — БЕЗ JWT. Вызывается EMQX при MQTT CONNECT из кластера. // /internal/ недоступен снаружи (Ingress не проксирует /internal/). r.HandleFunc("/internal/mqtt/auth", h.MQTTAuth).Methods(http.MethodPost) // MQTT ACL — БЕЗ JWT. Вызывается EMQX при каждом pub/sub для проверки прав. // Изолирует клиента в пределах его топиков: {namespace}/{deviceId}/# r.HandleFunc("/internal/mqtt/acl", h.MQTTAcl).Methods(http.MethodPost) // Цепочка middleware: CORS → logging → (auth только для /v1/) → router // /fn/ — без auth, /console — без auth, /v1/ — с auth. v1.Use(func(next http.Handler) http.Handler { return middleware.Auth(log, next) }) return corsMiddleware(middleware.Logging(log, r)) }