// Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 package api import ( "context" "log/slog" "math" "net/http" "time" "github.com/absmach/magistrala" api "github.com/absmach/magistrala/api/http" apiutil "github.com/absmach/magistrala/api/http/util" "github.com/absmach/magistrala/journal" smqauthn "github.com/absmach/magistrala/pkg/authn" "github.com/absmach/magistrala/pkg/errors" "github.com/absmach/magistrala/pkg/uuid" "github.com/go-chi/chi/v5" kithttp "github.com/go-kit/kit/transport/http" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) const ( operationKey = "operation" fromKey = "from" toKey = "to" attributesKey = "with_attributes" metadataKey = "with_metadata" ) // MakeHandler returns a HTTP API handler with health check and metrics. func MakeHandler(svc journal.Service, authn smqauthn.AuthNMiddleware, logger *slog.Logger, svcName, instanceID string) http.Handler { opts := []kithttp.ServerOption{ kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)), } mux := chi.NewRouter() idp := uuid.New() mux.Use(api.RequestIDMiddleware(idp)) mux.With(authn.WithOptions(smqauthn.WithDomainCheck(false)).Middleware()).Get("/journal/user/{userID}", otelhttp.NewHandler(kithttp.NewServer( retrieveJournalsEndpoint(svc), decodeRetrieveUserJournalReq, api.EncodeResponse, opts..., ), "list_user_journals").ServeHTTP) mux.Route("/{domainID}/journal", func(r chi.Router) { r.Use(authn.Middleware()) r.Get("/{entityType}/{entityID}", otelhttp.NewHandler(kithttp.NewServer( retrieveJournalsEndpoint(svc), decodeRetrieveEntityJournalReq, api.EncodeResponse, opts..., ), "list__entity_journals").ServeHTTP) r.Get("/client/{clientID}/telemetry", otelhttp.NewHandler(kithttp.NewServer( retrieveClientTelemetryEndpoint(svc), decodeRetrieveClientTelemetryReq, api.EncodeResponse, opts..., ), "view_client_telemetry").ServeHTTP) }) mux.Get("/health", magistrala.Health(svcName, instanceID)) mux.Handle("/metrics", promhttp.Handler()) return mux } func decodeRetrieveEntityJournalReq(_ context.Context, r *http.Request) (any, error) { page, err := decodePageQuery(r) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } entityType, err := journal.ToEntityType(chi.URLParam(r, "entityType")) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } page.EntityID = chi.URLParam(r, "entityID") page.EntityType = entityType req := retrieveJournalsReq{ token: apiutil.ExtractBearerToken(r), page: page, } return req, nil } func decodeRetrieveUserJournalReq(_ context.Context, r *http.Request) (any, error) { page, err := decodePageQuery(r) if err != nil { return nil, errors.Wrap(apiutil.ErrValidation, err) } page.EntityID = chi.URLParam(r, "userID") page.EntityType = journal.UserEntity req := retrieveJournalsReq{ token: apiutil.ExtractBearerToken(r), page: page, } return req, nil } func decodePageQuery(r *http.Request) (journal.Page, error) { offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } operation, err := apiutil.ReadStringQuery(r, operationKey, "") if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } from, err := apiutil.ReadNumQuery[int64](r, fromKey, 0) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } if from > math.MaxInt32 { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat) } var fromTime time.Time if from != 0 { fromTime = time.Unix(from, 0) } to, err := apiutil.ReadNumQuery[int64](r, toKey, 0) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } if to > math.MaxInt32 { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, apiutil.ErrInvalidTimeFormat) } var toTime time.Time if to != 0 { toTime = time.Unix(to, 0) } attributes, err := apiutil.ReadBoolQuery(r, attributesKey, false) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } metadata, err := apiutil.ReadBoolQuery(r, metadataKey, false) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DescDir) if err != nil { return journal.Page{}, errors.Wrap(apiutil.ErrValidation, err) } return journal.Page{ Offset: offset, Limit: limit, Operation: operation, From: fromTime, To: toTime, WithAttributes: attributes, WithMetadata: metadata, Direction: dir, }, nil } func decodeRetrieveClientTelemetryReq(_ context.Context, r *http.Request) (any, error) { req := retrieveClientTelemetryReq{ clientID: chi.URLParam(r, "clientID"), } return req, nil }