mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
19 reads across three files stop naming gorilla to address a resource by id and go through coretypes.Param: 13 in the query-service handler (rule id, integration id, pipeline version, six funnel ids), 1 in the parser (trace id), 5 in the gateway (key id, limit id). Registration is untouched — this is the read side. Where a site had bound `vars := mux.Vars(r)` only to index it once, the map is gone with it; a handler wants the VALUE, and keeping a map alive to hold one string was the router leaking into the body. The query half stays exactly where it was. ParseSearchTracesParams now reads the trace id from the PATH and the span id from the QUERY through two different calls, which is what they always were. http_handler.go keeps `mux.NewRouter().UseEncodedPath()` and the websocket upgrader: those are the internal tree and its transport, converted as one piece with the other 70 registration files, not here. parser.go drops the gorilla import entirely; gateway/handler.go likewise. Tests: neither package had one that exercised a path segment, so both go green whether the read works or not — this repo has already shipped that defect. Each now drives a REAL router registered at the REAL template. The gateway rows go router -> handler -> Gateway and assert the id the gateway was HANDED, so an empty segment fails on the recorded value rather than on a status code. Under a Param that returns "", all four tests go red; before them, both packages stayed green. Co-authored-by: Hanzo Dev <[email protected]>
288 lines
7.0 KiB
Go
288 lines
7.0 KiB
Go
package gateway
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/hanzoai/o11y/pkg/errors"
|
|
"github.com/hanzoai/o11y/pkg/http/render"
|
|
"github.com/hanzoai/o11y/pkg/types/authtypes"
|
|
"github.com/hanzoai/o11y/pkg/types/coretypes"
|
|
"github.com/hanzoai/o11y/pkg/types/gatewaytypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
)
|
|
|
|
const (
|
|
DefaultPage = 1
|
|
DefaultPageSize = 10
|
|
)
|
|
|
|
type handler struct {
|
|
gateway Gateway
|
|
}
|
|
|
|
func NewHandler(gateway Gateway) Handler {
|
|
return &handler{
|
|
gateway: gateway,
|
|
}
|
|
}
|
|
|
|
func (handler *handler) GetIngestionKeys(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
pageString := r.URL.Query().Get("page")
|
|
perPageString := r.URL.Query().Get("per_page")
|
|
|
|
page, err := parseIntWithDefaultValue(pageString, DefaultPage)
|
|
if err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "page must be a valid integer"))
|
|
return
|
|
}
|
|
|
|
perPage, err := parseIntWithDefaultValue(perPageString, DefaultPageSize)
|
|
if err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "per_page must be a valid integer"))
|
|
return
|
|
}
|
|
|
|
response, err := handler.gateway.GetIngestionKeys(ctx, orgID, page, perPage)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, response)
|
|
}
|
|
|
|
func (handler *handler) SearchIngestionKeys(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
pageString := r.URL.Query().Get("page")
|
|
perPageString := r.URL.Query().Get("per_page")
|
|
name := r.URL.Query().Get("name")
|
|
|
|
page, err := parseIntWithDefaultValue(pageString, DefaultPage)
|
|
if err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "page must be a valid integer"))
|
|
return
|
|
}
|
|
|
|
perPage, err := parseIntWithDefaultValue(perPageString, DefaultPageSize)
|
|
if err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "per_page must be a valid integer"))
|
|
return
|
|
}
|
|
|
|
response, err := handler.gateway.SearchIngestionKeysByName(ctx, orgID, name, page, perPage)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, response)
|
|
}
|
|
|
|
func (handler *handler) CreateIngestionKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
var req gatewaytypes.PostableIngestionKey
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
response, err := handler.gateway.CreateIngestionKey(ctx, orgID, req.Name, req.Tags, req.ExpiresAt)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, response)
|
|
}
|
|
|
|
func (handler *handler) UpdateIngestionKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
keyID := coretypes.Param(r, "keyId")
|
|
if keyID == "" {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
|
return
|
|
}
|
|
|
|
var req gatewaytypes.PostableIngestionKey
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
err = handler.gateway.UpdateIngestionKey(ctx, orgID, keyID, req.Name, req.Tags, req.ExpiresAt)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) DeleteIngestionKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
keyID := coretypes.Param(r, "keyId")
|
|
if keyID == "" {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
|
return
|
|
}
|
|
|
|
err = handler.gateway.DeleteIngestionKey(ctx, orgID, keyID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) CreateIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
keyID := coretypes.Param(r, "keyId")
|
|
if keyID == "" {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "keyId is required"))
|
|
return
|
|
}
|
|
|
|
var req gatewaytypes.PostableIngestionKeyLimit
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
response, err := handler.gateway.CreateIngestionKeyLimit(ctx, orgID, keyID, req.Signal, req.Config, req.Tags)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, response)
|
|
}
|
|
|
|
func (handler *handler) UpdateIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
limitID := coretypes.Param(r, "limitId")
|
|
if limitID == "" {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "limitId is required"))
|
|
return
|
|
}
|
|
|
|
var req gatewaytypes.UpdatableIngestionKeyLimit
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request body"))
|
|
return
|
|
}
|
|
|
|
err = handler.gateway.UpdateIngestionKeyLimit(ctx, orgID, limitID, req.Config, req.Tags)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) DeleteIngestionKeyLimit(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
limitID := coretypes.Param(r, "limitId")
|
|
if limitID == "" {
|
|
render.Error(rw, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "limitId is required"))
|
|
return
|
|
}
|
|
|
|
err = handler.gateway.DeleteIngestionKeyLimit(ctx, orgID, limitID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func parseIntWithDefaultValue(value string, defaultValue int) (int, error) {
|
|
if value == "" {
|
|
return defaultValue, nil
|
|
}
|
|
|
|
result, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|