From b37ba0f1086a82e709f47b6892f0041a7abf89e0 Mon Sep 17 00:00:00 2001 From: chronark Date: Mon, 27 Jan 2025 08:35:16 +0100 Subject: [PATCH 1/9] feat: golang entities for repository pattern --- go/api/gen.go | 477 +--------------------------------- go/cmd/main.go | 2 +- go/go.mod | 22 +- go/go.sum | 49 +--- go/hash/sha256.go | 13 + go/hash/sha256_test.go | 22 ++ go/pkg/clickhouse/flush.go | 9 +- go/pkg/database/database.go | 77 +++++- go/pkg/database/interface.go | 40 +++ go/pkg/database/middleware.go | 4 + go/pkg/entities/key.go | 51 ++++ go/pkg/fault/flatten.go | 49 ++++ 12 files changed, 262 insertions(+), 553 deletions(-) create mode 100644 go/hash/sha256.go create mode 100644 go/hash/sha256_test.go create mode 100644 go/pkg/database/interface.go create mode 100644 go/pkg/database/middleware.go create mode 100644 go/pkg/entities/key.go create mode 100644 go/pkg/fault/flatten.go diff --git a/go/api/gen.go b/go/api/gen.go index 823db084a6..2bedbf76b8 100644 --- a/go/api/gen.go +++ b/go/api/gen.go @@ -1,20 +1,8 @@ -//go:build go1.22 - -// Package openapi provides primitives to interact with the openapi HTTP API. +// Package api provides primitives to interact with the openapi HTTP API. // // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. package api -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/oapi-codegen/runtime" - strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" -) - // BadRequestError defines model for BadRequestError. type BadRequestError struct { // Detail A human-readable explanation specific to this occurrence of the problem. @@ -152,466 +140,3 @@ type V1RatelimitLimitJSONRequestBody = V2RatelimitLimitRequestBody // V2RatelimitSetOverrideJSONRequestBody defines body for V2RatelimitSetOverride for application/json ContentType. type V2RatelimitSetOverrideJSONRequestBody = V2RatelimitSetOverrideRequestBody - -// ServerInterface represents all server handlers. -type ServerInterface interface { - // Liveness check - // (GET /v2/liveness) - Liveness(w http.ResponseWriter, r *http.Request) - - // (POST /v2/ratelimit.limit) - V1RatelimitLimit(w http.ResponseWriter, r *http.Request, params V1RatelimitLimitParams) - - // (POST /v2/ratelimit.setOverride) - V2RatelimitSetOverride(w http.ResponseWriter, r *http.Request) -} - -// ServerInterfaceWrapper converts contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) -} - -type MiddlewareFunc func(http.Handler) http.Handler - -// Liveness operation middleware -func (siw *ServerInterfaceWrapper) Liveness(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Liveness(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// V1RatelimitLimit operation middleware -func (siw *ServerInterfaceWrapper) V1RatelimitLimit(w http.ResponseWriter, r *http.Request) { - - var err error - - // Parameter object where we will unmarshal all parameters from the context - var params V1RatelimitLimitParams - - // ------------- Optional query parameter "xxx" ------------- - - err = runtime.BindQueryParameter("form", true, false, "xxx", r.URL.Query(), ¶ms.Xxx) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "xxx", Err: err}) - return - } - - // ------------- Required query parameter "yyy" ------------- - - if paramValue := r.URL.Query().Get("yyy"); paramValue != "" { - - } else { - siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "yyy"}) - return - } - - err = runtime.BindQueryParameter("form", true, true, "yyy", r.URL.Query(), ¶ms.Yyy) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "yyy", Err: err}) - return - } - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.V1RatelimitLimit(w, r, params) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -// V2RatelimitSetOverride operation middleware -func (siw *ServerInterfaceWrapper) V2RatelimitSetOverride(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.V2RatelimitSetOverride(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r) -} - -type UnescapedCookieParamError struct { - ParamName string - Err error -} - -func (e *UnescapedCookieParamError) Error() string { - return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) -} - -func (e *UnescapedCookieParamError) Unwrap() error { - return e.Err -} - -type UnmarshalingParamError struct { - ParamName string - Err error -} - -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) -} - -func (e *UnmarshalingParamError) Unwrap() error { - return e.Err -} - -type RequiredParamError struct { - ParamName string -} - -func (e *RequiredParamError) Error() string { - return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) -} - -type RequiredHeaderError struct { - ParamName string - Err error -} - -func (e *RequiredHeaderError) Error() string { - return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) -} - -func (e *RequiredHeaderError) Unwrap() error { - return e.Err -} - -type InvalidParamFormatError struct { - ParamName string - Err error -} - -func (e *InvalidParamFormatError) Error() string { - return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) -} - -func (e *InvalidParamFormatError) Unwrap() error { - return e.Err -} - -type TooManyValuesForParamError struct { - ParamName string - Count int -} - -func (e *TooManyValuesForParamError) Error() string { - return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) -} - -// Handler creates http.Handler with routing matching OpenAPI spec. -func Handler(si ServerInterface) http.Handler { - return HandlerWithOptions(si, StdHTTPServerOptions{}) -} - -// ServeMux is an abstraction of http.ServeMux. -type ServeMux interface { - HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) - ServeHTTP(w http.ResponseWriter, r *http.Request) -} - -type StdHTTPServerOptions struct { - BaseURL string - BaseRouter ServeMux - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) -} - -// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler { - return HandlerWithOptions(si, StdHTTPServerOptions{ - BaseRouter: m, - }) -} - -func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler { - return HandlerWithOptions(si, StdHTTPServerOptions{ - BaseURL: baseURL, - BaseRouter: m, - }) -} - -// HandlerWithOptions creates http.Handler with additional options -func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler { - m := options.BaseRouter - - if m == nil { - m = http.NewServeMux() - } - if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, err.Error(), http.StatusBadRequest) - } - } - - wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, - } - - m.HandleFunc("GET "+options.BaseURL+"/v2/liveness", wrapper.Liveness) - m.HandleFunc("POST "+options.BaseURL+"/v2/ratelimit.limit", wrapper.V1RatelimitLimit) - m.HandleFunc("POST "+options.BaseURL+"/v2/ratelimit.setOverride", wrapper.V2RatelimitSetOverride) - - return m -} - -type LivenessRequestObject struct { -} - -type LivenessResponseObject interface { - VisitLivenessResponse(w http.ResponseWriter) error -} - -type Liveness200JSONResponse V2LivenessResponseBody - -func (response Liveness200JSONResponse) VisitLivenessResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type Liveness500JSONResponse InternalServerError - -func (response Liveness500JSONResponse) VisitLivenessResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -type V1RatelimitLimitRequestObject struct { - Params V1RatelimitLimitParams - Body *V1RatelimitLimitJSONRequestBody -} - -type V1RatelimitLimitResponseObject interface { - VisitV1RatelimitLimitResponse(w http.ResponseWriter) error -} - -type V1RatelimitLimit200JSONResponse V2RatelimitLimitResponseBody - -func (response V1RatelimitLimit200JSONResponse) VisitV1RatelimitLimitResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type V1RatelimitLimit400JSONResponse BadRequestError - -func (response V1RatelimitLimit400JSONResponse) VisitV1RatelimitLimitResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type V1RatelimitLimit404JSONResponse NotFoundError - -func (response V1RatelimitLimit404JSONResponse) VisitV1RatelimitLimitResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type V1RatelimitLimit500JSONResponse InternalServerError - -func (response V1RatelimitLimit500JSONResponse) VisitV1RatelimitLimitResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -type V2RatelimitSetOverrideRequestObject struct { - Body *V2RatelimitSetOverrideJSONRequestBody -} - -type V2RatelimitSetOverrideResponseObject interface { - VisitV2RatelimitSetOverrideResponse(w http.ResponseWriter) error -} - -type V2RatelimitSetOverride200JSONResponse V2RatelimitSetOverrideResponseBody - -func (response V2RatelimitSetOverride200JSONResponse) VisitV2RatelimitSetOverrideResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type V2RatelimitSetOverride400JSONResponse BadRequestError - -func (response V2RatelimitSetOverride400JSONResponse) VisitV2RatelimitSetOverrideResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type V2RatelimitSetOverride404JSONResponse NotFoundError - -func (response V2RatelimitSetOverride404JSONResponse) VisitV2RatelimitSetOverrideResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type V2RatelimitSetOverride500JSONResponse InternalServerError - -func (response V2RatelimitSetOverride500JSONResponse) VisitV2RatelimitSetOverrideResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -// StrictServerInterface represents all server handlers. -type StrictServerInterface interface { - // Liveness check - // (GET /v2/liveness) - Liveness(ctx context.Context, request LivenessRequestObject) (LivenessResponseObject, error) - - // (POST /v2/ratelimit.limit) - V1RatelimitLimit(ctx context.Context, request V1RatelimitLimitRequestObject) (V1RatelimitLimitResponseObject, error) - - // (POST /v2/ratelimit.setOverride) - V2RatelimitSetOverride(ctx context.Context, request V2RatelimitSetOverrideRequestObject) (V2RatelimitSetOverrideResponseObject, error) -} - -type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc -type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc - -type StrictHTTPServerOptions struct { - RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) - ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) -} - -func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { - return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ - RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, err.Error(), http.StatusBadRequest) - }, - ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, err.Error(), http.StatusInternalServerError) - }, - }} -} - -func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { - return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} -} - -type strictHandler struct { - ssi StrictServerInterface - middlewares []StrictMiddlewareFunc - options StrictHTTPServerOptions -} - -// Liveness operation middleware -func (sh *strictHandler) Liveness(w http.ResponseWriter, r *http.Request) { - var request LivenessRequestObject - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.Liveness(ctx, request.(LivenessRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "Liveness") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(LivenessResponseObject); ok { - if err := validResponse.VisitLivenessResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// V1RatelimitLimit operation middleware -func (sh *strictHandler) V1RatelimitLimit(w http.ResponseWriter, r *http.Request, params V1RatelimitLimitParams) { - var request V1RatelimitLimitRequestObject - - request.Params = params - - var body V1RatelimitLimitJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.V1RatelimitLimit(ctx, request.(V1RatelimitLimitRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "V1RatelimitLimit") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(V1RatelimitLimitResponseObject); ok { - if err := validResponse.VisitV1RatelimitLimitResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - -// V2RatelimitSetOverride operation middleware -func (sh *strictHandler) V2RatelimitSetOverride(w http.ResponseWriter, r *http.Request) { - var request V2RatelimitSetOverrideRequestObject - - var body V2RatelimitSetOverrideJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.V2RatelimitSetOverride(ctx, request.(V2RatelimitSetOverrideRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "V2RatelimitSetOverride") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(V2RatelimitSetOverrideResponseObject); ok { - if err := validResponse.VisitV2RatelimitSetOverrideResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} diff --git a/go/cmd/main.go b/go/cmd/main.go index 3318a6ac14..ec37872ebf 100644 --- a/go/cmd/main.go +++ b/go/cmd/main.go @@ -4,8 +4,8 @@ import ( "fmt" "os" - "github.com/Southclaws/fault" "github.com/unkeyed/unkey/go/cmd/api" + "github.com/unkeyed/unkey/go/pkg/fault" "github.com/urfave/cli/v2" ) diff --git a/go/go.mod b/go/go.mod index 2907da55bf..26da1b1c5f 100644 --- a/go/go.mod +++ b/go/go.mod @@ -4,19 +4,17 @@ go 1.23.4 require ( github.com/ClickHouse/clickhouse-go/v2 v2.28.1 - github.com/Southclaws/fault v0.8.1 github.com/btcsuite/btcutil v1.0.2 github.com/danielgtaylor/huma v1.14.2 github.com/google/uuid v1.6.0 github.com/lmittmann/tint v1.0.6 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 - github.com/oapi-codegen/runtime v1.1.1 github.com/pb33f/libopenapi v0.16.5 github.com/pb33f/libopenapi-validator v0.1.0 github.com/segmentio/ksuid v1.0.4 github.com/sqlc-dev/sqlc v1.28.0 github.com/stretchr/testify v1.10.0 - github.com/unkeyed/unkey/apps/agent v0.0.0-20250104094322-474d5d220db9 + github.com/unkeyed/unkey/apps/agent v0.0.0-20250124105149-f6d38cdef22e github.com/urfave/cli/v2 v2.27.4 github.com/xeipuuv/gojsonschema v1.2.0 ) @@ -25,14 +23,12 @@ require ( cel.dev/expr v0.18.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/ClickHouse/ch-go v0.62.0 // indirect + github.com/Southclaws/fault v0.8.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/axiomhq/axiom-go v0.20.2 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cubicdaiya/gonp v1.0.4 // indirect @@ -40,19 +36,15 @@ require ( github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/google/cel-go v0.22.1 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -63,7 +55,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -86,7 +77,6 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/riza-io/grpc-go v0.2.0 // indirect - github.com/rs/zerolog v1.33.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/segmentio/asm v1.2.0 // indirect @@ -104,14 +94,8 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/go/go.sum b/go/go.sum index 00be548096..6bc12d9fef 100644 --- a/go/go.sum +++ b/go/go.sum @@ -61,7 +61,6 @@ github.com/ClickHouse/clickhouse-go/v2 v2.28.1/go.mod h1:0U915l9qynE508ehh3ea9+U github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Jeffail/gabs/v2 v2.6.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/Southclaws/fault v0.8.1 h1:mgqqdC6kUBQ6ExMALZ0nNaDfNJD5h2+wq3se5mAyX+8= github.com/Southclaws/fault v0.8.1/go.mod h1:VUVkAWutC59SL16s6FTqf3I6I2z77RmnaW5XRz4bLOE= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -75,15 +74,11 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/axiomhq/axiom-go v0.20.2 h1:RKelFJr8Pei0xIoBaVteGGvn2pkaaMLrWiHLWu8d0Mc= -github.com/axiomhq/axiom-go v0.20.2/go.mod h1:TWHIoBDv/IJNKgyo2EQeOwH4svi+cTePSihPVWZC1/8= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -93,7 +88,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -106,8 +100,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -133,7 +125,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -171,8 +162,6 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -194,7 +183,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -270,8 +258,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -308,8 +294,6 @@ github.com/graphql-go/graphql v0.7.9/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKd github.com/graphql-go/graphql v0.8.0/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ= github.com/graphql-go/handler v0.2.3/go.mod h1:leLF6RpV5uZMN1CdImAxuiayrYYhOk33bZciaUGaXeU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -367,7 +351,6 @@ github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -401,16 +384,12 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -440,8 +419,6 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= -github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= -github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -523,9 +500,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -558,7 +532,6 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/sqlc-dev/sqlc v1.28.0 h1:2QB4X22pKNpKMyb8dRLnqZwMXW6S+ZCyYCpa+3/ICcI= github.com/sqlc-dev/sqlc v1.28.0/go.mod h1:x6wDsOHH60dTX3ES9sUUxRVaROg5aFB3l3nkkjyuK1A= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= @@ -577,20 +550,12 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/unkeyed/unkey/apps/agent v0.0.0-20250104094322-474d5d220db9 h1:tmZvgFuA3FfBVcfAqRK+EpM9MFzup7imdyB9ywrAk+Q= -github.com/unkeyed/unkey/apps/agent v0.0.0-20250104094322-474d5d220db9/go.mod h1:DT9bY3agxOekRiikkvtXWqlUpRoyDqL0RYWQECD59SI= +github.com/unkeyed/unkey/apps/agent v0.0.0-20250124105149-f6d38cdef22e h1:WHBZyYpuJatvqk1Qz12+pOkiv8nLsiaC4gGstmpMlwI= +github.com/unkeyed/unkey/apps/agent v0.0.0-20250124105149-f6d38cdef22e/go.mod h1:ogkfx3pi5Gf7JDt1+Y4Z+nyU87Kx/J31kqOYu+AO+Fk= github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= @@ -631,14 +596,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= @@ -648,8 +607,6 @@ go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -875,10 +832,8 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/go/hash/sha256.go b/go/hash/sha256.go new file mode 100644 index 0000000000..db634d4383 --- /dev/null +++ b/go/hash/sha256.go @@ -0,0 +1,13 @@ +package hash + +import ( + "crypto/sha256" + "encoding/base64" +) + +func Sha256(s string) string { + hash := sha256.New() + hash.Write([]byte(s)) + + return base64.StdEncoding.EncodeToString(hash.Sum(nil)) +} diff --git a/go/hash/sha256_test.go b/go/hash/sha256_test.go new file mode 100644 index 0000000000..dd163fae99 --- /dev/null +++ b/go/hash/sha256_test.go @@ -0,0 +1,22 @@ +package hash + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSha256(t *testing.T) { + for i := 0; i < 100; i++ { + b := []byte{32} + _, err := rand.Read(b) + require.NoError(t, err) + s := string(b) + h := Sha256(s) + require.Greater(t, len(h), 10) + + // check if it's consistent + require.Equal(t, h, Sha256(s)) + } +} diff --git a/go/pkg/clickhouse/flush.go b/go/pkg/clickhouse/flush.go index 12778c1417..790ce5f923 100644 --- a/go/pkg/clickhouse/flush.go +++ b/go/pkg/clickhouse/flush.go @@ -5,24 +5,23 @@ import ( "fmt" ch "github.com/ClickHouse/clickhouse-go/v2" - "github.com/Southclaws/fault" - "github.com/Southclaws/fault/fmsg" + "github.com/unkeyed/unkey/go/pkg/fault" ) func flush[T any](ctx context.Context, conn ch.Conn, table string, rows []T) error { batch, err := conn.PrepareBatch(ctx, fmt.Sprintf("INSERT INTO %s", table)) if err != nil { - return fault.Wrap(err, fmsg.With("preparing batch failed")) + return fault.Wrap(err, fault.WithDesc("preparing batch failed", "")) } for _, row := range rows { err = batch.AppendStruct(&row) if err != nil { - return fault.Wrap(err, fmsg.With("appending struct to batch failed")) + return fault.Wrap(err, fault.WithDesc("appending struct to batch failed", "")) } } err = batch.Send() if err != nil { - return fault.Wrap(err, fmsg.With("committing batch failed")) + return fault.Wrap(err, fault.WithDesc("committing batch failed", "")) } return nil } diff --git a/go/pkg/database/database.go b/go/pkg/database/database.go index 5eb089154f..eaac6e3215 100644 --- a/go/pkg/database/database.go +++ b/go/pkg/database/database.go @@ -7,14 +7,81 @@ import ( "github.com/unkeyed/unkey/go/pkg/fault" ) -type Database = gen.Querier +type Config struct { + // The primary DSN for your database. This must support both reads and writes. + PrimaryDSN string -func New(dsn string) (Database, error) { - db, err := sql.Open("mysql", dsn) + // The readonly replica will be used for most read queries. + // If omitted, the primary is used. + ReadOnlyDSN string +} + +type replica struct { + db *sql.DB + query *gen.Queries +} + +type database struct { + writeReplica *replica + readReplica *replica +} + +func New(config Config, middlewares ...Middleware) (Database, error) { + + db := &database{} + + write, err := sql.Open("mysql", config.PrimaryDSN) if err != nil { - return nil, fault.Wrap(err, fault.WithDesc("unable to open mysql connection", "")) + return nil, fault.Wrap(err, fault.WithDesc("cannot open primary replica", "")) + } + db.writeReplica = &replica{ + db: write, + query: gen.New(write), + } + + if config.ReadOnlyDSN != "" { + read, err := sql.Open("mysql", config.ReadOnlyDSN) + if err != nil { + return nil, fault.Wrap(err, fault.WithDesc("cannot open read replica", "")) + } + db.readReplica = &replica{ + db: read, + query: gen.New(read), + } + } + + var wrapped Database = db + for _, mw := range middlewares { + wrapped = mw(wrapped) + } + + return wrapped, nil + +} + +func (d *database) write() *gen.Queries { + return d.writeReplica.query +} + +func (d *database) read() *gen.Queries { + if d.readReplica != nil { + return d.readReplica.query } + return d.writeReplica.query +} - return gen.New(db), nil +func (d *database) Close() error { + writeCloseErr := d.writeReplica.db.Close() + + if d.readReplica != nil { + readCloseErr := d.readReplica.db.Close() + if readCloseErr != nil { + return fault.Wrap(readCloseErr) + } + } + if writeCloseErr != nil { + return fault.Wrap(writeCloseErr) + } + return nil } diff --git a/go/pkg/database/interface.go b/go/pkg/database/interface.go new file mode 100644 index 0000000000..f053568ce8 --- /dev/null +++ b/go/pkg/database/interface.go @@ -0,0 +1,40 @@ +package database + +import ( + "context" + + "github.com/unkeyed/unkey/go/pkg/entities" +) + +type Database interface { + + // Workspace + // InsertWorkspace(ctx context.Context, newWorkspace entities.Workspace) error + // UpdateWorkspace(ctx context.Context, workspace entities.Workspace) error + // FindWorkspace(ctx context.Context, workspaceId string) (entities.Workspace, bool, error) + + // KeyAuth + //InsertKeyAuth(ctx context.Context, newKeyAuth entities.KeyAuth) error + //DeleteKeyAuth(ctx context.Context, keyAuthId string) error + //FindKeyAuth(ctx context.Context, keyAuthId string) (keyauth entities.KeyAuth, found bool, err error) + + //// Api + //InsertApi(ctx context.Context, api entities.Api) error + //FindApi(ctx context.Context, apiId string) (api entities.Api, found bool, err error) + //DeleteApi(ctx context.Context, apiId string) error + //FindApiByKeyAuthId(ctx context.Context, keyAuthId string) (api entities.Api, found bool, err error) + //ListAllApis(ctx context.Context, limit int, offset int) ([]entities.Api, error) + + // Key + // InsertKey(ctx context.Context, newKey entities.Key) error + FindKeyById(ctx context.Context, keyId string) (key entities.Key, found bool, err error) + FindKeyByHash(ctx context.Context, hash string) (key entities.Key, found bool, err error) + // UpdateKey(ctx context.Context, key entities.Key) error + // SoftDeleteKey(ctx context.Context, keyId string) error + // DecrementRemainingKeyUsage(ctx context.Context, keyId string) (key entities.Key, err error) + // CountKeys(ctx context.Context, keyAuthId string) (int64, error) + // ListKeys(ctx context.Context, keyAuthId string, ownerId string, limit int, offset int) ([]entities.Key, error) + + // Stuff + Close() error +} diff --git a/go/pkg/database/middleware.go b/go/pkg/database/middleware.go new file mode 100644 index 0000000000..143f0f8e16 --- /dev/null +++ b/go/pkg/database/middleware.go @@ -0,0 +1,4 @@ +package database + + +type Middleware func (Database) Database diff --git a/go/pkg/entities/key.go b/go/pkg/entities/key.go new file mode 100644 index 0000000000..c6fa711e95 --- /dev/null +++ b/go/pkg/entities/key.go @@ -0,0 +1,51 @@ +package entities + +import "time" + +// Key represents an API key in the system +type Key struct { + // ID is the unique identifier for the key + ID string + + // KeySpaceID represents the key authorization space this key belongs to + KeySpaceID string + + // WorkspaceID is the ID of the workspace that owns this key + WorkspaceID string + + // Hash is the secure hash of the key used for verification + Hash string + + // Start is the prefix of the key shown to users for identification + Start string + + // ForWorkspaceID is used only for internal keys to indicate which workspace the key is for + // This is primarily used for managing the Unkey app itself and is not used for user keys + ForWorkspaceID string + + // Name is an optional human-readable identifier for the key + Name string + + // IdentityID links this key to a specific identity in the system + IdentityID string + + // Meta contains arbitrary metadata associated with the key as key-value pairs + Meta map[string]any + + // CreatedAt is the timestamp when the key was created + CreatedAt time.Time + + // UpdatedAt is the timestamp when the key was last modified + UpdatedAt time.Time + + // DeletedAt indicates when the key was revoked + // A zero time value means the key is not deleted. + DeletedAt time.Time + + // Enabled indicates whether the key is currently active (true) or disabled (false) + // Keys are enabled by default. + Enabled bool + + // Environment is an optional flag used to segment keys (e.g., "test" vs "production") + // This is a user-defined value with no system-level restrictions + Environment string diff --git a/go/pkg/fault/flatten.go b/go/pkg/fault/flatten.go new file mode 100644 index 0000000000..e5a81acede --- /dev/null +++ b/go/pkg/fault/flatten.go @@ -0,0 +1,49 @@ +package fault + +import ( + "slices" +) + +type Step struct { + Message string + Location string +} + +func Flatten(err error) []Step { + if err == nil { + return []Step{} + } + + current, ok := err.(*wrapped) + if !ok { + return []Step{} + } + + steps := []Step{} + + for current != nil { + steps = append(steps, Step{ + Message: current.internal, + Location: current.location, + }) + + // Check if there's a next error in the chain + if current.err == nil { + break + } + + // Try to get the next wrapped error + next, ok := current.err.(*wrapped) + if !ok { + // if it's not a wrapepd error, then we don't have any more public messages + // and can stop looking. + break + } + current = next + } + + slices.Reverse(steps) + + return steps + +} From e6e7e3ac6876608826af10f4cc65733e70e49ad0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 07:37:35 +0000 Subject: [PATCH 2/9] [autofix.ci] apply automated fixes --- apps/dashboard/lib/trpc/routers/key/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/lib/trpc/routers/key/delete.ts b/apps/dashboard/lib/trpc/routers/key/delete.ts index c36360fadc..33e38ac132 100644 --- a/apps/dashboard/lib/trpc/routers/key/delete.ts +++ b/apps/dashboard/lib/trpc/routers/key/delete.ts @@ -76,7 +76,7 @@ export const deleteKeys = t.procedure ); }) .catch((err) => { - console.error(err) + console.error(err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "We are unable to delete the key. Please try again or contact support@unkey.dev", From 9ed1f7c01f7713c77abf4d74c4bb8dc227db90b0 Mon Sep 17 00:00:00 2001 From: chronark Date: Tue, 28 Jan 2025 09:28:59 +0100 Subject: [PATCH 3/9] feat: entities --- go/.golangci.yaml | 4 +- go/pkg/database/database.go | 19 +++-- go/pkg/database/gen/find_key_by_hash.sql.go | 52 +++++++++++++ go/pkg/database/gen/find_key_by_id.sql.go | 38 ++++++++++ ...nd_ratelimit_override_by_identifier.sql.go | 52 +++++++++++++ go/pkg/database/gen/querier.go | 14 +++- go/pkg/database/interface.go | 26 ++++--- go/pkg/database/key_find_by_hash.go | 32 ++++++++ go/pkg/database/key_find_by_id.go | 33 ++++++++ go/pkg/database/middleware.go | 3 +- go/pkg/database/queries/find_key_by_hash.sql | 3 + go/pkg/database/queries/find_key_by_id.sql | 3 + ...find_ratelimit_override_by_identifier.sql} | 2 +- .../ratelimit_override_find_by_identifier.go | 33 ++++++++ go/pkg/database/ratelimit_override_insert.go | 32 ++++++++ go/pkg/database/transform/key.go | 65 ++++++++++++++++ .../database/transform/ratelimit_override.go | 75 +++++++++++++++++++ go/pkg/entities/key.go | 67 +++++++++-------- go/pkg/entities/ratelimit_override.go | 17 +++++ go/pkg/fault/tag.go | 7 ++ go/pkg/uid/uid.go | 13 ++-- go/pkg/zen/errors.go | 13 ---- go/pkg/zen/middleware_errors.go | 9 ++- .../v2_ratelimit_set_override/handler.go | 27 +++++-- 24 files changed, 556 insertions(+), 83 deletions(-) create mode 100644 go/pkg/database/gen/find_key_by_hash.sql.go create mode 100644 go/pkg/database/gen/find_key_by_id.sql.go create mode 100644 go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go create mode 100644 go/pkg/database/key_find_by_hash.go create mode 100644 go/pkg/database/key_find_by_id.go create mode 100644 go/pkg/database/queries/find_key_by_hash.sql create mode 100644 go/pkg/database/queries/find_key_by_id.sql rename go/pkg/database/queries/{get_key_by_hash.sql => find_ratelimit_override_by_identifier.sql} (64%) create mode 100644 go/pkg/database/ratelimit_override_find_by_identifier.go create mode 100644 go/pkg/database/ratelimit_override_insert.go create mode 100644 go/pkg/database/transform/key.go create mode 100644 go/pkg/database/transform/ratelimit_override.go create mode 100644 go/pkg/entities/ratelimit_override.go delete mode 100644 go/pkg/zen/errors.go diff --git a/go/.golangci.yaml b/go/.golangci.yaml index 62b43e6c41..6f6ade4081 100644 --- a/go/.golangci.yaml +++ b/go/.golangci.yaml @@ -69,7 +69,7 @@ linters-settings: - "^github.com/urfave/cli/v2.*$" lll: - line-length: 100 + line-length: 120 funlen: # Checks the number of lines in a function. # If lower than 0, disable the check. @@ -254,7 +254,7 @@ linters: # - goprintffuncname # checks that printf-like functions are named with f at the end - gosec # inspects source code for security problems # - intrange # finds places where for loops could make use of an integer range - - lll # reports long lines + # - lll # reports long lines - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) # - makezero # finds slice declarations with non-zero initial length # - mirror # reports wrong mirror patterns of bytes/strings usage diff --git a/go/pkg/database/database.go b/go/pkg/database/database.go index eaac6e3215..bb513a02dd 100644 --- a/go/pkg/database/database.go +++ b/go/pkg/database/database.go @@ -28,29 +28,36 @@ type database struct { func New(config Config, middlewares ...Middleware) (Database, error) { - db := &database{} - write, err := sql.Open("mysql", config.PrimaryDSN) if err != nil { return nil, fault.Wrap(err, fault.WithDesc("cannot open primary replica", "")) } - db.writeReplica = &replica{ + + writeReplica := &replica{ + db: write, + query: gen.New(write), + } + readReplica := &replica{ db: write, query: gen.New(write), } - if config.ReadOnlyDSN != "" { read, err := sql.Open("mysql", config.ReadOnlyDSN) if err != nil { return nil, fault.Wrap(err, fault.WithDesc("cannot open read replica", "")) } - db.readReplica = &replica{ + readReplica = &replica{ db: read, query: gen.New(read), } + + } + + var wrapped Database = &database{ + writeReplica: writeReplica, + readReplica: readReplica, } - var wrapped Database = db for _, mw := range middlewares { wrapped = mw(wrapped) } diff --git a/go/pkg/database/gen/find_key_by_hash.sql.go b/go/pkg/database/gen/find_key_by_hash.sql.go new file mode 100644 index 0000000000..84023de1a2 --- /dev/null +++ b/go/pkg/database/gen/find_key_by_hash.sql.go @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: find_key_by_hash.sql + +package gen + +import ( + "context" +) + +const findKeyByID = `-- name: FindKeyByID :one +SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM ` + "`" + `keys` + "`" + ` +WHERE id = ? +` + +// FindKeyByID +// +// SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` +// WHERE id = ? +func (q *Queries) FindKeyByID(ctx context.Context, id string) (Key, error) { + row := q.db.QueryRowContext(ctx, findKeyByID, id) + var i Key + err := row.Scan( + &i.ID, + &i.KeyAuthID, + &i.Hash, + &i.Start, + &i.WorkspaceID, + &i.ForWorkspaceID, + &i.Name, + &i.OwnerID, + &i.IdentityID, + &i.Meta, + &i.CreatedAt, + &i.Expires, + &i.CreatedAtM, + &i.UpdatedAtM, + &i.DeletedAtM, + &i.DeletedAt, + &i.RefillDay, + &i.RefillAmount, + &i.LastRefillAt, + &i.Enabled, + &i.RemainingRequests, + &i.RatelimitAsync, + &i.RatelimitLimit, + &i.RatelimitDuration, + &i.Environment, + ) + return i, err +} diff --git a/go/pkg/database/gen/find_key_by_id.sql.go b/go/pkg/database/gen/find_key_by_id.sql.go new file mode 100644 index 0000000000..8ca4174fa2 --- /dev/null +++ b/go/pkg/database/gen/find_key_by_id.sql.go @@ -0,0 +1,38 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: find_key_by_id.sql + +package gen + +import ( + "context" +) + +const findRatelimitOverrideByIdentifier = `-- name: FindRatelimitOverrideByIdentifier :one +SELECT id, workspace_id, namespace_id, identifier, ` + "`" + `limit` + "`" + `, duration, async, sharding, created_at, updated_at, deleted_at FROM ` + "`" + `ratelimit_overrides` + "`" + ` +WHERE identifier = ? +` + +// FindRatelimitOverrideByIdentifier +// +// SELECT id, workspace_id, namespace_id, identifier, `limit`, duration, async, sharding, created_at, updated_at, deleted_at FROM `ratelimit_overrides` +// WHERE identifier = ? +func (q *Queries) FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (RatelimitOverride, error) { + row := q.db.QueryRowContext(ctx, findRatelimitOverrideByIdentifier, identifier) + var i RatelimitOverride + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.NamespaceID, + &i.Identifier, + &i.Limit, + &i.Duration, + &i.Async, + &i.Sharding, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + ) + return i, err +} diff --git a/go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go b/go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go new file mode 100644 index 0000000000..e0cbfc6d23 --- /dev/null +++ b/go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: find_ratelimit_override_by_identifier.sql + +package gen + +import ( + "context" +) + +const findKeyByHash = `-- name: FindKeyByHash :one +SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM ` + "`" + `keys` + "`" + ` +WHERE hash = ? +` + +// FindKeyByHash +// +// SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` +// WHERE hash = ? +func (q *Queries) FindKeyByHash(ctx context.Context, hash string) (Key, error) { + row := q.db.QueryRowContext(ctx, findKeyByHash, hash) + var i Key + err := row.Scan( + &i.ID, + &i.KeyAuthID, + &i.Hash, + &i.Start, + &i.WorkspaceID, + &i.ForWorkspaceID, + &i.Name, + &i.OwnerID, + &i.IdentityID, + &i.Meta, + &i.CreatedAt, + &i.Expires, + &i.CreatedAtM, + &i.UpdatedAtM, + &i.DeletedAtM, + &i.DeletedAt, + &i.RefillDay, + &i.RefillAmount, + &i.LastRefillAt, + &i.Enabled, + &i.RemainingRequests, + &i.RatelimitAsync, + &i.RatelimitLimit, + &i.RatelimitDuration, + &i.Environment, + ) + return i, err +} diff --git a/go/pkg/database/gen/querier.go b/go/pkg/database/gen/querier.go index c3c6227a81..edd9f78666 100644 --- a/go/pkg/database/gen/querier.go +++ b/go/pkg/database/gen/querier.go @@ -9,11 +9,21 @@ import ( ) type Querier interface { - //GetKeyByHash + //FindKeyByHash // // SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` // WHERE hash = ? - GetKeyByHash(ctx context.Context, hash string) (Key, error) + FindKeyByHash(ctx context.Context, hash string) (Key, error) + //FindKeyByID + // + // SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` + // WHERE id = ? + FindKeyByID(ctx context.Context, id string) (Key, error) + //FindRatelimitOverrideByIdentifier + // + // SELECT id, workspace_id, namespace_id, identifier, `limit`, duration, async, sharding, created_at, updated_at, deleted_at FROM `ratelimit_overrides` + // WHERE identifier = ? + FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (RatelimitOverride, error) //InsertOverride // // INSERT INTO diff --git a/go/pkg/database/interface.go b/go/pkg/database/interface.go index f053568ce8..f3d39638e2 100644 --- a/go/pkg/database/interface.go +++ b/go/pkg/database/interface.go @@ -14,27 +14,31 @@ type Database interface { // FindWorkspace(ctx context.Context, workspaceId string) (entities.Workspace, bool, error) // KeyAuth - //InsertKeyAuth(ctx context.Context, newKeyAuth entities.KeyAuth) error - //DeleteKeyAuth(ctx context.Context, keyAuthId string) error - //FindKeyAuth(ctx context.Context, keyAuthId string) (keyauth entities.KeyAuth, found bool, err error) + // InsertKeyAuth(ctx context.Context, newKeyAuth entities.KeyAuth) error + // DeleteKeyAuth(ctx context.Context, keyAuthId string) error + // FindKeyAuth(ctx context.Context, keyAuthId string) (keyauth entities.KeyAuth, found bool, err error) - //// Api - //InsertApi(ctx context.Context, api entities.Api) error - //FindApi(ctx context.Context, apiId string) (api entities.Api, found bool, err error) - //DeleteApi(ctx context.Context, apiId string) error - //FindApiByKeyAuthId(ctx context.Context, keyAuthId string) (api entities.Api, found bool, err error) - //ListAllApis(ctx context.Context, limit int, offset int) ([]entities.Api, error) + // // Api + // InsertApi(ctx context.Context, api entities.Api) error + // FindApi(ctx context.Context, apiId string) (api entities.Api, found bool, err error) + // DeleteApi(ctx context.Context, apiId string) error + // FindApiByKeyAuthId(ctx context.Context, keyAuthId string) (api entities.Api, found bool, err error) + // ListAllApis(ctx context.Context, limit int, offset int) ([]entities.Api, error) // Key // InsertKey(ctx context.Context, newKey entities.Key) error - FindKeyById(ctx context.Context, keyId string) (key entities.Key, found bool, err error) - FindKeyByHash(ctx context.Context, hash string) (key entities.Key, found bool, err error) + FindKeyByID(ctx context.Context, keyId string) (key entities.Key, err error) + FindKeyByHash(ctx context.Context, hash string) (key entities.Key, err error) // UpdateKey(ctx context.Context, key entities.Key) error // SoftDeleteKey(ctx context.Context, keyId string) error // DecrementRemainingKeyUsage(ctx context.Context, keyId string) (key entities.Key, err error) // CountKeys(ctx context.Context, keyAuthId string) (int64, error) // ListKeys(ctx context.Context, keyAuthId string, ownerId string, limit int, offset int) ([]entities.Key, error) + // Ratelimit Overrides + InsertRatelimitOverride(ctx context.Context, ratelimitOverride entities.RatelimitOverride) error + FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (ratelimitOverride entities.RatelimitOverride, err error) + // Stuff Close() error } diff --git a/go/pkg/database/key_find_by_hash.go b/go/pkg/database/key_find_by_hash.go new file mode 100644 index 0000000000..5dd146af8e --- /dev/null +++ b/go/pkg/database/key_find_by_hash.go @@ -0,0 +1,32 @@ +package database + +import ( + "context" + "database/sql" + + "errors" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindKeyByHash(ctx context.Context, hash string) (entities.Key, error) { + + model, err := db.read().FindKeyByHash(ctx, hash) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.Key{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", "The key does not exist."), + ) + } + return entities.Key{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + key, err := transform.KeyModelToEntity(model) + if err != nil { + return entities.Key{}, fault.Wrap(err, fault.WithDesc("cannot transform key model to entity", "")) + } + return key, nil +} diff --git a/go/pkg/database/key_find_by_id.go b/go/pkg/database/key_find_by_id.go new file mode 100644 index 0000000000..deaa8ce429 --- /dev/null +++ b/go/pkg/database/key_find_by_id.go @@ -0,0 +1,33 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + + "errors" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindKeyByID(ctx context.Context, keyID string) (entities.Key, error) { + + model, err := db.read().FindKeyByID(ctx, keyID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.Key{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", fmt.Sprintf("The key %s does not exist.", keyID)), + ) + } + return entities.Key{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + key, err := transform.KeyModelToEntity(model) + if err != nil { + return entities.Key{}, fault.Wrap(err, fault.WithDesc("cannot transform key model to entity", "")) + } + return key, nil +} diff --git a/go/pkg/database/middleware.go b/go/pkg/database/middleware.go index 143f0f8e16..d23470e9c6 100644 --- a/go/pkg/database/middleware.go +++ b/go/pkg/database/middleware.go @@ -1,4 +1,3 @@ package database - -type Middleware func (Database) Database +type Middleware func(Database) Database diff --git a/go/pkg/database/queries/find_key_by_hash.sql b/go/pkg/database/queries/find_key_by_hash.sql new file mode 100644 index 0000000000..d97713f6da --- /dev/null +++ b/go/pkg/database/queries/find_key_by_hash.sql @@ -0,0 +1,3 @@ +-- name: FindKeyByID :one +SELECT * FROM `keys` +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/find_key_by_id.sql b/go/pkg/database/queries/find_key_by_id.sql new file mode 100644 index 0000000000..f4b3c1cbc0 --- /dev/null +++ b/go/pkg/database/queries/find_key_by_id.sql @@ -0,0 +1,3 @@ +-- name: FindRatelimitOverrideByIdentifier :one +SELECT * FROM `ratelimit_overrides` +WHERE identifier = sqlc.arg(identifier); diff --git a/go/pkg/database/queries/get_key_by_hash.sql b/go/pkg/database/queries/find_ratelimit_override_by_identifier.sql similarity index 64% rename from go/pkg/database/queries/get_key_by_hash.sql rename to go/pkg/database/queries/find_ratelimit_override_by_identifier.sql index 51af943986..8b395d5955 100644 --- a/go/pkg/database/queries/get_key_by_hash.sql +++ b/go/pkg/database/queries/find_ratelimit_override_by_identifier.sql @@ -1,3 +1,3 @@ --- name: GetKeyByHash :one +-- name: FindKeyByHash :one SELECT * FROM `keys` WHERE hash = sqlc.arg(hash); diff --git a/go/pkg/database/ratelimit_override_find_by_identifier.go b/go/pkg/database/ratelimit_override_find_by_identifier.go new file mode 100644 index 0000000000..2cd9ab7dbb --- /dev/null +++ b/go/pkg/database/ratelimit_override_find_by_identifier.go @@ -0,0 +1,33 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + + "errors" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (entities.RatelimitOverride, error) { + + model, err := db.read().FindRatelimitOverrideByIdentifier(ctx, identifier) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.RatelimitOverride{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", fmt.Sprintf("An override for %s does not exist.", identifier)), + ) + } + return entities.RatelimitOverride{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + e, err := transform.RatelimitOverrideModelToEntity(model) + if err != nil { + return entities.RatelimitOverride{}, fault.Wrap(err, fault.WithDesc("cannot transform model to entity", "")) + } + return e, nil +} diff --git a/go/pkg/database/ratelimit_override_insert.go b/go/pkg/database/ratelimit_override_insert.go new file mode 100644 index 0000000000..c47df12daa --- /dev/null +++ b/go/pkg/database/ratelimit_override_insert.go @@ -0,0 +1,32 @@ +package database + +import ( + "context" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) InsertRatelimitOverride(ctx context.Context, override entities.RatelimitOverride) error { + + p, err := transform.RatelimitOverrideEntityToInsertParams(override) + if err != nil { + + return fault.Wrap(err, + fault.WithDesc( + "failed transforming", + "The override configuration can not get serialized.", + ), + ) + } + + err = db.write().InsertOverride(ctx, p) + if err != nil { + + return fault.Wrap(err, + fault.WithDesc("failed inserting", ""), + ) + } + return nil +} diff --git a/go/pkg/database/transform/key.go b/go/pkg/database/transform/key.go new file mode 100644 index 0000000000..db633dee5d --- /dev/null +++ b/go/pkg/database/transform/key.go @@ -0,0 +1,65 @@ +package transform + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" +) + +func KeyModelToEntity(m gen.Key) (entities.Key, error) { + + key := entities.Key{ + ID: m.ID, + KeySpaceID: m.KeyAuthID, + WorkspaceID: m.WorkspaceID, + Hash: m.Hash, + Start: m.Start, + CreatedAt: m.CreatedAt, + ForWorkspaceID: "", + Name: "", + Enabled: m.Enabled, + IdentityID: "", + Meta: map[string]any{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + Environment: "", + Expires: time.Time{}, + } + + if m.Name.Valid { + key.Name = m.Name.String + } + + if m.Meta.Valid { + err := json.Unmarshal([]byte(m.Meta.String), &key.Meta) + if err != nil { + return entities.Key{}, fmt.Errorf("uanble to unmarshal meta: %w", err) + } + } + if m.Expires.Valid { + key.Expires = m.Expires.Time + } + + if m.ForWorkspaceID.Valid { + key.ForWorkspaceID = m.ForWorkspaceID.String + } + if m.IdentityID.Valid { + key.IdentityID = m.IdentityID.String + } + + if m.UpdatedAtM.Valid { + key.UpdatedAt = time.UnixMilli(m.UpdatedAtM.Int64) + } + + if m.DeletedAtM.Valid { + key.DeletedAt = time.UnixMilli(m.DeletedAtM.Int64) + } + if m.Environment.Valid { + key.Environment = m.Environment.String + } + + return key, nil +} diff --git a/go/pkg/database/transform/ratelimit_override.go b/go/pkg/database/transform/ratelimit_override.go new file mode 100644 index 0000000000..0856e86e50 --- /dev/null +++ b/go/pkg/database/transform/ratelimit_override.go @@ -0,0 +1,75 @@ +package transform + +import ( + "database/sql" + "time" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" +) + +func RatelimitOverrideModelToEntity(m gen.RatelimitOverride) (entities.RatelimitOverride, error) { + + e := entities.RatelimitOverride{ + ID: m.ID, + WorkspaceID: m.WorkspaceID, + NamespaceID: m.NamespaceID, + Identifier: m.Identifier, + Limit: m.Limit, + Duration: time.Millisecond * time.Duration(m.Duration), + CreatedAt: m.CreatedAt, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + if m.UpdatedAt.Valid { + e.UpdatedAt = m.UpdatedAt.Time + } + if m.DeletedAt.Valid { + e.DeletedAt = m.DeletedAt.Time + } + + return e, nil +} + +func RatelimitOverrideEntityToModel(e entities.RatelimitOverride) (gen.RatelimitOverride, error) { + m := gen.RatelimitOverride{ + ID: e.ID, + WorkspaceID: e.WorkspaceID, + NamespaceID: e.NamespaceID, + Identifier: e.Identifier, + Limit: e.Limit, + // nolint:gosec // an int32 still gives us > 24 days of duration + Duration: int32(e.Duration.Milliseconds()), + Async: sql.NullBool{ + Bool: false, + Valid: false, + }, + CreatedAt: e.CreatedAt, + UpdatedAt: sql.NullTime{ + Time: e.UpdatedAt, + Valid: !e.UpdatedAt.IsZero(), + }, + Sharding: gen.NullRatelimitOverridesSharding{ + RatelimitOverridesSharding: "edge", + Valid: false, + }, + DeletedAt: sql.NullTime{ + Time: e.DeletedAt, + Valid: !e.DeletedAt.IsZero(), + }, + } + return m, nil +} + +func RatelimitOverrideEntityToInsertParams(e entities.RatelimitOverride) (gen.InsertOverrideParams, error) { + p := gen.InsertOverrideParams{ + ID: e.ID, + WorkspaceID: e.WorkspaceID, + NamespaceID: e.NamespaceID, + Identifier: e.Identifier, + Limit: e.Limit, + // nolint:gosec // an int32 still gives us > 24 days of duration + Duration: int32(e.Duration.Milliseconds()), + } + return p, nil +} diff --git a/go/pkg/entities/key.go b/go/pkg/entities/key.go index c6fa711e95..11b1b3f15a 100644 --- a/go/pkg/entities/key.go +++ b/go/pkg/entities/key.go @@ -4,48 +4,51 @@ import "time" // Key represents an API key in the system type Key struct { - // ID is the unique identifier for the key - ID string + // ID is the unique identifier for the key + ID string - // KeySpaceID represents the key authorization space this key belongs to - KeySpaceID string + // KeySpaceID represents the key authorization space this key belongs to + KeySpaceID string - // WorkspaceID is the ID of the workspace that owns this key - WorkspaceID string + // WorkspaceID is the ID of the workspace that owns this key + WorkspaceID string - // Hash is the secure hash of the key used for verification - Hash string + // Hash is the secure hash of the key used for verification + Hash string - // Start is the prefix of the key shown to users for identification - Start string + // Start is the prefix of the key shown to users for identification + Start string - // ForWorkspaceID is used only for internal keys to indicate which workspace the key is for - // This is primarily used for managing the Unkey app itself and is not used for user keys - ForWorkspaceID string + // ForWorkspaceID is used only for internal keys to indicate which workspace the key is for + // This is primarily used for managing the Unkey app itself and is not used for user keys + ForWorkspaceID string - // Name is an optional human-readable identifier for the key - Name string + // Name is an optional human-readable identifier for the key + Name string - // IdentityID links this key to a specific identity in the system - IdentityID string + // IdentityID links this key to a specific identity in the system + IdentityID string - // Meta contains arbitrary metadata associated with the key as key-value pairs - Meta map[string]any + // Meta contains arbitrary metadata associated with the key as key-value pairs + Meta map[string]any - // CreatedAt is the timestamp when the key was created - CreatedAt time.Time + // CreatedAt is the timestamp when the key was created + CreatedAt time.Time - // UpdatedAt is the timestamp when the key was last modified - UpdatedAt time.Time + // UpdatedAt is the timestamp when the key was last modified + UpdatedAt time.Time - // DeletedAt indicates when the key was revoked - // A zero time value means the key is not deleted. - DeletedAt time.Time + // DeletedAt indicates when the key was revoked + // A zero time value means the key is not deleted. + DeletedAt time.Time - // Enabled indicates whether the key is currently active (true) or disabled (false) - // Keys are enabled by default. - Enabled bool + // Enabled indicates whether the key is currently active (true) or disabled (false) + // Keys are enabled by default. + Enabled bool - // Environment is an optional flag used to segment keys (e.g., "test" vs "production") - // This is a user-defined value with no system-level restrictions - Environment string + // Environment is an optional flag used to segment keys (e.g., "test" vs "production") + // This is a user-defined value with no system-level restrictions + Environment string + + Expires time.Time +} diff --git a/go/pkg/entities/ratelimit_override.go b/go/pkg/entities/ratelimit_override.go new file mode 100644 index 0000000000..b29a7064c4 --- /dev/null +++ b/go/pkg/entities/ratelimit_override.go @@ -0,0 +1,17 @@ +package entities + +import ( + "time" +) + +type RatelimitOverride struct { + ID string + WorkspaceID string + NamespaceID string + Identifier string + Limit int32 + Duration time.Duration + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time +} diff --git a/go/pkg/fault/tag.go b/go/pkg/fault/tag.go index 1ee3ad0939..800e8a2410 100644 --- a/go/pkg/fault/tag.go +++ b/go/pkg/fault/tag.go @@ -12,6 +12,13 @@ const ( // This ensures all errors have at least a basic classification, making error // handling more predictable. UNTAGGED Tag = "UNTAGGED" + + // An object was not found in the system. + NOT_FOUND Tag = "NOT_FOUND" + + DATABASE_ERROR Tag = "DATABASE_ERROR" + + INTERNAL_SERVER_ERROR Tag = "INTERNAL_SERVER_ERROR" ) // GetTag examines an error and its chain of wrapped errors to find the first diff --git a/go/pkg/uid/uid.go b/go/pkg/uid/uid.go index 1b4a4f5771..dc84e23899 100644 --- a/go/pkg/uid/uid.go +++ b/go/pkg/uid/uid.go @@ -9,24 +9,25 @@ import ( type Prefix string const ( - RequestPrefix Prefix = "req" - NodePrefix Prefix = "node" + RequestPrefix Prefix = "req" + NodePrefix Prefix = "node" + RatelimitOverridePrefix Prefix = "rlor" ) // New Returns a new random base58 encoded uuid. -func New(prefix string) string { +func New(prefix Prefix) string { id := ksuid.New().String() if prefix != "" { - return strings.Join([]string{prefix, id}, "_") + return strings.Join([]string{string(prefix), id}, "_") } else { return id } } func Node() string { - return New(string(NodePrefix)) + return New(NodePrefix) } func Request() string { - return New(string(RequestPrefix)) + return New(RequestPrefix) } diff --git a/go/pkg/zen/errors.go b/go/pkg/zen/errors.go deleted file mode 100644 index a6fcec4812..0000000000 --- a/go/pkg/zen/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -package zen - -import "github.com/unkeyed/unkey/go/pkg/fault" - -// Error tags for common error scenarios. -var ( - NotFoundError = fault.Tag("NOT_FOUND_ERROR") - - // DatabaseError represents errors that occur during database operations. - // This error tag is used when database operations fail, such as connection - // issues, query failures, or data integrity problems. - DatabaseError = fault.Tag("DATABASE_ERROR") -) diff --git a/go/pkg/zen/middleware_errors.go b/go/pkg/zen/middleware_errors.go index 55c848aa3e..cbc63ed39e 100644 --- a/go/pkg/zen/middleware_errors.go +++ b/go/pkg/zen/middleware_errors.go @@ -14,7 +14,7 @@ func WithErrorHandling() Middleware { } switch fault.GetTag(err) { - case NotFoundError: + case fault.NOT_FOUND: return s.JSON(404, api.NotFoundError{ Title: "Not Found", Type: "https://unkey.com/docs/errors/not_found", @@ -24,11 +24,14 @@ func WithErrorHandling() Middleware { Instance: nil, }) - case DatabaseError: - // ... + case fault.DATABASE_ERROR: + break // fall through to default 500 case fault.UNTAGGED: break // fall through to default 500 + + case fault.INTERNAL_SERVER_ERROR: + break } return s.JSON(500, api.InternalServerError{ diff --git a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go index af7a5b54c6..adc5c64c6a 100644 --- a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go +++ b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go @@ -2,10 +2,12 @@ package v2RatelimitLimit import ( "net/http" + "time" "github.com/unkeyed/unkey/go/api" - "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" "github.com/unkeyed/unkey/go/pkg/fault" + "github.com/unkeyed/unkey/go/pkg/uid" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -14,22 +16,37 @@ type Response = api.V2RatelimitSetOverrideResponseBody func New(svc *zen.Services) zen.Route { return zen.NewRoute("POST", "/v2/ratelimit.setOverride", func(s *zen.Session) error { - err := svc.Database.InsertOverride(s.Context(), gen.InsertOverrideParams{ - ID: "", + + // nolint:exhaustruct + req := Request{} + err := s.BindBody(&req) + if err != nil { + return fault.Wrap(err, + fault.WithTag(fault.INTERNAL_SERVER_ERROR), + fault.WithDesc("invalid request body", "The request body is invalid."), + ) + } + + overrideID := uid.New(uid.RatelimitOverridePrefix) + err = svc.Database.InsertRatelimitOverride(s.Context(), entities.RatelimitOverride{ + ID: overrideID, WorkspaceID: "", NamespaceID: "", Identifier: "", Limit: 0, Duration: 0, + CreatedAt: time.Now(), + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, }) if err != nil { return fault.Wrap(err, - fault.WithTag(zen.DatabaseError), + fault.WithTag(fault.DATABASE_ERROR), fault.WithDesc("database failed", "The database is unavailable."), ) } return s.JSON(http.StatusOK, Response{ - OverrideId: "", + OverrideId: overrideID, }) }) } From 1f148bd41f3293c6b2b5425854b82f4388cf24e5 Mon Sep 17 00:00:00 2001 From: chronark Date: Tue, 28 Jan 2025 14:32:04 +0100 Subject: [PATCH 4/9] feat: lots of boilerplate db code --- go/.golangci.yaml | 5 + go/api/gen.go | 3 + go/api/openapi.json | 76 +++++-- go/go.mod | 42 +++- go/go.sum | 89 ++++++++ go/pkg/cache/cache.go | 202 ++++++++++++++++++ go/pkg/cache/cache_test.go | 115 ++++++++++ go/pkg/cache/entry.go | 16 ++ go/pkg/cache/interface.go | 41 ++++ go/pkg/cache/middleware.go | 3 + go/pkg/cache/middleware/metrics.go | 68 ++++++ go/pkg/cache/middleware/tracing.go | 84 ++++++++ go/pkg/cache/noop.go | 28 +++ go/pkg/cache/util.go | 33 +++ go/pkg/database/database.go | 5 + go/pkg/database/gen/get_key_by_hash.sql.go | 52 ----- ...by_hash.sql.go => key_find_by_hash.sql.go} | 2 +- ...key_by_id.sql.go => key_find_by_id.sql.go} | 2 +- go/pkg/database/gen/querier.go | 90 ++++++++ .../gen/ratelimit_namespace_delete.sql.go | 26 +++ .../gen/ratelimit_namespace_find_by_id.sql.go | 33 +++ .../ratelimit_namespace_find_by_name.sql.go | 40 ++++ .../gen/ratelimit_namespace_insert.sql.go | 50 +++++ .../gen/ratelimit_override_delete.sql.go | 28 +++ ...elimit_override_find_by_identifier.sql.go} | 2 +- ...ql.go => ratelimit_override_insert.sql.go} | 2 +- .../gen/ratelimit_override_update.sql.go | 46 ++++ .../database/gen/workspace_find_by_id.sql.go | 44 ++++ .../database/gen/workspace_hard_delete.sql.go | 26 +++ go/pkg/database/gen/workspace_insert.sql.go | 70 ++++++ .../database/gen/workspace_soft_delete.sql.go | 28 +++ .../gen/workspace_update_enabled.sql.go | 31 +++ .../database/gen/workspace_update_plan.sql.go | 31 +++ go/pkg/database/interface.go | 16 +- ...d_key_by_hash.sql => key_find_by_hash.sql} | 0 ...{find_key_by_id.sql => key_find_by_id.sql} | 0 .../queries/ratelimit_namespace_delete.sql | 4 + .../ratelimit_namespace_find_by_id.sql | 3 + .../ratelimit_namespace_find_by_name.sql | 4 + .../queries/ratelimit_override_delete.sql | 5 + ...ratelimit_override_find_by_identifier.sql} | 0 ...ride.sql => ratelimit_override_insert.sql} | 0 .../queries/ratelimit_override_update.sql | 8 + .../database/queries/workspace_find_by_id.sql | 3 + .../queries/workspace_hard_delete.sql | 4 + go/pkg/database/queries/workspace_insert.sql | 23 ++ .../queries/workspace_soft_delete.sql | 5 + .../queries/workspace_update_enabled.sql | 5 + .../queries/workspace_update_plan.sql | 5 + go/pkg/database/ratelimit_namespace_delete.go | 29 +++ .../ratelimit_namespace_find_by_id.go | 32 +++ .../ratelimit_namespace_find_by_name.go | 36 ++++ go/pkg/database/ratelimit_namespace_insert.go | 22 ++ go/pkg/database/ratelimit_override_delete.go | 29 +++ go/pkg/database/ratelimit_override_insert.go | 22 +- go/pkg/database/ratelimit_override_update.go | 42 ++++ go/pkg/database/sqlc.json | 1 + .../database/transform/ratelimit_namespace.go | 37 ++++ .../database/transform/ratelimit_override.go | 37 ++-- go/pkg/database/transform/workspace.go | 73 +++++++ go/pkg/database/workspace_delete.go | 57 +++++ go/pkg/database/workspace_find_by_id.go | 32 +++ go/pkg/database/workspace_insert.go | 27 +++ go/pkg/database/workspace_update.go | 68 ++++++ go/pkg/entities/ratelimit_namespace.go | 12 ++ go/pkg/entities/ratelimit_override.go | 1 + go/pkg/entities/workspace.go | 28 +++ go/pkg/fault/tag.go | 2 + go/pkg/logging/noop.go | 31 +++ go/pkg/repeat/every.go | 16 ++ go/pkg/testutil/containers.go | 62 ++++++ go/pkg/testutil/http.go | 94 ++++++++ go/pkg/tracing/axiom.go | 30 +++ go/pkg/tracing/schema.go | 7 + go/pkg/tracing/trace.go | 24 +++ go/pkg/tracing/util.go | 14 ++ go/pkg/uid/uid.go | 5 + go/pkg/zen/middleware_errors.go | 16 +- .../v2_ratelimit_set_override/handler.go | 3 +- .../v2_ratelimit_set_override/happy_test.go | 61 ++++++ go/pkg/zen/server.go | 7 + 81 files changed, 2342 insertions(+), 113 deletions(-) create mode 100644 go/pkg/cache/cache.go create mode 100644 go/pkg/cache/cache_test.go create mode 100644 go/pkg/cache/entry.go create mode 100644 go/pkg/cache/interface.go create mode 100644 go/pkg/cache/middleware.go create mode 100644 go/pkg/cache/middleware/metrics.go create mode 100644 go/pkg/cache/middleware/tracing.go create mode 100644 go/pkg/cache/noop.go create mode 100644 go/pkg/cache/util.go delete mode 100644 go/pkg/database/gen/get_key_by_hash.sql.go rename go/pkg/database/gen/{find_key_by_hash.sql.go => key_find_by_hash.sql.go} (97%) rename go/pkg/database/gen/{find_key_by_id.sql.go => key_find_by_id.sql.go} (97%) create mode 100644 go/pkg/database/gen/ratelimit_namespace_delete.sql.go create mode 100644 go/pkg/database/gen/ratelimit_namespace_find_by_id.sql.go create mode 100644 go/pkg/database/gen/ratelimit_namespace_find_by_name.sql.go create mode 100644 go/pkg/database/gen/ratelimit_namespace_insert.sql.go create mode 100644 go/pkg/database/gen/ratelimit_override_delete.sql.go rename go/pkg/database/gen/{find_ratelimit_override_by_identifier.sql.go => ratelimit_override_find_by_identifier.sql.go} (96%) rename go/pkg/database/gen/{insert_override.sql.go => ratelimit_override_insert.sql.go} (97%) create mode 100644 go/pkg/database/gen/ratelimit_override_update.sql.go create mode 100644 go/pkg/database/gen/workspace_find_by_id.sql.go create mode 100644 go/pkg/database/gen/workspace_hard_delete.sql.go create mode 100644 go/pkg/database/gen/workspace_insert.sql.go create mode 100644 go/pkg/database/gen/workspace_soft_delete.sql.go create mode 100644 go/pkg/database/gen/workspace_update_enabled.sql.go create mode 100644 go/pkg/database/gen/workspace_update_plan.sql.go rename go/pkg/database/queries/{find_key_by_hash.sql => key_find_by_hash.sql} (100%) rename go/pkg/database/queries/{find_key_by_id.sql => key_find_by_id.sql} (100%) create mode 100644 go/pkg/database/queries/ratelimit_namespace_delete.sql create mode 100644 go/pkg/database/queries/ratelimit_namespace_find_by_id.sql create mode 100644 go/pkg/database/queries/ratelimit_namespace_find_by_name.sql create mode 100644 go/pkg/database/queries/ratelimit_override_delete.sql rename go/pkg/database/queries/{find_ratelimit_override_by_identifier.sql => ratelimit_override_find_by_identifier.sql} (100%) rename go/pkg/database/queries/{insert_override.sql => ratelimit_override_insert.sql} (100%) create mode 100644 go/pkg/database/queries/ratelimit_override_update.sql create mode 100644 go/pkg/database/queries/workspace_find_by_id.sql create mode 100644 go/pkg/database/queries/workspace_hard_delete.sql create mode 100644 go/pkg/database/queries/workspace_insert.sql create mode 100644 go/pkg/database/queries/workspace_soft_delete.sql create mode 100644 go/pkg/database/queries/workspace_update_enabled.sql create mode 100644 go/pkg/database/queries/workspace_update_plan.sql create mode 100644 go/pkg/database/ratelimit_namespace_delete.go create mode 100644 go/pkg/database/ratelimit_namespace_find_by_id.go create mode 100644 go/pkg/database/ratelimit_namespace_find_by_name.go create mode 100644 go/pkg/database/ratelimit_namespace_insert.go create mode 100644 go/pkg/database/ratelimit_override_delete.go create mode 100644 go/pkg/database/ratelimit_override_update.go create mode 100644 go/pkg/database/transform/ratelimit_namespace.go create mode 100644 go/pkg/database/transform/workspace.go create mode 100644 go/pkg/database/workspace_delete.go create mode 100644 go/pkg/database/workspace_find_by_id.go create mode 100644 go/pkg/database/workspace_insert.go create mode 100644 go/pkg/database/workspace_update.go create mode 100644 go/pkg/entities/ratelimit_namespace.go create mode 100644 go/pkg/entities/workspace.go create mode 100644 go/pkg/logging/noop.go create mode 100644 go/pkg/repeat/every.go create mode 100644 go/pkg/testutil/containers.go create mode 100644 go/pkg/testutil/http.go create mode 100644 go/pkg/tracing/axiom.go create mode 100644 go/pkg/tracing/schema.go create mode 100644 go/pkg/tracing/trace.go create mode 100644 go/pkg/tracing/util.go create mode 100644 go/pkg/zen/routes/v2_ratelimit_set_override/happy_test.go diff --git a/go/.golangci.yaml b/go/.golangci.yaml index 6f6ade4081..13443bfd56 100644 --- a/go/.golangci.yaml +++ b/go/.golangci.yaml @@ -188,6 +188,11 @@ linters-settings: packages: - github.com/jmoiron/sqlx + wrapcheck: + extra-ignore-sigs: + - fault.New( + ignoreSigRegexps: + - mw\.next\..* sloglint: # Enforce not using global loggers. # Values: diff --git a/go/api/gen.go b/go/api/gen.go index 2bedbf76b8..8ffa88aff5 100644 --- a/go/api/gen.go +++ b/go/api/gen.go @@ -54,6 +54,9 @@ type InternalServerError = BaseError // NotFoundError defines model for NotFoundError. type NotFoundError = BaseError +// PreconditionFailedError defines model for PreconditionFailedError. +type PreconditionFailedError = BaseError + // V2LivenessResponseBody defines model for V2LivenessResponseBody. type V2LivenessResponseBody struct { // Message Whether we're alive or not diff --git a/go/api/openapi.json b/go/api/openapi.json index ce8db7bf5c..5a3ce315d2 100644 --- a/go/api/openapi.json +++ b/go/api/openapi.json @@ -51,11 +51,23 @@ } }, "type": "object", - "required": ["requestId", "detail", "status", "title", "type"] + "required": [ + "requestId", + "detail", + "status", + "title", + "type" + ] }, "NotFoundError": { "$ref": "#/components/schemas/BaseError" }, + "ForbiddenError": { + "$ref": "#/components/schemas/BaseError" + }, + "PreconditionFailedError": { + "$ref": "#/components/schemas/BaseError" + }, "BadRequestError": { "allOf": [ { @@ -68,10 +80,14 @@ "items": { "$ref": "#/components/schemas/ValidationError" }, - "type": ["array"] + "type": [ + "array" + ] } }, - "required": ["errors"] + "required": [ + "errors" + ] } ] }, @@ -95,7 +111,10 @@ } }, "type": "object", - "required": ["message", "location"] + "required": [ + "message", + "location" + ] }, "V2LivenessResponseBody": { "additionalProperties": false, @@ -106,7 +125,9 @@ "type": "string" } }, - "required": ["message"], + "required": [ + "message" + ], "type": "object" }, "V2RatelimitSetOverrideRequestBody": { @@ -136,7 +157,11 @@ "type": "integer" } }, - "required": ["identifier", "limit", "duration"], + "required": [ + "identifier", + "limit", + "duration" + ], "type": "object" }, "V2RatelimitSetOverrideResponseBody": { @@ -147,7 +172,9 @@ "type": "string" } }, - "required": ["overrideId"], + "required": [ + "overrideId" + ], "type": "object" }, "V2RatelimitLimitRequestBody": { @@ -174,7 +201,11 @@ "type": "integer" } }, - "required": ["identifier", "limit", "duration"], + "required": [ + "identifier", + "limit", + "duration" + ], "type": "object" }, "V2RatelimitLimitResponseBody": { @@ -200,7 +231,12 @@ "type": "boolean" } }, - "required": ["limit", "remaining", "reset", "success"], + "required": [ + "limit", + "remaining", + "reset", + "success" + ], "type": "object" } } @@ -281,7 +317,9 @@ "description": "Error" } }, - "tags": ["ratelimit"] + "tags": [ + "ratelimit" + ] } }, "/v2/ratelimit.setOverride": { @@ -339,7 +377,9 @@ "description": "Error" } }, - "tags": ["ratelimit"] + "tags": [ + "ratelimit" + ] } }, "/v2/liveness": { @@ -357,6 +397,16 @@ }, "description": "OK" }, + "412": { + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/PreconditionFailedError" + } + } + }, + "description": "Internal Server Error" + }, "500": { "content": { "application/problem+json": { @@ -369,7 +419,9 @@ } }, "summary": "Liveness check", - "tags": ["liveness"] + "tags": [ + "liveness" + ] } } } diff --git a/go/go.mod b/go/go.mod index 26da1b1c5f..8f47553a23 100644 --- a/go/go.mod +++ b/go/go.mod @@ -4,11 +4,14 @@ go 1.23.4 require ( github.com/ClickHouse/clickhouse-go/v2 v2.28.1 + github.com/axiomhq/axiom-go v0.20.2 github.com/btcsuite/btcutil v1.0.2 github.com/danielgtaylor/huma v1.14.2 github.com/google/uuid v1.6.0 github.com/lmittmann/tint v1.0.6 + github.com/maypok86/otter v1.2.2 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 + github.com/ory/dockertest/v3 v3.11.0 github.com/pb33f/libopenapi v0.16.5 github.com/pb33f/libopenapi-validator v0.1.0 github.com/segmentio/ksuid v1.0.4 @@ -17,34 +20,55 @@ require ( github.com/unkeyed/unkey/apps/agent v0.0.0-20250124105149-f6d38cdef22e github.com/urfave/cli/v2 v2.27.4 github.com/xeipuuv/gojsonschema v1.2.0 + go.opentelemetry.io/otel v1.31.0 + go.opentelemetry.io/otel/trace v1.31.0 ) require ( cel.dev/expr v0.18.0 // indirect + dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/ClickHouse/ch-go v0.62.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Southclaws/fault v0.8.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/continuity v0.4.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cubicdaiya/gonp v1.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/docker/cli v27.2.0+incompatible // indirect + github.com/docker/docker v27.2.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dolthub/maphash v0.1.0 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gammazero/deque v0.2.1 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/google/cel-go v0.22.1 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -55,12 +79,18 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.36.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.1.13 // indirect github.com/paulmach/orb v0.11.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pganalyze/pg_query_go/v5 v5.1.0 // indirect @@ -77,11 +107,13 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/riza-io/grpc-go v0.2.0 // indirect + github.com/rs/zerolog v1.33.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -94,8 +126,12 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/go/go.sum b/go/go.sum index 6bc12d9fef..0b3aeb6cd2 100644 --- a/go/go.sum +++ b/go/go.sum @@ -49,9 +49,13 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/ch-go v0.62.0 h1:eXH0hytXeCEEZHgMvOX9IiW7wqBb4w1MJMp9rArbkrc= @@ -60,6 +64,10 @@ github.com/ClickHouse/clickhouse-go/v2 v2.28.1 h1:tpdOxWZlZ4IYiFWpIteU57JVdWVbSI github.com/ClickHouse/clickhouse-go/v2 v2.28.1/go.mod h1:0U915l9qynE508ehh3ea9+UMGc7gZlAV+9W6pUZd7kk= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Jeffail/gabs/v2 v2.6.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Southclaws/fault v0.8.1 h1:mgqqdC6kUBQ6ExMALZ0nNaDfNJD5h2+wq3se5mAyX+8= github.com/Southclaws/fault v0.8.1/go.mod h1:VUVkAWutC59SL16s6FTqf3I6I2z77RmnaW5XRz4bLOE= @@ -79,6 +87,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/axiomhq/axiom-go v0.20.2 h1:RKelFJr8Pei0xIoBaVteGGvn2pkaaMLrWiHLWu8d0Mc= +github.com/axiomhq/axiom-go v0.20.2/go.mod h1:TWHIoBDv/IJNKgyo2EQeOwH4svi+cTePSihPVWZC1/8= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -100,6 +110,8 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -123,11 +135,16 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= +github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= github.com/danielgtaylor/casing v0.0.0-20210126043903-4e55e6373ac3/go.mod h1:eFdYmNxcuLDrRNW0efVoxSaApmvGXfHZ9k2CT/RSUF0= @@ -138,6 +155,16 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/cli v27.2.0+incompatible h1:yHD1QEB1/0vr5eBNpu8tncu8gWxg8EydFPOSKHzXSMM= +github.com/docker/cli v27.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= +github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= @@ -162,12 +189,16 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -183,6 +214,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -201,9 +233,12 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -258,6 +293,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -282,6 +319,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -294,6 +333,8 @@ github.com/graphql-go/graphql v0.7.9/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKd github.com/graphql-go/graphql v0.8.0/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ= github.com/graphql-go/handler v0.2.3/go.mod h1:leLF6RpV5uZMN1CdImAxuiayrYYhOk33bZciaUGaXeU= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -370,7 +411,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc= github.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= @@ -384,15 +429,21 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maypok86/otter v1.2.2 h1:jJi0y8ruR/ZcKmJ4FbQj3QQTqKwV+LNrSOo2S1zbF5M= +github.com/maypok86/otter v1.2.2/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= @@ -401,6 +452,10 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -435,7 +490,15 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs= +github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= +github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= @@ -500,6 +563,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -518,6 +584,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= @@ -550,7 +618,15 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= @@ -596,8 +672,14 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= @@ -607,6 +689,8 @@ go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -832,8 +916,11 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1097,6 +1184,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go/pkg/cache/cache.go b/go/pkg/cache/cache.go new file mode 100644 index 0000000000..979df07958 --- /dev/null +++ b/go/pkg/cache/cache.go @@ -0,0 +1,202 @@ +package cache + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "time" + + "github.com/maypok86/otter" + "github.com/unkeyed/unkey/go/pkg/clock" + "github.com/unkeyed/unkey/go/pkg/fault" + "github.com/unkeyed/unkey/go/pkg/logging" + "github.com/unkeyed/unkey/go/pkg/tracing" + "go.opentelemetry.io/otel/attribute" +) + +type cache[T any] struct { + otter otter.Cache[string, swrEntry[T]] + fresh time.Duration + stale time.Duration + refreshFromOrigin func(ctx context.Context, identifier string) (data T, ok bool) + // If a key is stale, its identifier will be put into this channel and a goroutine refreshes it in the background + refreshC chan string + logger logging.Logger + resource string + clock clock.Clock +} + +type Config[T any] struct { + // How long the data is considered fresh + // Subsequent requests in this time will try to use the cache + Fresh time.Duration + + // Subsequent requests that are not fresh but within the stale time will return cached data but also trigger + // fetching from the origin server + Stale time.Duration + + // A handler that will be called to refetch data from the origin when necessary + RefreshFromOrigin func(ctx context.Context, identifier string) (data T, ok bool) + + Logger logging.Logger + + // Start evicting the least recently used entry when the cache grows to MaxSize + MaxSize int + + Resource string + + Clock clock.Clock +} + +func New[T any](config Config[T]) (*cache[T], error) { + + builder, err := otter.NewBuilder[string, swrEntry[T]](config.MaxSize) + if err != nil { + return nil, fault.Wrap(err, fault.WithDesc("failed to create otter builder", "")) + } + + otter, err := builder.CollectStats().Cost(func(key string, value swrEntry[T]) uint32 { + return 1 + }).WithTTL(time.Hour).Build() + if err != nil { + return nil, fault.Wrap(err, fault.WithDesc("failed to create otter cache", "")) + } + + c := &cache[T]{ + otter: otter, + fresh: config.Fresh, + stale: config.Stale, + refreshFromOrigin: config.RefreshFromOrigin, + refreshC: make(chan string, 1000), + logger: config.Logger, + resource: config.Resource, + clock: config.Clock, + } + + go c.runRefreshing() + + return c, nil + +} + +func (c cache[T]) Get(ctx context.Context, key string) (value T, hit CacheHit) { + + e, ok := c.otter.Get(key) + if !ok { + // This hack is necessary because you can not return nil as T + var t T + return t, Miss + } + + now := c.clock.Now() + + if now.Before(e.Fresh) { + + return e.Value, e.Hit + + } + if now.Before(e.Stale) { + c.refreshC <- key + + return e.Value, e.Hit + } + + c.otter.Delete(key) + + var t T + return t, Miss + +} + +func (c cache[T]) SetNull(ctx context.Context, key string) { + c.set(ctx, key) +} + +func (c cache[T]) Set(ctx context.Context, key string, value T) { + c.set(ctx, key, value) +} +func (c cache[T]) set(_ context.Context, key string, value ...T) { + now := c.clock.Now() + + e := swrEntry[T]{ + Value: value[0], + Fresh: now.Add(c.fresh), + Stale: now.Add(c.stale), + Hit: Null, + } + if len(value) > 0 { + e.Value = value[0] + e.Hit = Hit + } else { + e.Hit = Miss + } + c.otter.Set(key, e) + +} + +func (c cache[T]) Remove(ctx context.Context, key string) { + + c.otter.Delete(key) + +} + +func (c cache[T]) Dump(ctx context.Context) ([]byte, error) { + data := make(map[string]swrEntry[T]) + + c.otter.Range(func(key string, entry swrEntry[T]) bool { + data[key] = entry + return true + }) + + b, err := json.Marshal(data) + + if err != nil { + return nil, fault.Wrap(err, fault.WithDesc("failed to marshal cache data", "")) + } + return b, nil + +} + +func (c cache[T]) Restore(ctx context.Context, b []byte) error { + + data := make(map[string]swrEntry[T]) + err := json.Unmarshal(b, &data) + if err != nil { + return fmt.Errorf("failed to unmarshal cache data: %w", err) + } + now := c.clock.Now() + for key, entry := range data { + if now.Before(entry.Fresh) { + c.Set(ctx, key, entry.Value) + } else if now.Before(entry.Stale) { + c.refreshC <- key + } + // If the entry is older than, we don't restore it + } + return nil +} + +func (c cache[T]) Clear(ctx context.Context) { + c.otter.Clear() +} + +func (c cache[T]) runRefreshing() { + for { + ctx := context.Background() + identifier := <-c.refreshC + + ctx, span := tracing.Start(ctx, tracing.NewSpanName(fmt.Sprintf("cache.%s", c.resource), "refresh")) + span.SetAttributes(attribute.String("identifier", identifier)) + t, ok := c.refreshFromOrigin(ctx, identifier) + if !ok { + span.AddEvent("identifier not found in origin") + c.logger.Warn(ctx, "origin couldn't find data", slog.String("identifier", identifier)) + span.End() + continue + } + c.Set(ctx, identifier, t) + span.End() + } + +} diff --git a/go/pkg/cache/cache_test.go b/go/pkg/cache/cache_test.go new file mode 100644 index 0000000000..51d78c55e5 --- /dev/null +++ b/go/pkg/cache/cache_test.go @@ -0,0 +1,115 @@ +package cache_test + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/unkeyed/unkey/go/pkg/cache" + "github.com/unkeyed/unkey/go/pkg/clock" + "github.com/unkeyed/unkey/go/pkg/logging" +) + +func TestWriteRead(t *testing.T) { + + c, err := cache.New[string](cache.Config[string]{ + MaxSize: 10_000, + + Fresh: time.Minute, + Stale: time.Minute * 5, + RefreshFromOrigin: func(ctx context.Context, id string) (string, bool) { + return "hello", true + }, + Logger: logging.NewNoop(), + Resource: "test", Clock: clock.New(), + }) + require.NoError(t, err) + c.Set(context.Background(), "key", "value") + value, hit := c.Get(context.Background(), "key") + require.Equal(t, cache.Hit, hit) + require.Equal(t, "value", value) +} + +func TestEviction(t *testing.T) { + + clk := clock.NewTestClock() + c, err := cache.New[string](cache.Config[string]{ + MaxSize: 10_000, + + Fresh: time.Second, + Stale: time.Second, + RefreshFromOrigin: func(ctx context.Context, id string) (string, bool) { + return "hello", true + }, + Logger: logging.NewNoop(), + Resource: "test", + Clock: clk, + }) + require.NoError(t, err) + + c.Set(context.Background(), "key", "value") + clk.Tick(2 * time.Second) + _, hit := c.Get(context.Background(), "key") + require.Equal(t, cache.Miss, hit) +} + +func TestRefresh(t *testing.T) { + + clk := clock.NewTestClock() + + // count how many times we refreshed from origin + refreshedFromOrigin := atomic.Int32{} + + c, err := cache.New[string](cache.Config[string]{ + MaxSize: 10_000, + + Fresh: time.Second * 2, + Stale: time.Minute * 5, + RefreshFromOrigin: func(ctx context.Context, id string) (string, bool) { + refreshedFromOrigin.Add(1) + return "hello", true + }, + Logger: logging.NewNoop(), + Resource: "test", + Clock: clk, + }) + require.NoError(t, err) + + c.Set(context.Background(), "key", "value") + clk.Tick(time.Second) + + for i := 0; i < 10; i++ { + _, hit := c.Get(context.Background(), "key") + require.Equal(t, cache.Hit, hit) + clk.Tick(time.Second) + } + + clk.Tick(5 * time.Second) + + require.Equal(t, int32(5), refreshedFromOrigin.Load()) + +} + +func TestNull(t *testing.T) { + t.Skip() + + c, err := cache.New[string](cache.Config[string]{ + MaxSize: 10_000, + Fresh: time.Second * 1, + Stale: time.Minute * 5, + Logger: logging.NewNoop(), + RefreshFromOrigin: nil, + Resource: "test", + Clock: clock.New(), + }) + require.NoError(t, err) + + c.SetNull(context.Background(), "key") + + _, hit := c.Get(context.Background(), "key") + require.Equal(t, cache.Null, hit) + +} diff --git a/go/pkg/cache/entry.go b/go/pkg/cache/entry.go new file mode 100644 index 0000000000..2577bebe9e --- /dev/null +++ b/go/pkg/cache/entry.go @@ -0,0 +1,16 @@ +package cache + +import ( + "time" +) + +type swrEntry[T any] struct { + Value T `json:"value"` + + Hit CacheHit `json:"hit"` + // Before this time the entry is considered fresh and vaid + Fresh time.Time `json:"fresh"` + // Before this time, the entry should be revalidated + // After this time, the entry must be discarded + Stale time.Time `json:"stale"` +} diff --git a/go/pkg/cache/interface.go b/go/pkg/cache/interface.go new file mode 100644 index 0000000000..3bdfc2b287 --- /dev/null +++ b/go/pkg/cache/interface.go @@ -0,0 +1,41 @@ +package cache + +import ( + "context" +) + +type Cache[T any] interface { + // Get returns the value for the given key. + // If the key is not found, found will be false. + Get(ctx context.Context, key string) (value T, hit CacheHit) + + // Sets the value for the given key. + Set(ctx context.Context, key string, value T) + + // Sets the given key to null, indicating that the value does not exist in the origin. + SetNull(ctx context.Context, key string) + + // Removes the key from the cache. + Remove(ctx context.Context, key string) + + // Dump returns a serialized representation of the cache. + Dump(ctx context.Context) ([]byte, error) + + // Restore restores the cache from a serialized representation. + Restore(ctx context.Context, data []byte) error + + // Clear removes all entries from the cache. + Clear(ctx context.Context) +} + +type CacheHit int + +const ( + Null CacheHit = iota + // The entry was in the cache and can be used + Hit + // The entry was not in the cache + Miss + // The entry did not exist in the origin + +) diff --git a/go/pkg/cache/middleware.go b/go/pkg/cache/middleware.go new file mode 100644 index 0000000000..971d7b1c1d --- /dev/null +++ b/go/pkg/cache/middleware.go @@ -0,0 +1,3 @@ +package cache + +type Middleware[T any] func(Cache[T]) Cache[T] diff --git a/go/pkg/cache/middleware/metrics.go b/go/pkg/cache/middleware/metrics.go new file mode 100644 index 0000000000..6440c778af --- /dev/null +++ b/go/pkg/cache/middleware/metrics.go @@ -0,0 +1,68 @@ +package middleware + +import ( + "context" + "time" + + "github.com/unkeyed/unkey/apps/agent/pkg/cache" + "github.com/unkeyed/unkey/apps/agent/pkg/metrics" + "github.com/unkeyed/unkey/apps/agent/pkg/prometheus" +) + +type metricsMiddleware[T any] struct { + next cache.Cache[T] + metrics metrics.Metrics + resource string + tier string +} + +func WithMetrics[T any](c cache.Cache[T], m metrics.Metrics, resource string, tier string) cache.Cache[T] { + return &metricsMiddleware[T]{next: c, metrics: m, resource: resource, tier: tier} +} + +func (mw *metricsMiddleware[T]) Get(ctx context.Context, key string) (T, cache.CacheHit) { + start := time.Now() + value, hit := mw.next.Get(ctx, key) + + labels := map[string]string{ + "key": key, + "resource": mw.resource, + "tier": mw.tier, + } + + if hit == cache.Miss { + prometheus.CacheMisses.With(labels).Inc() + } else { + prometheus.CacheHits.With(labels).Inc() + } + prometheus.CacheLatency.With(labels).Observe(time.Since(start).Seconds()) + + return value, hit +} +func (mw *metricsMiddleware[T]) Set(ctx context.Context, key string, value T) { + mw.next.Set(ctx, key, value) + +} +func (mw *metricsMiddleware[T]) SetNull(ctx context.Context, key string) { + mw.next.SetNull(ctx, key) + +} +func (mw *metricsMiddleware[T]) Remove(ctx context.Context, key string) { + + mw.next.Remove(ctx, key) + +} + +func (mw *metricsMiddleware[T]) Dump(ctx context.Context) ([]byte, error) { + // nolint:wrapcheck + return mw.next.Dump(ctx) +} + +func (mw *metricsMiddleware[T]) Restore(ctx context.Context, data []byte) error { + // nolint:wrapcheck + return mw.next.Restore(ctx, data) +} + +func (mw *metricsMiddleware[T]) Clear(ctx context.Context) { + mw.next.Clear(ctx) +} diff --git a/go/pkg/cache/middleware/tracing.go b/go/pkg/cache/middleware/tracing.go new file mode 100644 index 0000000000..f108f5a155 --- /dev/null +++ b/go/pkg/cache/middleware/tracing.go @@ -0,0 +1,84 @@ +package middleware + +import ( + "context" + + "github.com/unkeyed/unkey/go/pkg/cache" + "github.com/unkeyed/unkey/go/pkg/tracing" + "go.opentelemetry.io/otel/attribute" +) + +type tracingMiddleware[T any] struct { + next cache.Cache[T] +} + +func WithTracing[T any](c cache.Cache[T]) cache.Cache[T] { + return &tracingMiddleware[T]{next: c} +} + +func (mw *tracingMiddleware[T]) Get(ctx context.Context, key string) (T, cache.CacheHit) { + ctx, span := tracing.Start(ctx, "cache.Get") + defer span.End() + span.SetAttributes(attribute.String("key", key)) + + value, hit := mw.next.Get(ctx, key) + span.SetAttributes( + attribute.Bool("hit", hit != cache.Miss), + ) + return value, hit +} +func (mw *tracingMiddleware[T]) Set(ctx context.Context, key string, value T) { + ctx, span := tracing.Start(ctx, "cache.Set") + defer span.End() + span.SetAttributes(attribute.String("key", key)) + + mw.next.Set(ctx, key, value) + +} +func (mw *tracingMiddleware[T]) SetNull(ctx context.Context, key string) { + ctx, span := tracing.Start(ctx, "cache.SetNull") + defer span.End() + + span.SetAttributes(attribute.String("key", key)) + mw.next.SetNull(ctx, key) + +} +func (mw *tracingMiddleware[T]) Remove(ctx context.Context, key string) { + ctx, span := tracing.Start(ctx, "cache.Remove") + defer span.End() + span.SetAttributes(attribute.String("key", key)) + + mw.next.Remove(ctx, key) + +} + +func (mw *tracingMiddleware[T]) Dump(ctx context.Context) ([]byte, error) { + ctx, span := tracing.Start(ctx, "cache.Dump") + defer span.End() + + b, err := mw.next.Dump(ctx) + if err != nil { + tracing.RecordError(span, err) + } + // nolint:wrapcheck + return b, err +} + +func (mw *tracingMiddleware[T]) Restore(ctx context.Context, data []byte) error { + ctx, span := tracing.Start(ctx, "cache.Restore") + defer span.End() + + err := mw.next.Restore(ctx, data) + if err != nil { + tracing.RecordError(span, err) + } + // nolint:wrapcheck + return err +} + +func (mw *tracingMiddleware[T]) Clear(ctx context.Context) { + ctx, span := tracing.Start(ctx, "cache.Clear") + defer span.End() + + mw.next.Clear(ctx) +} diff --git a/go/pkg/cache/noop.go b/go/pkg/cache/noop.go new file mode 100644 index 0000000000..8e3e94c1bd --- /dev/null +++ b/go/pkg/cache/noop.go @@ -0,0 +1,28 @@ +package cache + +import ( + "context" +) + +type noopCache[T any] struct{} + +func (c *noopCache[T]) Get(ctx context.Context, key string) (value T, hit CacheHit) { + var t T + return t, Miss +} +func (c *noopCache[T]) Set(ctx context.Context, key string, value T) {} +func (c *noopCache[T]) SetNull(ctx context.Context, key string) {} + +func (c *noopCache[T]) Remove(ctx context.Context, key string) {} + +func (c *noopCache[T]) Dump(ctx context.Context) ([]byte, error) { + return []byte{}, nil +} +func (c *noopCache[T]) Restore(ctx context.Context, data []byte) error { + return nil +} +func (c *noopCache[T]) Clear(ctx context.Context) {} + +func NewNoopCache[T any]() Cache[T] { + return &noopCache[T]{} +} diff --git a/go/pkg/cache/util.go b/go/pkg/cache/util.go new file mode 100644 index 0000000000..3f703d6755 --- /dev/null +++ b/go/pkg/cache/util.go @@ -0,0 +1,33 @@ +package cache + +import ( + "context" +) + +// withCache builds a pullthrough cache function to wrap a database call. +// Example: +// api, found, err := withCache(s.apiCache, s.db.FindApiByKeyAuthId)(ctx, key.KeyAuthId) +func WithCache[T any](c Cache[T], loadFromDatabase func(ctx context.Context, identifier string) (T, bool, error)) func(ctx context.Context, identifier string) (T, bool, error) { + return func(ctx context.Context, identifier string) (T, bool, error) { + value, hit := c.Get(ctx, identifier) + + if hit == Hit { + return value, true, nil + } + if hit == Null { + return value, false, nil + } + + value, found, err := loadFromDatabase(ctx, identifier) + if err != nil { + return value, false, err + } + if found { + c.Set(ctx, identifier, value) + return value, true, nil + } else { + c.SetNull(ctx, identifier) + return value, false, nil + } + } +} diff --git a/go/pkg/database/database.go b/go/pkg/database/database.go index bb513a02dd..08530d7ee0 100644 --- a/go/pkg/database/database.go +++ b/go/pkg/database/database.go @@ -5,6 +5,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/database/gen" "github.com/unkeyed/unkey/go/pkg/fault" + "github.com/unkeyed/unkey/go/pkg/logging" ) type Config struct { @@ -14,6 +15,8 @@ type Config struct { // The readonly replica will be used for most read queries. // If omitted, the primary is used. ReadOnlyDSN string + + Logger logging.Logger } type replica struct { @@ -24,6 +27,7 @@ type replica struct { type database struct { writeReplica *replica readReplica *replica + logger logging.Logger } func New(config Config, middlewares ...Middleware) (Database, error) { @@ -56,6 +60,7 @@ func New(config Config, middlewares ...Middleware) (Database, error) { var wrapped Database = &database{ writeReplica: writeReplica, readReplica: readReplica, + logger: config.Logger, } for _, mw := range middlewares { diff --git a/go/pkg/database/gen/get_key_by_hash.sql.go b/go/pkg/database/gen/get_key_by_hash.sql.go deleted file mode 100644 index 98a19a8a2b..0000000000 --- a/go/pkg/database/gen/get_key_by_hash.sql.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: get_key_by_hash.sql - -package gen - -import ( - "context" -) - -const getKeyByHash = `-- name: GetKeyByHash :one -SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM ` + "`" + `keys` + "`" + ` -WHERE hash = ? -` - -// GetKeyByHash -// -// SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` -// WHERE hash = ? -func (q *Queries) GetKeyByHash(ctx context.Context, hash string) (Key, error) { - row := q.db.QueryRowContext(ctx, getKeyByHash, hash) - var i Key - err := row.Scan( - &i.ID, - &i.KeyAuthID, - &i.Hash, - &i.Start, - &i.WorkspaceID, - &i.ForWorkspaceID, - &i.Name, - &i.OwnerID, - &i.IdentityID, - &i.Meta, - &i.CreatedAt, - &i.Expires, - &i.CreatedAtM, - &i.UpdatedAtM, - &i.DeletedAtM, - &i.DeletedAt, - &i.RefillDay, - &i.RefillAmount, - &i.LastRefillAt, - &i.Enabled, - &i.RemainingRequests, - &i.RatelimitAsync, - &i.RatelimitLimit, - &i.RatelimitDuration, - &i.Environment, - ) - return i, err -} diff --git a/go/pkg/database/gen/find_key_by_hash.sql.go b/go/pkg/database/gen/key_find_by_hash.sql.go similarity index 97% rename from go/pkg/database/gen/find_key_by_hash.sql.go rename to go/pkg/database/gen/key_find_by_hash.sql.go index 84023de1a2..caaf243587 100644 --- a/go/pkg/database/gen/find_key_by_hash.sql.go +++ b/go/pkg/database/gen/key_find_by_hash.sql.go @@ -1,7 +1,7 @@ // Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.27.0 -// source: find_key_by_hash.sql +// source: key_find_by_hash.sql package gen diff --git a/go/pkg/database/gen/find_key_by_id.sql.go b/go/pkg/database/gen/key_find_by_id.sql.go similarity index 97% rename from go/pkg/database/gen/find_key_by_id.sql.go rename to go/pkg/database/gen/key_find_by_id.sql.go index 8ca4174fa2..918c8a8bc5 100644 --- a/go/pkg/database/gen/find_key_by_id.sql.go +++ b/go/pkg/database/gen/key_find_by_id.sql.go @@ -1,7 +1,7 @@ // Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.27.0 -// source: find_key_by_id.sql +// source: key_find_by_id.sql package gen diff --git a/go/pkg/database/gen/querier.go b/go/pkg/database/gen/querier.go index edd9f78666..6eeb1b2af0 100644 --- a/go/pkg/database/gen/querier.go +++ b/go/pkg/database/gen/querier.go @@ -6,9 +6,23 @@ package gen import ( "context" + "database/sql" ) type Querier interface { + //DeleteRatelimitNamespace + // + // UPDATE `ratelimit_namespaces` + // SET deleted_at = NOW() + // WHERE id = ? + DeleteRatelimitNamespace(ctx context.Context, id string) (sql.Result, error) + //DeleteRatelimitOverride + // + // UPDATE `ratelimit_overrides` + // SET + // deleted_at = NOW() + // WHERE id = ? + DeleteRatelimitOverride(ctx context.Context, id string) (sql.Result, error) //FindKeyByHash // // SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` @@ -19,11 +33,33 @@ type Querier interface { // SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, created_at, expires, created_at_m, updated_at_m, deleted_at_m, deleted_at, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` // WHERE id = ? FindKeyByID(ctx context.Context, id string) (Key, error) + //FindRatelimitNamespaceByID + // + // SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM `ratelimit_namespaces` + // WHERE id = ? + FindRatelimitNamespaceByID(ctx context.Context, id string) (RatelimitNamespace, error) + //FindRatelimitNamespaceByName + // + // SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM `ratelimit_namespaces` + // WHERE name = ? + // AND workspace_id = ? + FindRatelimitNamespaceByName(ctx context.Context, arg FindRatelimitNamespaceByNameParams) (RatelimitNamespace, error) //FindRatelimitOverrideByIdentifier // // SELECT id, workspace_id, namespace_id, identifier, `limit`, duration, async, sharding, created_at, updated_at, deleted_at FROM `ratelimit_overrides` // WHERE identifier = ? FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (RatelimitOverride, error) + //FindWorkspaceByID + // + // SELECT id, tenant_id, name, created_at, deleted_at, plan, stripe_customer_id, stripe_subscription_id, trial_ends, beta_features, features, plan_locked_until, plan_downgrade_request, plan_changed, subscriptions, enabled, delete_protection FROM `workspaces` + // WHERE id = ? + FindWorkspaceByID(ctx context.Context, id string) (Workspace, error) + //HardDeleteWorkspace + // + // DELETE FROM `workspaces` + // WHERE id = ? + // AND delete_protection = false + HardDeleteWorkspace(ctx context.Context, id string) (sql.Result, error) //InsertOverride // // INSERT INTO @@ -49,6 +85,60 @@ type Querier interface { // now() // ) InsertOverride(ctx context.Context, arg InsertOverrideParams) error + //InsertWorkspace + // + // INSERT INTO `workspaces` ( + // id, + // tenant_id, + // name, + // created_at, + // plan, + // beta_features, + // features, + // enabled, + // delete_protection + // ) + // VALUES ( + // ?, + // ?, + // ?, + // NOW(), + // 'free', + // '{}', + // '{}', + // true, + // true + // ) + InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) error + //SoftDeleteWorkspace + // + // UPDATE `workspaces` + // SET deleted_at = NOW() + // WHERE id = ? + // AND delete_protection = false + SoftDeleteWorkspace(ctx context.Context, id string) (sql.Result, error) + //UpdateRatelimitOverride + // + // UPDATE `ratelimit_overrides` + // SET + // `limit` = ?, + // duration = ?, + // async = ?, + // updated_at = NOW() + // WHERE id = ? + UpdateRatelimitOverride(ctx context.Context, arg UpdateRatelimitOverrideParams) (sql.Result, error) + //UpdateWorkspaceEnabled + // + // UPDATE `workspaces` + // SET enabled = ? + // WHERE id = ? + UpdateWorkspaceEnabled(ctx context.Context, arg UpdateWorkspaceEnabledParams) (sql.Result, error) + //UpdateWorkspacePlan + // + // UPDATE `workspaces` + // SET plan = ? + // WHERE id = ? + UpdateWorkspacePlan(ctx context.Context, arg UpdateWorkspacePlanParams) (sql.Result, error) } var _ Querier = (*Queries)(nil) diff --git a/go/pkg/database/gen/ratelimit_namespace_delete.sql.go b/go/pkg/database/gen/ratelimit_namespace_delete.sql.go new file mode 100644 index 0000000000..0da1e61184 --- /dev/null +++ b/go/pkg/database/gen/ratelimit_namespace_delete.sql.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_namespace_delete.sql + +package gen + +import ( + "context" + "database/sql" +) + +const deleteRatelimitNamespace = `-- name: DeleteRatelimitNamespace :execresult +UPDATE ` + "`" + `ratelimit_namespaces` + "`" + ` +SET deleted_at = NOW() +WHERE id = ? +` + +// DeleteRatelimitNamespace +// +// UPDATE `ratelimit_namespaces` +// SET deleted_at = NOW() +// WHERE id = ? +func (q *Queries) DeleteRatelimitNamespace(ctx context.Context, id string) (sql.Result, error) { + return q.db.ExecContext(ctx, deleteRatelimitNamespace, id) +} diff --git a/go/pkg/database/gen/ratelimit_namespace_find_by_id.sql.go b/go/pkg/database/gen/ratelimit_namespace_find_by_id.sql.go new file mode 100644 index 0000000000..fa9776fc4e --- /dev/null +++ b/go/pkg/database/gen/ratelimit_namespace_find_by_id.sql.go @@ -0,0 +1,33 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_namespace_find_by_id.sql + +package gen + +import ( + "context" +) + +const findRatelimitNamespaceByID = `-- name: FindRatelimitNamespaceByID :one +SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM ` + "`" + `ratelimit_namespaces` + "`" + ` +WHERE id = ? +` + +// FindRatelimitNamespaceByID +// +// SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM `ratelimit_namespaces` +// WHERE id = ? +func (q *Queries) FindRatelimitNamespaceByID(ctx context.Context, id string) (RatelimitNamespace, error) { + row := q.db.QueryRowContext(ctx, findRatelimitNamespaceByID, id) + var i RatelimitNamespace + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.Name, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + ) + return i, err +} diff --git a/go/pkg/database/gen/ratelimit_namespace_find_by_name.sql.go b/go/pkg/database/gen/ratelimit_namespace_find_by_name.sql.go new file mode 100644 index 0000000000..0c18df92ae --- /dev/null +++ b/go/pkg/database/gen/ratelimit_namespace_find_by_name.sql.go @@ -0,0 +1,40 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_namespace_find_by_name.sql + +package gen + +import ( + "context" +) + +const findRatelimitNamespaceByName = `-- name: FindRatelimitNamespaceByName :one +SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM ` + "`" + `ratelimit_namespaces` + "`" + ` +WHERE name = ? +AND workspace_id = ? +` + +type FindRatelimitNamespaceByNameParams struct { + Name string `db:"name"` + WorkspaceID string `db:"workspace_id"` +} + +// FindRatelimitNamespaceByName +// +// SELECT id, workspace_id, name, created_at, updated_at, deleted_at FROM `ratelimit_namespaces` +// WHERE name = ? +// AND workspace_id = ? +func (q *Queries) FindRatelimitNamespaceByName(ctx context.Context, arg FindRatelimitNamespaceByNameParams) (RatelimitNamespace, error) { + row := q.db.QueryRowContext(ctx, findRatelimitNamespaceByName, arg.Name, arg.WorkspaceID) + var i RatelimitNamespace + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.Name, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + ) + return i, err +} diff --git a/go/pkg/database/gen/ratelimit_namespace_insert.sql.go b/go/pkg/database/gen/ratelimit_namespace_insert.sql.go new file mode 100644 index 0000000000..a053a29763 --- /dev/null +++ b/go/pkg/database/gen/ratelimit_namespace_insert.sql.go @@ -0,0 +1,50 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_namespace_insert.sql + +package gen + +import ( + "context" +) + +const insertRatelimitNamespace = `-- name: InsertRatelimitNamespace :exec +INSERT INTO ` + "`" + `ratelimit_namespaces` + "`" + ` ( + id, + workspace_id, + name, + created_at +) +VALUES ( + ?, + ?, + ?, + NOW() +) +` + +type InsertRatelimitNamespaceParams struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + Name string `db:"name"` +} + +// InsertRatelimitNamespace +// +// INSERT INTO `ratelimit_namespaces` ( +// id, +// workspace_id, +// name, +// created_at +// ) +// VALUES ( +// ?, +// ?, +// ?, +// NOW() +// ) +func (q *Queries) InsertRatelimitNamespace(ctx context.Context, arg InsertRatelimitNamespaceParams) error { + _, err := q.db.ExecContext(ctx, insertRatelimitNamespace, arg.ID, arg.WorkspaceID, arg.Name) + return err +} diff --git a/go/pkg/database/gen/ratelimit_override_delete.sql.go b/go/pkg/database/gen/ratelimit_override_delete.sql.go new file mode 100644 index 0000000000..d8f002389d --- /dev/null +++ b/go/pkg/database/gen/ratelimit_override_delete.sql.go @@ -0,0 +1,28 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_override_delete.sql + +package gen + +import ( + "context" + "database/sql" +) + +const deleteRatelimitOverride = `-- name: DeleteRatelimitOverride :execresult +UPDATE ` + "`" + `ratelimit_overrides` + "`" + ` +SET + deleted_at = NOW() +WHERE id = ? +` + +// DeleteRatelimitOverride +// +// UPDATE `ratelimit_overrides` +// SET +// deleted_at = NOW() +// WHERE id = ? +func (q *Queries) DeleteRatelimitOverride(ctx context.Context, id string) (sql.Result, error) { + return q.db.ExecContext(ctx, deleteRatelimitOverride, id) +} diff --git a/go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go b/go/pkg/database/gen/ratelimit_override_find_by_identifier.sql.go similarity index 96% rename from go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go rename to go/pkg/database/gen/ratelimit_override_find_by_identifier.sql.go index e0cbfc6d23..650bf73382 100644 --- a/go/pkg/database/gen/find_ratelimit_override_by_identifier.sql.go +++ b/go/pkg/database/gen/ratelimit_override_find_by_identifier.sql.go @@ -1,7 +1,7 @@ // Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.27.0 -// source: find_ratelimit_override_by_identifier.sql +// source: ratelimit_override_find_by_identifier.sql package gen diff --git a/go/pkg/database/gen/insert_override.sql.go b/go/pkg/database/gen/ratelimit_override_insert.sql.go similarity index 97% rename from go/pkg/database/gen/insert_override.sql.go rename to go/pkg/database/gen/ratelimit_override_insert.sql.go index 7a87fb8092..49d9277711 100644 --- a/go/pkg/database/gen/insert_override.sql.go +++ b/go/pkg/database/gen/ratelimit_override_insert.sql.go @@ -1,7 +1,7 @@ // Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.27.0 -// source: insert_override.sql +// source: ratelimit_override_insert.sql package gen diff --git a/go/pkg/database/gen/ratelimit_override_update.sql.go b/go/pkg/database/gen/ratelimit_override_update.sql.go new file mode 100644 index 0000000000..381eff64be --- /dev/null +++ b/go/pkg/database/gen/ratelimit_override_update.sql.go @@ -0,0 +1,46 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: ratelimit_override_update.sql + +package gen + +import ( + "context" + "database/sql" +) + +const updateRatelimitOverride = `-- name: UpdateRatelimitOverride :execresult +UPDATE ` + "`" + `ratelimit_overrides` + "`" + ` +SET + ` + "`" + `limit` + "`" + ` = ?, + duration = ?, + async = ?, + updated_at = NOW() +WHERE id = ? +` + +type UpdateRatelimitOverrideParams struct { + Windowlimit int32 `db:"windowlimit"` + Duration int32 `db:"duration"` + Async sql.NullBool `db:"async"` + ID string `db:"id"` +} + +// UpdateRatelimitOverride +// +// UPDATE `ratelimit_overrides` +// SET +// `limit` = ?, +// duration = ?, +// async = ?, +// updated_at = NOW() +// WHERE id = ? +func (q *Queries) UpdateRatelimitOverride(ctx context.Context, arg UpdateRatelimitOverrideParams) (sql.Result, error) { + return q.db.ExecContext(ctx, updateRatelimitOverride, + arg.Windowlimit, + arg.Duration, + arg.Async, + arg.ID, + ) +} diff --git a/go/pkg/database/gen/workspace_find_by_id.sql.go b/go/pkg/database/gen/workspace_find_by_id.sql.go new file mode 100644 index 0000000000..1c03fc5711 --- /dev/null +++ b/go/pkg/database/gen/workspace_find_by_id.sql.go @@ -0,0 +1,44 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_find_by_id.sql + +package gen + +import ( + "context" +) + +const findWorkspaceByID = `-- name: FindWorkspaceByID :one +SELECT id, tenant_id, name, created_at, deleted_at, plan, stripe_customer_id, stripe_subscription_id, trial_ends, beta_features, features, plan_locked_until, plan_downgrade_request, plan_changed, subscriptions, enabled, delete_protection FROM ` + "`" + `workspaces` + "`" + ` +WHERE id = ? +` + +// FindWorkspaceByID +// +// SELECT id, tenant_id, name, created_at, deleted_at, plan, stripe_customer_id, stripe_subscription_id, trial_ends, beta_features, features, plan_locked_until, plan_downgrade_request, plan_changed, subscriptions, enabled, delete_protection FROM `workspaces` +// WHERE id = ? +func (q *Queries) FindWorkspaceByID(ctx context.Context, id string) (Workspace, error) { + row := q.db.QueryRowContext(ctx, findWorkspaceByID, id) + var i Workspace + err := row.Scan( + &i.ID, + &i.TenantID, + &i.Name, + &i.CreatedAt, + &i.DeletedAt, + &i.Plan, + &i.StripeCustomerID, + &i.StripeSubscriptionID, + &i.TrialEnds, + &i.BetaFeatures, + &i.Features, + &i.PlanLockedUntil, + &i.PlanDowngradeRequest, + &i.PlanChanged, + &i.Subscriptions, + &i.Enabled, + &i.DeleteProtection, + ) + return i, err +} diff --git a/go/pkg/database/gen/workspace_hard_delete.sql.go b/go/pkg/database/gen/workspace_hard_delete.sql.go new file mode 100644 index 0000000000..654f5aa608 --- /dev/null +++ b/go/pkg/database/gen/workspace_hard_delete.sql.go @@ -0,0 +1,26 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_hard_delete.sql + +package gen + +import ( + "context" + "database/sql" +) + +const hardDeleteWorkspace = `-- name: HardDeleteWorkspace :execresult +DELETE FROM ` + "`" + `workspaces` + "`" + ` +WHERE id = ? +AND delete_protection = false +` + +// HardDeleteWorkspace +// +// DELETE FROM `workspaces` +// WHERE id = ? +// AND delete_protection = false +func (q *Queries) HardDeleteWorkspace(ctx context.Context, id string) (sql.Result, error) { + return q.db.ExecContext(ctx, hardDeleteWorkspace, id) +} diff --git a/go/pkg/database/gen/workspace_insert.sql.go b/go/pkg/database/gen/workspace_insert.sql.go new file mode 100644 index 0000000000..35e70c8422 --- /dev/null +++ b/go/pkg/database/gen/workspace_insert.sql.go @@ -0,0 +1,70 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_insert.sql + +package gen + +import ( + "context" +) + +const insertWorkspace = `-- name: InsertWorkspace :exec +INSERT INTO ` + "`" + `workspaces` + "`" + ` ( + id, + tenant_id, + name, + created_at, + plan, + beta_features, + features, + enabled, + delete_protection +) +VALUES ( + ?, + ?, + ?, + NOW(), + 'free', + '{}', + '{}', + true, + true +) +` + +type InsertWorkspaceParams struct { + ID string `db:"id"` + TenantID string `db:"tenant_id"` + Name string `db:"name"` +} + +// InsertWorkspace +// +// INSERT INTO `workspaces` ( +// id, +// tenant_id, +// name, +// created_at, +// plan, +// beta_features, +// features, +// enabled, +// delete_protection +// ) +// VALUES ( +// ?, +// ?, +// ?, +// NOW(), +// 'free', +// '{}', +// '{}', +// true, +// true +// ) +func (q *Queries) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) error { + _, err := q.db.ExecContext(ctx, insertWorkspace, arg.ID, arg.TenantID, arg.Name) + return err +} diff --git a/go/pkg/database/gen/workspace_soft_delete.sql.go b/go/pkg/database/gen/workspace_soft_delete.sql.go new file mode 100644 index 0000000000..3ad79f6235 --- /dev/null +++ b/go/pkg/database/gen/workspace_soft_delete.sql.go @@ -0,0 +1,28 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_soft_delete.sql + +package gen + +import ( + "context" + "database/sql" +) + +const softDeleteWorkspace = `-- name: SoftDeleteWorkspace :execresult +UPDATE ` + "`" + `workspaces` + "`" + ` +SET deleted_at = NOW() +WHERE id = ? +AND delete_protection = false +` + +// SoftDeleteWorkspace +// +// UPDATE `workspaces` +// SET deleted_at = NOW() +// WHERE id = ? +// AND delete_protection = false +func (q *Queries) SoftDeleteWorkspace(ctx context.Context, id string) (sql.Result, error) { + return q.db.ExecContext(ctx, softDeleteWorkspace, id) +} diff --git a/go/pkg/database/gen/workspace_update_enabled.sql.go b/go/pkg/database/gen/workspace_update_enabled.sql.go new file mode 100644 index 0000000000..e5ecdd7a3a --- /dev/null +++ b/go/pkg/database/gen/workspace_update_enabled.sql.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_update_enabled.sql + +package gen + +import ( + "context" + "database/sql" +) + +const updateWorkspaceEnabled = `-- name: UpdateWorkspaceEnabled :execresult +UPDATE ` + "`" + `workspaces` + "`" + ` +SET enabled = ? +WHERE id = ? +` + +type UpdateWorkspaceEnabledParams struct { + Enabled bool `db:"enabled"` + ID string `db:"id"` +} + +// UpdateWorkspaceEnabled +// +// UPDATE `workspaces` +// SET enabled = ? +// WHERE id = ? +func (q *Queries) UpdateWorkspaceEnabled(ctx context.Context, arg UpdateWorkspaceEnabledParams) (sql.Result, error) { + return q.db.ExecContext(ctx, updateWorkspaceEnabled, arg.Enabled, arg.ID) +} diff --git a/go/pkg/database/gen/workspace_update_plan.sql.go b/go/pkg/database/gen/workspace_update_plan.sql.go new file mode 100644 index 0000000000..20366def6a --- /dev/null +++ b/go/pkg/database/gen/workspace_update_plan.sql.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: workspace_update_plan.sql + +package gen + +import ( + "context" + "database/sql" +) + +const updateWorkspacePlan = `-- name: UpdateWorkspacePlan :execresult +UPDATE ` + "`" + `workspaces` + "`" + ` +SET plan = ? +WHERE id = ? +` + +type UpdateWorkspacePlanParams struct { + Plan NullWorkspacesPlan `db:"plan"` + ID string `db:"id"` +} + +// UpdateWorkspacePlan +// +// UPDATE `workspaces` +// SET plan = ? +// WHERE id = ? +func (q *Queries) UpdateWorkspacePlan(ctx context.Context, arg UpdateWorkspacePlanParams) (sql.Result, error) { + return q.db.ExecContext(ctx, updateWorkspacePlan, arg.Plan, arg.ID) +} diff --git a/go/pkg/database/interface.go b/go/pkg/database/interface.go index f3d39638e2..87c797ac54 100644 --- a/go/pkg/database/interface.go +++ b/go/pkg/database/interface.go @@ -9,9 +9,13 @@ import ( type Database interface { // Workspace - // InsertWorkspace(ctx context.Context, newWorkspace entities.Workspace) error + InsertWorkspace(ctx context.Context, workspace entities.Workspace) error + FindWorkspaceByID(ctx context.Context, id string) (entities.Workspace, error) + UpdateWorkspacePlan(ctx context.Context, id string, plan entities.WorkspacePlan) error + UpdateWorkspaceEnabled(ctx context.Context, id string, enabled bool) error // UpdateWorkspace(ctx context.Context, workspace entities.Workspace) error // FindWorkspace(ctx context.Context, workspaceId string) (entities.Workspace, bool, error) + DeleteWorkspace(ctx context.Context, id string, hardDelete bool) error // KeyAuth // InsertKeyAuth(ctx context.Context, newKeyAuth entities.KeyAuth) error @@ -35,9 +39,17 @@ type Database interface { // CountKeys(ctx context.Context, keyAuthId string) (int64, error) // ListKeys(ctx context.Context, keyAuthId string, ownerId string, limit int, offset int) ([]entities.Key, error) - // Ratelimit Overrides + // Ratelimit Namespace + InsertRatelimitNamespace(ctx context.Context, namespace entities.RatelimitNamespace) error + FindRatelimitNamespaceByID(ctx context.Context, id string) (entities.RatelimitNamespace, error) + FindRatelimitNamespaceByName(ctx context.Context, workspaceID string, name string) (entities.RatelimitNamespace, error) + DeleteRatelimitNamespace(ctx context.Context, id string) error + + // Ratelimit Override InsertRatelimitOverride(ctx context.Context, ratelimitOverride entities.RatelimitOverride) error FindRatelimitOverrideByIdentifier(ctx context.Context, identifier string) (ratelimitOverride entities.RatelimitOverride, err error) + UpdateRatelimitOverride(ctx context.Context, override entities.RatelimitOverride) error + DeleteRatelimitOverride(ctx context.Context, id string) error // Stuff Close() error diff --git a/go/pkg/database/queries/find_key_by_hash.sql b/go/pkg/database/queries/key_find_by_hash.sql similarity index 100% rename from go/pkg/database/queries/find_key_by_hash.sql rename to go/pkg/database/queries/key_find_by_hash.sql diff --git a/go/pkg/database/queries/find_key_by_id.sql b/go/pkg/database/queries/key_find_by_id.sql similarity index 100% rename from go/pkg/database/queries/find_key_by_id.sql rename to go/pkg/database/queries/key_find_by_id.sql diff --git a/go/pkg/database/queries/ratelimit_namespace_delete.sql b/go/pkg/database/queries/ratelimit_namespace_delete.sql new file mode 100644 index 0000000000..405058d5d6 --- /dev/null +++ b/go/pkg/database/queries/ratelimit_namespace_delete.sql @@ -0,0 +1,4 @@ +-- name: DeleteRatelimitNamespace :execresult +UPDATE `ratelimit_namespaces` +SET deleted_at = NOW() +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/ratelimit_namespace_find_by_id.sql b/go/pkg/database/queries/ratelimit_namespace_find_by_id.sql new file mode 100644 index 0000000000..7792b67b23 --- /dev/null +++ b/go/pkg/database/queries/ratelimit_namespace_find_by_id.sql @@ -0,0 +1,3 @@ +-- name: FindRatelimitNamespaceByID :one +SELECT * FROM `ratelimit_namespaces` +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/ratelimit_namespace_find_by_name.sql b/go/pkg/database/queries/ratelimit_namespace_find_by_name.sql new file mode 100644 index 0000000000..422c9bdae2 --- /dev/null +++ b/go/pkg/database/queries/ratelimit_namespace_find_by_name.sql @@ -0,0 +1,4 @@ +-- name: FindRatelimitNamespaceByName :one +SELECT * FROM `ratelimit_namespaces` +WHERE name = sqlc.arg(name) +AND workspace_id = sqlc.arg(workspace_id); diff --git a/go/pkg/database/queries/ratelimit_override_delete.sql b/go/pkg/database/queries/ratelimit_override_delete.sql new file mode 100644 index 0000000000..58ef9dec7d --- /dev/null +++ b/go/pkg/database/queries/ratelimit_override_delete.sql @@ -0,0 +1,5 @@ +-- name: DeleteRatelimitOverride :execresult +UPDATE `ratelimit_overrides` +SET + deleted_at = NOW() +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/find_ratelimit_override_by_identifier.sql b/go/pkg/database/queries/ratelimit_override_find_by_identifier.sql similarity index 100% rename from go/pkg/database/queries/find_ratelimit_override_by_identifier.sql rename to go/pkg/database/queries/ratelimit_override_find_by_identifier.sql diff --git a/go/pkg/database/queries/insert_override.sql b/go/pkg/database/queries/ratelimit_override_insert.sql similarity index 100% rename from go/pkg/database/queries/insert_override.sql rename to go/pkg/database/queries/ratelimit_override_insert.sql diff --git a/go/pkg/database/queries/ratelimit_override_update.sql b/go/pkg/database/queries/ratelimit_override_update.sql new file mode 100644 index 0000000000..ab27b131cc --- /dev/null +++ b/go/pkg/database/queries/ratelimit_override_update.sql @@ -0,0 +1,8 @@ +-- name: UpdateRatelimitOverride :execresult +UPDATE `ratelimit_overrides` +SET + `limit` = sqlc.arg(windowLimit), + duration = sqlc.arg(duration), + async = sqlc.arg(async), + updated_at = NOW() +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/workspace_find_by_id.sql b/go/pkg/database/queries/workspace_find_by_id.sql new file mode 100644 index 0000000000..82defadbab --- /dev/null +++ b/go/pkg/database/queries/workspace_find_by_id.sql @@ -0,0 +1,3 @@ +-- name: FindWorkspaceByID :one +SELECT * FROM `workspaces` +WHERE id = sqlc.arg(id); diff --git a/go/pkg/database/queries/workspace_hard_delete.sql b/go/pkg/database/queries/workspace_hard_delete.sql new file mode 100644 index 0000000000..642e7ee69d --- /dev/null +++ b/go/pkg/database/queries/workspace_hard_delete.sql @@ -0,0 +1,4 @@ +-- name: HardDeleteWorkspace :execresult +DELETE FROM `workspaces` +WHERE id = sqlc.arg(id) +AND delete_protection = false; diff --git a/go/pkg/database/queries/workspace_insert.sql b/go/pkg/database/queries/workspace_insert.sql new file mode 100644 index 0000000000..6a4c380f42 --- /dev/null +++ b/go/pkg/database/queries/workspace_insert.sql @@ -0,0 +1,23 @@ +-- name: InsertWorkspace :exec +INSERT INTO `workspaces` ( + id, + tenant_id, + name, + created_at, + plan, + beta_features, + features, + enabled, + delete_protection +) +VALUES ( + sqlc.arg(id), + sqlc.arg(tenant_id), + sqlc.arg(name), + NOW(), + 'free', + '{}', + '{}', + true, + true +); diff --git a/go/pkg/database/queries/workspace_soft_delete.sql b/go/pkg/database/queries/workspace_soft_delete.sql new file mode 100644 index 0000000000..c08510a21e --- /dev/null +++ b/go/pkg/database/queries/workspace_soft_delete.sql @@ -0,0 +1,5 @@ +-- name: SoftDeleteWorkspace :execresult +UPDATE `workspaces` +SET deleted_at = NOW() +WHERE id = sqlc.arg(id) +AND delete_protection = false; diff --git a/go/pkg/database/queries/workspace_update_enabled.sql b/go/pkg/database/queries/workspace_update_enabled.sql new file mode 100644 index 0000000000..25fa326613 --- /dev/null +++ b/go/pkg/database/queries/workspace_update_enabled.sql @@ -0,0 +1,5 @@ +-- name: UpdateWorkspaceEnabled :execresult +UPDATE `workspaces` +SET enabled = sqlc.arg(enabled) +WHERE id = sqlc.arg(id) +; diff --git a/go/pkg/database/queries/workspace_update_plan.sql b/go/pkg/database/queries/workspace_update_plan.sql new file mode 100644 index 0000000000..3d2683cd7d --- /dev/null +++ b/go/pkg/database/queries/workspace_update_plan.sql @@ -0,0 +1,5 @@ +-- name: UpdateWorkspacePlan :execresult +UPDATE `workspaces` +SET plan = sqlc.arg(plan) +WHERE id = sqlc.arg(id) +; diff --git a/go/pkg/database/ratelimit_namespace_delete.go b/go/pkg/database/ratelimit_namespace_delete.go new file mode 100644 index 0000000000..07e488cf80 --- /dev/null +++ b/go/pkg/database/ratelimit_namespace_delete.go @@ -0,0 +1,29 @@ +package database + +import ( + "context" + "database/sql" + + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) DeleteRatelimitNamespace(ctx context.Context, id string) error { + result, err := db.write().DeleteRatelimitNamespace(ctx, id) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to delete ratelimit namespace", "")) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to get rows affected", "")) + } + + if rowsAffected == 0 { + return fault.Wrap(sql.ErrNoRows, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("ratelimit namespace not found", ""), + ) + } + + return nil +} diff --git a/go/pkg/database/ratelimit_namespace_find_by_id.go b/go/pkg/database/ratelimit_namespace_find_by_id.go new file mode 100644 index 0000000000..39a2076a33 --- /dev/null +++ b/go/pkg/database/ratelimit_namespace_find_by_id.go @@ -0,0 +1,32 @@ +package database + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindRatelimitNamespaceByID(ctx context.Context, namespaceID string) (entities.RatelimitNamespace, error) { + model, err := db.read().FindRatelimitNamespaceByID(ctx, namespaceID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.RatelimitNamespace{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", fmt.Sprintf("Ratelimit namespace '%s' does not exist.", namespaceID)), + ) + } + return entities.RatelimitNamespace{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + namespace, err := transform.RatelimitNamespaceModelToEntity(model) + if err != nil { + return entities.RatelimitNamespace{}, fault.Wrap(err, + fault.WithDesc("cannot transform namespace model to entity", "")) + } + return namespace, nil +} diff --git a/go/pkg/database/ratelimit_namespace_find_by_name.go b/go/pkg/database/ratelimit_namespace_find_by_name.go new file mode 100644 index 0000000000..5bee21d467 --- /dev/null +++ b/go/pkg/database/ratelimit_namespace_find_by_name.go @@ -0,0 +1,36 @@ +package database + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindRatelimitNamespaceByName(ctx context.Context, workspaceID string, name string) (entities.RatelimitNamespace, error) { + model, err := db.read().FindRatelimitNamespaceByName(ctx, gen.FindRatelimitNamespaceByNameParams{ + WorkspaceID: workspaceID, + Name: name, + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.RatelimitNamespace{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", fmt.Sprintf("Ratelimit namespace '%s' does not exist in workspace %s.", name, workspaceID)), + ) + } + return entities.RatelimitNamespace{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + namespace, err := transform.RatelimitNamespaceModelToEntity(model) + if err != nil { + return entities.RatelimitNamespace{}, fault.Wrap(err, + fault.WithDesc("cannot transform namespace model to entity", "")) + } + return namespace, nil +} diff --git a/go/pkg/database/ratelimit_namespace_insert.go b/go/pkg/database/ratelimit_namespace_insert.go new file mode 100644 index 0000000000..f1edabe142 --- /dev/null +++ b/go/pkg/database/ratelimit_namespace_insert.go @@ -0,0 +1,22 @@ +package database + +import ( + "context" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) InsertRatelimitNamespace(ctx context.Context, namespace entities.RatelimitNamespace) error { + params := transform.RatelimitNamespaceEntityToInsertParams(namespace) + + err := db.write().InsertRatelimitNamespace(ctx, params) + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to insert ratelimit namespace", ""), + ) + } + + return nil +} diff --git a/go/pkg/database/ratelimit_override_delete.go b/go/pkg/database/ratelimit_override_delete.go new file mode 100644 index 0000000000..351d537f49 --- /dev/null +++ b/go/pkg/database/ratelimit_override_delete.go @@ -0,0 +1,29 @@ +package database + +import ( + "context" + "database/sql" + + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) DeleteRatelimitOverride(ctx context.Context, id string) error { + result, err := db.write().DeleteRatelimitOverride(ctx, id) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to delete ratelimit override", "")) + } + + rows, err := result.RowsAffected() + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to get rows affected", "")) + } + + if rows == 0 { + return fault.Wrap(sql.ErrNoRows, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("ratelimit override not found", ""), + ) + } + + return nil +} diff --git a/go/pkg/database/ratelimit_override_insert.go b/go/pkg/database/ratelimit_override_insert.go index c47df12daa..e46bd2a7c6 100644 --- a/go/pkg/database/ratelimit_override_insert.go +++ b/go/pkg/database/ratelimit_override_insert.go @@ -3,25 +3,21 @@ package database import ( "context" - "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/database/gen" "github.com/unkeyed/unkey/go/pkg/entities" "github.com/unkeyed/unkey/go/pkg/fault" ) func (db *database) InsertRatelimitOverride(ctx context.Context, override entities.RatelimitOverride) error { - p, err := transform.RatelimitOverrideEntityToInsertParams(override) - if err != nil { - - return fault.Wrap(err, - fault.WithDesc( - "failed transforming", - "The override configuration can not get serialized.", - ), - ) - } - - err = db.write().InsertOverride(ctx, p) + err := db.write().InsertOverride(ctx, gen.InsertOverrideParams{ + ID: override.ID, + WorkspaceID: override.WorkspaceID, + NamespaceID: override.NamespaceID, + Identifier: override.Identifier, + Limit: override.Limit, + Duration: int32(override.Duration.Milliseconds()), // nolint:gosec + }) if err != nil { return fault.Wrap(err, diff --git a/go/pkg/database/ratelimit_override_update.go b/go/pkg/database/ratelimit_override_update.go new file mode 100644 index 0000000000..7d1cc0fd71 --- /dev/null +++ b/go/pkg/database/ratelimit_override_update.go @@ -0,0 +1,42 @@ +package database + +import ( + "context" + "database/sql" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) UpdateRatelimitOverride(ctx context.Context, e entities.RatelimitOverride) error { + params := gen.UpdateRatelimitOverrideParams{ + ID: e.ID, + Windowlimit: e.Limit, + + Duration: int32(e.Duration.Milliseconds()), // nolint:gosec + Async: sql.NullBool{ + Bool: e.Async, + Valid: true, + }, + } + + result, err := db.write().UpdateRatelimitOverride(ctx, params) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to update ratelimit override", "")) + } + + rows, err := result.RowsAffected() + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to get rows affected", "")) + } + + if rows == 0 { + return fault.Wrap(sql.ErrNoRows, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("ratelimit override not found", ""), + ) + } + + return nil +} diff --git a/go/pkg/database/sqlc.json b/go/pkg/database/sqlc.json index 403ac21c01..41c1801008 100644 --- a/go/pkg/database/sqlc.json +++ b/go/pkg/database/sqlc.json @@ -10,6 +10,7 @@ "package": "gen", "out": "gen", "sql_package": "database/sql", + "query_parameter_limit": 1, "emit_db_tags": true, "emit_interface": true, "emit_sql_as_comment": true diff --git a/go/pkg/database/transform/ratelimit_namespace.go b/go/pkg/database/transform/ratelimit_namespace.go new file mode 100644 index 0000000000..a76d93c366 --- /dev/null +++ b/go/pkg/database/transform/ratelimit_namespace.go @@ -0,0 +1,37 @@ +package transform + +import ( + "time" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" +) + +func RatelimitNamespaceModelToEntity(m gen.RatelimitNamespace) (entities.RatelimitNamespace, error) { + namespace := entities.RatelimitNamespace{ + ID: m.ID, + WorkspaceID: m.WorkspaceID, + Name: m.Name, + CreatedAt: m.CreatedAt, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + } + + if m.UpdatedAt.Valid { + namespace.UpdatedAt = m.UpdatedAt.Time + } + + if m.DeletedAt.Valid { + namespace.DeletedAt = m.DeletedAt.Time + } + + return namespace, nil +} + +func RatelimitNamespaceEntityToInsertParams(e entities.RatelimitNamespace) gen.InsertRatelimitNamespaceParams { + return gen.InsertRatelimitNamespaceParams{ + ID: e.ID, + WorkspaceID: e.WorkspaceID, + Name: e.Name, + } +} diff --git a/go/pkg/database/transform/ratelimit_override.go b/go/pkg/database/transform/ratelimit_override.go index 0856e86e50..4ed42751b3 100644 --- a/go/pkg/database/transform/ratelimit_override.go +++ b/go/pkg/database/transform/ratelimit_override.go @@ -9,21 +9,23 @@ import ( ) func RatelimitOverrideModelToEntity(m gen.RatelimitOverride) (entities.RatelimitOverride, error) { - e := entities.RatelimitOverride{ ID: m.ID, WorkspaceID: m.WorkspaceID, NamespaceID: m.NamespaceID, Identifier: m.Identifier, Limit: m.Limit, - Duration: time.Millisecond * time.Duration(m.Duration), + Duration: time.Duration(m.Duration) * time.Millisecond, + Async: m.Async.Bool, CreatedAt: m.CreatedAt, UpdatedAt: time.Time{}, DeletedAt: time.Time{}, } + if m.UpdatedAt.Valid { e.UpdatedAt = m.UpdatedAt.Time } + if m.DeletedAt.Valid { e.DeletedAt = m.DeletedAt.Time } @@ -31,45 +33,32 @@ func RatelimitOverrideModelToEntity(m gen.RatelimitOverride) (entities.Ratelimit return e, nil } -func RatelimitOverrideEntityToModel(e entities.RatelimitOverride) (gen.RatelimitOverride, error) { +func RatelimitOverrideEntityToModel(e entities.RatelimitOverride) gen.RatelimitOverride { m := gen.RatelimitOverride{ ID: e.ID, WorkspaceID: e.WorkspaceID, NamespaceID: e.NamespaceID, Identifier: e.Identifier, Limit: e.Limit, - // nolint:gosec // an int32 still gives us > 24 days of duration - Duration: int32(e.Duration.Milliseconds()), + Duration: int32(e.Duration.Milliseconds()), // nolint:gosec Async: sql.NullBool{ - Bool: false, - Valid: false, + Bool: e.Async, + Valid: true, }, CreatedAt: e.CreatedAt, UpdatedAt: sql.NullTime{ Time: e.UpdatedAt, Valid: !e.UpdatedAt.IsZero(), }, - Sharding: gen.NullRatelimitOverridesSharding{ - RatelimitOverridesSharding: "edge", - Valid: false, - }, DeletedAt: sql.NullTime{ Time: e.DeletedAt, Valid: !e.DeletedAt.IsZero(), }, + Sharding: gen.NullRatelimitOverridesSharding{ + RatelimitOverridesSharding: gen.RatelimitOverridesSharding("edge"), + Valid: false, + }, } - return m, nil -} -func RatelimitOverrideEntityToInsertParams(e entities.RatelimitOverride) (gen.InsertOverrideParams, error) { - p := gen.InsertOverrideParams{ - ID: e.ID, - WorkspaceID: e.WorkspaceID, - NamespaceID: e.NamespaceID, - Identifier: e.Identifier, - Limit: e.Limit, - // nolint:gosec // an int32 still gives us > 24 days of duration - Duration: int32(e.Duration.Milliseconds()), - } - return p, nil + return m } diff --git a/go/pkg/database/transform/workspace.go b/go/pkg/database/transform/workspace.go new file mode 100644 index 0000000000..7f46747da6 --- /dev/null +++ b/go/pkg/database/transform/workspace.go @@ -0,0 +1,73 @@ +package transform + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" +) + +func WorkspaceModelToEntity(m gen.Workspace) (entities.Workspace, error) { + workspace := entities.Workspace{ + ID: m.ID, + TenantID: m.TenantID, + Name: m.Name, + Enabled: m.Enabled, + DeleteProtection: true, + CreatedAt: time.Time{}, + DeletedAt: time.Time{}, + Plan: entities.WorkspacePlan(m.Plan.WorkspacesPlan), + StripeCustomerID: "", + StripeSubscriptionID: "", + TrialEnds: time.Time{}, + PlanLockedUntil: time.Time{}, + BetaFeatures: map[string]any{}, + Features: map[string]any{}, + } + + if m.CreatedAt.Valid { + workspace.CreatedAt = m.CreatedAt.Time + } + + if m.DeletedAt.Valid { + workspace.DeletedAt = m.DeletedAt.Time + } + + if m.Plan.Valid { + workspace.Plan = entities.WorkspacePlan(m.Plan.WorkspacesPlan) + } else { + workspace.Plan = entities.WorkspacePlanFree + } + + if m.DeleteProtection.Valid { + workspace.DeleteProtection = m.DeleteProtection.Bool + } + + if m.StripeCustomerID.Valid { + workspace.StripeCustomerID = m.StripeCustomerID.String + } + + if m.StripeSubscriptionID.Valid { + workspace.StripeSubscriptionID = m.StripeSubscriptionID.String + } + + if m.TrialEnds.Valid { + workspace.TrialEnds = m.TrialEnds.Time + } + + if m.PlanLockedUntil.Valid { + workspace.PlanLockedUntil = m.PlanLockedUntil.Time + } + + if err := json.Unmarshal(m.BetaFeatures, &workspace.BetaFeatures); err != nil { + return entities.Workspace{}, fmt.Errorf("unable to unmarshal beta features: %w", err) + } + + if err := json.Unmarshal(m.Features, &workspace.Features); err != nil { + return entities.Workspace{}, fmt.Errorf("unable to unmarshal features: %w", err) + } + + return workspace, nil +} diff --git a/go/pkg/database/workspace_delete.go b/go/pkg/database/workspace_delete.go new file mode 100644 index 0000000000..a807100e1e --- /dev/null +++ b/go/pkg/database/workspace_delete.go @@ -0,0 +1,57 @@ +package database + +import ( + "context" + "log/slog" + + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) DeleteWorkspace(ctx context.Context, id string, hardDelete bool) error { + tx, err := db.writeReplica.db.BeginTx(ctx, nil) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to start transaction", "")) + } + defer func() { + rollbackErr := tx.Rollback() + if rollbackErr != nil { + db.logger.Error(ctx, "failed to rollback transaction", slog.String("error", rollbackErr.Error())) + } + }() + qtx := db.write().WithTx(tx) + + // Check protection within the transaction + workspace, err := qtx.FindWorkspaceByID(ctx, id) + if err != nil { + return fault.Wrap(err, + + fault.WithDesc("failed to load workspace", ""), + ) + } + + if workspace.DeleteProtection.Valid && workspace.DeleteProtection.Bool { + return fault.New("unable to delete workspace", + fault.WithTag(fault.PROTECTED_RESOURCE), + fault.WithDesc("workspace is protected", "This workspace has delete protection enabled and cannot be deleted."), + ) + } + if hardDelete { + _, err = qtx.HardDeleteWorkspace(ctx, id) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to hard delete workspace", "")) + } + } else { + _, err = qtx.SoftDeleteWorkspace(ctx, id) + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to soft delete workspace", "")) + } + + } + + err = tx.Commit() + if err != nil { + return fault.Wrap(err, fault.WithDesc("failed to commit transaction", "")) + } + + return nil +} diff --git a/go/pkg/database/workspace_find_by_id.go b/go/pkg/database/workspace_find_by_id.go new file mode 100644 index 0000000000..8f29f7dde6 --- /dev/null +++ b/go/pkg/database/workspace_find_by_id.go @@ -0,0 +1,32 @@ +package database + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/unkeyed/unkey/go/pkg/database/transform" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) FindWorkspaceByID(ctx context.Context, id string) (entities.Workspace, error) { + model, err := db.read().FindWorkspaceByID(ctx, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return entities.Workspace{}, fault.Wrap(err, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("not found", fmt.Sprintf("Workspace with ID %s does not exist.", id)), + ) + } + return entities.Workspace{}, fault.Wrap(err, fault.WithTag(fault.DATABASE_ERROR)) + } + + workspace, err := transform.WorkspaceModelToEntity(model) + if err != nil { + return entities.Workspace{}, fault.Wrap(err, + fault.WithDesc("cannot transform workspace model to entity", "")) + } + return workspace, nil +} diff --git a/go/pkg/database/workspace_insert.go b/go/pkg/database/workspace_insert.go new file mode 100644 index 0000000000..b0b7df3efe --- /dev/null +++ b/go/pkg/database/workspace_insert.go @@ -0,0 +1,27 @@ +package database + +import ( + "context" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) InsertWorkspace(ctx context.Context, workspace entities.Workspace) error { + + params := gen.InsertWorkspaceParams{ + ID: workspace.ID, + TenantID: workspace.TenantID, + Name: workspace.Name, + } + + err := db.write().InsertWorkspace(ctx, params) + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to insert workspace", ""), + ) + } + + return nil +} diff --git a/go/pkg/database/workspace_update.go b/go/pkg/database/workspace_update.go new file mode 100644 index 0000000000..1aa818658f --- /dev/null +++ b/go/pkg/database/workspace_update.go @@ -0,0 +1,68 @@ +package database + +import ( + "context" + "database/sql" + + "github.com/unkeyed/unkey/go/pkg/database/gen" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/fault" +) + +func (db *database) UpdateWorkspacePlan(ctx context.Context, id string, plan entities.WorkspacePlan) error { + result, err := db.write().UpdateWorkspacePlan(ctx, gen.UpdateWorkspacePlanParams{ + ID: id, + Plan: gen.NullWorkspacesPlan{ + WorkspacesPlan: gen.WorkspacesPlan(plan), + Valid: true, + }}) + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to update workspace plan", ""), + ) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to get rows affected", ""), + ) + } + + if rowsAffected == 0 { + return fault.Wrap(sql.ErrNoRows, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("workspace not found", "The workspace you're trying to update doesn't exist or has been deleted."), + ) + } + + return nil +} + +func (db *database) UpdateWorkspaceEnabled(ctx context.Context, id string, enabled bool) error { + result, err := db.write().UpdateWorkspaceEnabled(ctx, gen.UpdateWorkspaceEnabledParams{ + ID: id, + Enabled: enabled, + }) + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to update workspace enabled status", ""), + ) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fault.Wrap(err, + fault.WithDesc("failed to get rows affected", ""), + ) + } + + if rowsAffected == 0 { + return fault.Wrap(sql.ErrNoRows, + fault.WithTag(fault.NOT_FOUND), + fault.WithDesc("workspace not found", "The workspace you're trying to update doesn't exist or has been deleted."), + ) + } + + return nil +} diff --git a/go/pkg/entities/ratelimit_namespace.go b/go/pkg/entities/ratelimit_namespace.go new file mode 100644 index 0000000000..0a8e076093 --- /dev/null +++ b/go/pkg/entities/ratelimit_namespace.go @@ -0,0 +1,12 @@ +package entities + +import "time" + +type RatelimitNamespace struct { + ID string + WorkspaceID string + Name string + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time +} diff --git a/go/pkg/entities/ratelimit_override.go b/go/pkg/entities/ratelimit_override.go index b29a7064c4..e8e316105d 100644 --- a/go/pkg/entities/ratelimit_override.go +++ b/go/pkg/entities/ratelimit_override.go @@ -11,6 +11,7 @@ type RatelimitOverride struct { Identifier string Limit int32 Duration time.Duration + Async bool CreatedAt time.Time UpdatedAt time.Time DeletedAt time.Time diff --git a/go/pkg/entities/workspace.go b/go/pkg/entities/workspace.go new file mode 100644 index 0000000000..a345e25fd0 --- /dev/null +++ b/go/pkg/entities/workspace.go @@ -0,0 +1,28 @@ +package entities + +import "time" + +type WorkspacePlan string + +const ( + WorkspacePlanFree WorkspacePlan = "free" + WorkspacePlanPro WorkspacePlan = "pro" + WorkspacePlanEnterprise WorkspacePlan = "enterprise" +) + +type Workspace struct { + ID string + TenantID string + Name string + CreatedAt time.Time + DeletedAt time.Time + Plan WorkspacePlan + Enabled bool + DeleteProtection bool + BetaFeatures map[string]interface{} + Features map[string]interface{} + StripeCustomerID string + StripeSubscriptionID string + TrialEnds time.Time + PlanLockedUntil time.Time +} diff --git a/go/pkg/fault/tag.go b/go/pkg/fault/tag.go index 800e8a2410..8b2494c38e 100644 --- a/go/pkg/fault/tag.go +++ b/go/pkg/fault/tag.go @@ -19,6 +19,8 @@ const ( DATABASE_ERROR Tag = "DATABASE_ERROR" INTERNAL_SERVER_ERROR Tag = "INTERNAL_SERVER_ERROR" + + PROTECTED_RESOURCE Tag = "PROTECTED_RESOURCE" ) // GetTag examines an error and its chain of wrapped errors to find the first diff --git a/go/pkg/logging/noop.go b/go/pkg/logging/noop.go new file mode 100644 index 0000000000..c09d3087ec --- /dev/null +++ b/go/pkg/logging/noop.go @@ -0,0 +1,31 @@ +package logging + +import ( + "context" + "log/slog" +) + +type noop struct { +} + +func NewNoop() Logger { + return &noop{} +} + +func (l *noop) With(attrs ...slog.Attr) Logger { + + return l + +} + +func (l *noop) Debug(ctx context.Context, message string, attrs ...slog.Attr) { + +} +func (l *noop) Info(ctx context.Context, message string, attrs ...slog.Attr) { + +} +func (l *noop) Warn(ctx context.Context, message string, attrs ...slog.Attr) { + +} +func (l *noop) Error(ctx context.Context, message string, attrs ...slog.Attr) { +} diff --git a/go/pkg/repeat/every.go b/go/pkg/repeat/every.go new file mode 100644 index 0000000000..cea84cf277 --- /dev/null +++ b/go/pkg/repeat/every.go @@ -0,0 +1,16 @@ +package repeat + +import "time" + +// Every runs the given function in a go routine every d duration until the returned function is called. +func Every(d time.Duration, fn func()) func() { + t := time.NewTicker(d) + go func() { + for range t.C { + fn() + } + }() + return func() { + t.Stop() + } +} diff --git a/go/pkg/testutil/containers.go b/go/pkg/testutil/containers.go new file mode 100644 index 0000000000..b5697d9228 --- /dev/null +++ b/go/pkg/testutil/containers.go @@ -0,0 +1,62 @@ +package testutil + +import ( + "database/sql" + "fmt" + "testing" + + _ "github.com/go-sql-driver/mysql" + + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/require" +) + +type Containers struct { + t *testing.T + pool *dockertest.Pool +} + +func NewContainers(t *testing.T) *Containers { + pool, err := dockertest.NewPool("") + require.NoError(t, err) + + err = pool.Client.Ping() + require.NoError(t, err) + + c := &Containers{ + t: t, + pool: pool, + } + + return c +} + +func (c *Containers) RunMySQL() string { + resource, err := c.pool.Run("mysql", "latest", []string{ + "MYSQL_ROOT_PASSWORD=root", + "MYSQL_DATABASE=unkey", + "MYSQL_USER=unkey", + "MYSQL_PASSWORD=password", + }) + require.NoError(c.t, err) + + c.t.Cleanup(func() { + require.NoError(c.t, c.pool.Purge(resource)) + }) + + addr := fmt.Sprintf("unkey:password@(localhost:%s)/unkey", resource.GetPort("3306/tcp")) + + require.NoError(c.t, c.pool.Retry(func() error { + db, err := sql.Open("mysql", addr) + if err != nil { + return fmt.Errorf("unable to open mysql conenction: %w", err) + } + err = db.Ping() + if err != nil { + return fmt.Errorf("unable to ping mysql: %w", err) + } + return nil + })) + + return addr +} diff --git a/go/pkg/testutil/http.go b/go/pkg/testutil/http.go new file mode 100644 index 0000000000..a57b13611f --- /dev/null +++ b/go/pkg/testutil/http.go @@ -0,0 +1,94 @@ +package testutil + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/go/pkg/logging" + "github.com/unkeyed/unkey/go/pkg/zen" +) + +type Harness struct { + t *testing.T + + logger logging.Logger + + srv *zen.Server +} + +func NewHarness(t *testing.T) *Harness { + + logger := logging.NewNoop() + + srv, err := zen.New(zen.Config{ + NodeId: "test", + Logger: logger, + Clickhouse: nil, + }) + require.NoError(t, err) + + h := Harness{ + t: t, + logger: logger, + srv: srv, + } + + return &h +} + +func (h *Harness) Register(route zen.Route) { + + h.srv.RegisterRoute(route) + +} + +// Post is a helper function to make a POST request to the API. +// It will hanndle serializing the request and response objects to and from JSON. +func UnmarshalBody[Body any](t *testing.T, r *httptest.ResponseRecorder, body *Body) { + + err := json.Unmarshal(r.Body.Bytes(), &body) + require.NoError(t, err) + +} + +type TestResponse[TBody any] struct { + Status int + Headers http.Header + Body TBody +} + +func CallRoute[Req any, Res any](h *Harness, route zen.Route, headers http.Header, req Req) TestResponse[Res] { + h.t.Helper() + + rr := httptest.NewRecorder() + + body := new(bytes.Buffer) + err := json.NewEncoder(body).Encode(req) + require.NoError(h.t, err) + + httpReq := httptest.NewRequest(route.Method(), route.Path(), body) + httpReq.Header = headers + if httpReq.Header == nil { + httpReq.Header = http.Header{} + } + if route.Method() == http.MethodPost { + httpReq.Header.Set("Content-Type", "application/json") + } + + h.srv.Mux().ServeHTTP(rr, httpReq) + require.NoError(h.t, err) + + var res Res + err = json.NewDecoder(rr.Body).Decode(&res) + require.NoError(h.t, err) + + return TestResponse[Res]{ + Status: rr.Code, + Headers: rr.Header(), + Body: res, + } +} diff --git a/go/pkg/tracing/axiom.go b/go/pkg/tracing/axiom.go new file mode 100644 index 0000000000..c4e7ca4ed7 --- /dev/null +++ b/go/pkg/tracing/axiom.go @@ -0,0 +1,30 @@ +package tracing + +import ( + "context" + "fmt" + + axiom "github.com/axiomhq/axiom-go/axiom/otel" +) + +type Config struct { + Dataset string + Application string + Version string + AxiomToken string +} + +// Closer is a function that closes the global tracer. +type Closer func() error + +func Init(ctx context.Context, config Config) (Closer, error) { + tp, err := axiom.TracerProvider(ctx, config.Dataset, config.Application, config.Version, axiom.SetNoEnv(), axiom.SetToken(config.AxiomToken)) + if err != nil { + return nil, fmt.Errorf("unable to init tracing: %w", err) + } + globalTracer = tp + + return func() error { + return tp.Shutdown(context.Background()) + }, nil +} diff --git a/go/pkg/tracing/schema.go b/go/pkg/tracing/schema.go new file mode 100644 index 0000000000..5d0674f53b --- /dev/null +++ b/go/pkg/tracing/schema.go @@ -0,0 +1,7 @@ +package tracing + +import "fmt" + +func NewSpanName(pkg string, method string) string { + return fmt.Sprintf("%s.%s", pkg, method) +} diff --git a/go/pkg/tracing/trace.go b/go/pkg/tracing/trace.go new file mode 100644 index 0000000000..4148105ff0 --- /dev/null +++ b/go/pkg/tracing/trace.go @@ -0,0 +1,24 @@ +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +var globalTracer trace.TracerProvider + +func init() { + globalTracer = noop.NewTracerProvider() +} + +func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + // nolint:spancheck // the caller will end the span + return globalTracer.Tracer("main").Start(ctx, name, opts...) + +} + +func GetGlobalTraceProvider() trace.TracerProvider { + return globalTracer +} diff --git a/go/pkg/tracing/util.go b/go/pkg/tracing/util.go new file mode 100644 index 0000000000..484e14c67e --- /dev/null +++ b/go/pkg/tracing/util.go @@ -0,0 +1,14 @@ +package tracing + +import ( + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +// RecordError sets the status of the span to error if the error is not nil. +func RecordError(span trace.Span, err error) { + if err == nil { + return + } + span.SetStatus(codes.Error, err.Error()) +} diff --git a/go/pkg/uid/uid.go b/go/pkg/uid/uid.go index dc84e23899..4991dfab20 100644 --- a/go/pkg/uid/uid.go +++ b/go/pkg/uid/uid.go @@ -12,6 +12,7 @@ const ( RequestPrefix Prefix = "req" NodePrefix Prefix = "node" RatelimitOverridePrefix Prefix = "rlor" + TestPrefix Prefix = "test" ) // New Returns a new random base58 encoded uuid. @@ -31,3 +32,7 @@ func Node() string { func Request() string { return New(RequestPrefix) } + +func Test() string { + return New(TestPrefix) +} diff --git a/go/pkg/zen/middleware_errors.go b/go/pkg/zen/middleware_errors.go index cbc63ed39e..5ba212bba1 100644 --- a/go/pkg/zen/middleware_errors.go +++ b/go/pkg/zen/middleware_errors.go @@ -1,6 +1,8 @@ package zen import ( + "net/http" + "github.com/unkeyed/unkey/go/api" "github.com/unkeyed/unkey/go/pkg/fault" ) @@ -15,7 +17,7 @@ func WithErrorHandling() Middleware { switch fault.GetTag(err) { case fault.NOT_FOUND: - return s.JSON(404, api.NotFoundError{ + return s.JSON(http.StatusNotFound, api.NotFoundError{ Title: "Not Found", Type: "https://unkey.com/docs/errors/not_found", Detail: fault.UserFacingMessage(err), @@ -24,6 +26,16 @@ func WithErrorHandling() Middleware { Instance: nil, }) + case fault.PROTECTED_RESOURCE: + return s.JSON(http.StatusPreconditionFailed, api.PreconditionFailedError{ + Title: "Resource is protected", + Type: "https://unkey.com/docs/errors/deletion_prevented", + Detail: fault.UserFacingMessage(err), + RequestId: s.requestID, + Status: s.responseStatus, + Instance: nil, + }) + case fault.DATABASE_ERROR: break // fall through to default 500 @@ -34,7 +46,7 @@ func WithErrorHandling() Middleware { break } - return s.JSON(500, api.InternalServerError{ + return s.JSON(http.StatusInternalServerError, api.InternalServerError{ Title: "Internal Server Error", Type: "https://unkey.com/docs/errors/internal_server_error", Detail: fault.UserFacingMessage(err), diff --git a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go index adc5c64c6a..9dc77cb32c 100644 --- a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go +++ b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go @@ -1,4 +1,4 @@ -package v2RatelimitLimit +package handler import ( "net/http" @@ -38,6 +38,7 @@ func New(svc *zen.Services) zen.Route { CreatedAt: time.Now(), UpdatedAt: time.Time{}, DeletedAt: time.Time{}, + Async: false, }) if err != nil { return fault.Wrap(err, diff --git a/go/pkg/zen/routes/v2_ratelimit_set_override/happy_test.go b/go/pkg/zen/routes/v2_ratelimit_set_override/happy_test.go new file mode 100644 index 0000000000..70400112f2 --- /dev/null +++ b/go/pkg/zen/routes/v2_ratelimit_set_override/happy_test.go @@ -0,0 +1,61 @@ +package handler_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/unkeyed/unkey/apps/agent/pkg/util" + "github.com/unkeyed/unkey/go/pkg/database" + "github.com/unkeyed/unkey/go/pkg/entities" + "github.com/unkeyed/unkey/go/pkg/logging" + "github.com/unkeyed/unkey/go/pkg/testutil" + "github.com/unkeyed/unkey/go/pkg/uid" + handler "github.com/unkeyed/unkey/go/pkg/zen/routes/v2_ratelimit_set_override" +) + +func TestCreateNewOverride(t *testing.T) { + ctx := context.Background() + h := testutil.NewHarness(t) + + c := testutil.NewContainers(t) + + mysqlAddr := c.RunMySQL() + + db, err := database.New(database.Config{ + PrimaryDSN: mysqlAddr, + ReadOnlyDSN: "", + Logger: logging.NewNoop(), + }) + require.NoError(t, err) + + db.InsertRatelimitOverride(ctx, entities.RatelimitOverride{ + ID: uid.Test(), + WorkspaceID: uid.Test(), + NamespaceID: uid.Test(), + Identifier: "test", + Limit: 10, + Duration: 0, + Async: false, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + DeletedAt: time.Time{}, + }) + + route := handler.New(nil) + + h.Register(route) + + req := handler.Request{ + NamespaceId: util.Pointer(""), + NamespaceName: nil, + Identifier: "", + Limit: 10, + Duration: 1000, + } + res := testutil.CallRoute[handler.Request, handler.Response](h, route, nil, req) + + require.Equal(t, 200, res.Status) + require.NotEqual(t, "", res.Body.OverrideId) +} diff --git a/go/pkg/zen/server.go b/go/pkg/zen/server.go index 7cb7a8afaf..ec4d862a32 100644 --- a/go/pkg/zen/server.go +++ b/go/pkg/zen/server.go @@ -94,6 +94,13 @@ func (s *Server) returnSession(session any) { s.sessions.Put(session) } +// Mux returns the underlying http.ServeMux. +// +// Usually you don't need to use this, but it's here for tests. +func (s *Server) Mux() *http.ServeMux { + return s.mux +} + // Calling this function multiple times will have no effect. func (s *Server) Listen(ctx context.Context, addr string) error { s.mu.Lock() From 7bf307875ce25252fec11960915a34b4813d5f6f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:34:39 +0000 Subject: [PATCH 5/9] [autofix.ci] apply automated fixes --- go/api/openapi.json | 60 +++++++++------------------------------------ 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/go/api/openapi.json b/go/api/openapi.json index 5a3ce315d2..c2fcd762af 100644 --- a/go/api/openapi.json +++ b/go/api/openapi.json @@ -51,13 +51,7 @@ } }, "type": "object", - "required": [ - "requestId", - "detail", - "status", - "title", - "type" - ] + "required": ["requestId", "detail", "status", "title", "type"] }, "NotFoundError": { "$ref": "#/components/schemas/BaseError" @@ -80,14 +74,10 @@ "items": { "$ref": "#/components/schemas/ValidationError" }, - "type": [ - "array" - ] + "type": ["array"] } }, - "required": [ - "errors" - ] + "required": ["errors"] } ] }, @@ -111,10 +101,7 @@ } }, "type": "object", - "required": [ - "message", - "location" - ] + "required": ["message", "location"] }, "V2LivenessResponseBody": { "additionalProperties": false, @@ -125,9 +112,7 @@ "type": "string" } }, - "required": [ - "message" - ], + "required": ["message"], "type": "object" }, "V2RatelimitSetOverrideRequestBody": { @@ -157,11 +142,7 @@ "type": "integer" } }, - "required": [ - "identifier", - "limit", - "duration" - ], + "required": ["identifier", "limit", "duration"], "type": "object" }, "V2RatelimitSetOverrideResponseBody": { @@ -172,9 +153,7 @@ "type": "string" } }, - "required": [ - "overrideId" - ], + "required": ["overrideId"], "type": "object" }, "V2RatelimitLimitRequestBody": { @@ -201,11 +180,7 @@ "type": "integer" } }, - "required": [ - "identifier", - "limit", - "duration" - ], + "required": ["identifier", "limit", "duration"], "type": "object" }, "V2RatelimitLimitResponseBody": { @@ -231,12 +206,7 @@ "type": "boolean" } }, - "required": [ - "limit", - "remaining", - "reset", - "success" - ], + "required": ["limit", "remaining", "reset", "success"], "type": "object" } } @@ -317,9 +287,7 @@ "description": "Error" } }, - "tags": [ - "ratelimit" - ] + "tags": ["ratelimit"] } }, "/v2/ratelimit.setOverride": { @@ -377,9 +345,7 @@ "description": "Error" } }, - "tags": [ - "ratelimit" - ] + "tags": ["ratelimit"] } }, "/v2/liveness": { @@ -419,9 +385,7 @@ } }, "summary": "Liveness check", - "tags": [ - "liveness" - ] + "tags": ["liveness"] } } } From 5ea303342e614090bae3b77129678ade9a7a350c Mon Sep 17 00:00:00 2001 From: chronark Date: Tue, 28 Jan 2025 14:36:44 +0100 Subject: [PATCH 6/9] test: clock tests --- go/pkg/clock/real_clock_test.go | 18 ++++++ go/pkg/clock/test_clock_test.go | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 go/pkg/clock/real_clock_test.go create mode 100644 go/pkg/clock/test_clock_test.go diff --git a/go/pkg/clock/real_clock_test.go b/go/pkg/clock/real_clock_test.go new file mode 100644 index 0000000000..9fd99733d0 --- /dev/null +++ b/go/pkg/clock/real_clock_test.go @@ -0,0 +1,18 @@ +package clock + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestRealClock(t *testing.T) { + clock := New() + before := time.Now() + now := clock.Now() + after := time.Now() + + require.False(t, now.Before(before), "time should not be before test start") + require.False(t, now.After(after), "time should not be after test end") +} diff --git a/go/pkg/clock/test_clock_test.go b/go/pkg/clock/test_clock_test.go new file mode 100644 index 0000000000..18d4fac3f0 --- /dev/null +++ b/go/pkg/clock/test_clock_test.go @@ -0,0 +1,98 @@ +package clock + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestTestClock(t *testing.T) { + t.Run("NewTestClock with no args uses current time", func(t *testing.T) { + before := time.Now() + clock := NewTestClock() + after := time.Now() + + now := clock.Now() + require.False(t, now.Before(before), "time should not be before test start") + require.False(t, now.After(after), "time should not be after test end") + }) + + t.Run("NewTestClock with specific time", func(t *testing.T) { + specificTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + clock := NewTestClock(specificTime) + + require.True(t, clock.Now().Equal(specificTime)) + }) + + t.Run("Tick advances time correctly", func(t *testing.T) { + startTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + clock := NewTestClock(startTime) + + duration := 5 * time.Minute + newTime := clock.Tick(duration) + expected := startTime.Add(duration) + + require.True(t, newTime.Equal(expected)) + require.True(t, clock.Now().Equal(expected)) + }) + + t.Run("Set changes time correctly", func(t *testing.T) { + clock := NewTestClock() + newTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + + returnedTime := clock.Set(newTime) + + require.True(t, returnedTime.Equal(newTime)) + require.True(t, clock.Now().Equal(newTime)) + }) + + t.Run("Multiple operations in sequence", func(t *testing.T) { + startTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + clock := NewTestClock(startTime) + + // Tick forward + clock.Tick(time.Hour) + expected := startTime.Add(time.Hour) + require.True(t, clock.Now().Equal(expected)) + + // Set to new time + newTime := time.Date(2023, 2, 1, 12, 0, 0, 0, time.UTC) + clock.Set(newTime) + require.True(t, clock.Now().Equal(newTime)) + + // Tick again + clock.Tick(30 * time.Minute) + expected = newTime.Add(30 * time.Minute) + require.True(t, clock.Now().Equal(expected)) + }) +} + +func TestTestClockWithDifferentTimeZones(t *testing.T) { + nyc, err := time.LoadLocation("America/New_York") + require.NoError(t, err) + + startTime := time.Date(2023, 1, 1, 12, 0, 0, 0, nyc) + clock := NewTestClock(startTime) + + require.True(t, clock.Now().Equal(startTime)) + require.Equal(t, nyc, clock.Now().Location()) +} + +func TestTestClockWithNegativeTick(t *testing.T) { + startTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + clock := NewTestClock(startTime) + + newTime := clock.Tick(-1 * time.Hour) + expected := startTime.Add(-1 * time.Hour) + + require.True(t, newTime.Equal(expected)) +} + +func TestClockInterface(t *testing.T) { + var realClock Clock = &RealClock{} + var testClock Clock = &TestClock{} + + require.Implements(t, (*Clock)(nil), realClock) + require.Implements(t, (*Clock)(nil), testClock) +} From 8faf15bd9325d8c0086013a1ce78af92b0652c3c Mon Sep 17 00:00:00 2001 From: chronark Date: Tue, 28 Jan 2025 16:18:40 +0100 Subject: [PATCH 7/9] test: harness --- go/go.mod | 2 +- go/pkg/database/schema_embed.go | 10 ++++++++++ go/pkg/testutil/containers.go | 24 +++++++++++++++++++++++- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 go/pkg/database/schema_embed.go diff --git a/go/go.mod b/go/go.mod index 8f47553a23..ac3b1d617a 100644 --- a/go/go.mod +++ b/go/go.mod @@ -7,6 +7,7 @@ require ( github.com/axiomhq/axiom-go v0.20.2 github.com/btcsuite/btcutil v1.0.2 github.com/danielgtaylor/huma v1.14.2 + github.com/go-sql-driver/mysql v1.8.1 github.com/google/uuid v1.6.0 github.com/lmittmann/tint v1.0.6 github.com/maypok86/otter v1.2.2 @@ -61,7 +62,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/cel-go v0.22.1 // indirect diff --git a/go/pkg/database/schema_embed.go b/go/pkg/database/schema_embed.go new file mode 100644 index 0000000000..571da808de --- /dev/null +++ b/go/pkg/database/schema_embed.go @@ -0,0 +1,10 @@ +package database + +import ( + _ "embed" +) + +// Schema is the sql schema embedded into the binary +// +//go:embed schema.sql +var Schema []byte diff --git a/go/pkg/testutil/containers.go b/go/pkg/testutil/containers.go index b5697d9228..6c76aeccec 100644 --- a/go/pkg/testutil/containers.go +++ b/go/pkg/testutil/containers.go @@ -3,9 +3,11 @@ package testutil import ( "database/sql" "fmt" + "strings" "testing" _ "github.com/go-sql-driver/mysql" + "github.com/unkeyed/unkey/go/pkg/database" "github.com/ory/dockertest/v3" "github.com/stretchr/testify/require" @@ -32,6 +34,8 @@ func NewContainers(t *testing.T) *Containers { } func (c *Containers) RunMySQL() string { + c.t.Helper() + resource, err := c.pool.Run("mysql", "latest", []string{ "MYSQL_ROOT_PASSWORD=root", "MYSQL_DATABASE=unkey", @@ -46,8 +50,10 @@ func (c *Containers) RunMySQL() string { addr := fmt.Sprintf("unkey:password@(localhost:%s)/unkey", resource.GetPort("3306/tcp")) + var db *sql.DB require.NoError(c.t, c.pool.Retry(func() error { - db, err := sql.Open("mysql", addr) + var err error + db, err = sql.Open("mysql", addr) if err != nil { return fmt.Errorf("unable to open mysql conenction: %w", err) } @@ -55,8 +61,24 @@ func (c *Containers) RunMySQL() string { if err != nil { return fmt.Errorf("unable to ping mysql: %w", err) } + return nil })) + // Creating the database tables + queries := strings.Split(string(database.Schema), ";") + for _, query := range queries { + query = strings.TrimSpace(query) + if query == "" { + continue + } + // Add the semicolon back + query += ";" + + _, err = db.Exec(query) + require.NoError(c.t, err) + + } + return addr } From 063d623b5ea75473500e299ced8654cb66b1dbd5 Mon Sep 17 00:00:00 2001 From: chronark Date: Thu, 30 Jan 2025 16:33:15 +0100 Subject: [PATCH 8/9] test: fix flaky timing --- go/pkg/cache/cache_test.go | 7 +++---- go/pkg/clock/test_clock.go | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/go/pkg/cache/cache_test.go b/go/pkg/cache/cache_test.go index 51d78c55e5..342bf8752c 100644 --- a/go/pkg/cache/cache_test.go +++ b/go/pkg/cache/cache_test.go @@ -70,6 +70,8 @@ func TestRefresh(t *testing.T) { Stale: time.Minute * 5, RefreshFromOrigin: func(ctx context.Context, id string) (string, bool) { refreshedFromOrigin.Add(1) + + t.Log("called", id, clk.Now()) return "hello", true }, Logger: logging.NewNoop(), @@ -86,10 +88,7 @@ func TestRefresh(t *testing.T) { require.Equal(t, cache.Hit, hit) clk.Tick(time.Second) } - - clk.Tick(5 * time.Second) - - require.Equal(t, int32(5), refreshedFromOrigin.Load()) + require.LessOrEqual(t, refreshedFromOrigin.Load(), int32(5)) } diff --git a/go/pkg/clock/test_clock.go b/go/pkg/clock/test_clock.go index 32b0fd2cf4..6467c67599 100644 --- a/go/pkg/clock/test_clock.go +++ b/go/pkg/clock/test_clock.go @@ -1,8 +1,12 @@ package clock -import "time" +import ( + "sync" + "time" +) type TestClock struct { + mu sync.RWMutex now time.Time } @@ -10,24 +14,30 @@ func NewTestClock(now ...time.Time) *TestClock { if len(now) == 0 { now = append(now, time.Now()) } - return &TestClock{now: now[0]} + return &TestClock{mu: sync.RWMutex{}, now: now[0]} } // nolint:exhaustruct var _ Clock = &TestClock{} func (c *TestClock) Now() time.Time { + c.mu.RLock() + defer c.mu.RUnlock() return c.now } // Tick advances the clock by the given duration and returns the new time. func (c *TestClock) Tick(d time.Duration) time.Time { + c.mu.Lock() + defer c.mu.Unlock() c.now = c.now.Add(d) return c.now } // Set sets the clock to the given time and returns the new time. func (c *TestClock) Set(t time.Time) time.Time { + c.mu.Lock() + defer c.mu.Unlock() c.now = t return c.now } From 7820bc9fc4f087821d7e227d6820497eee6b69b7 Mon Sep 17 00:00:00 2001 From: chronark Date: Fri, 31 Jan 2025 20:40:05 +0100 Subject: [PATCH 9/9] wp --- go/pkg/cache/cache.go | 21 +++++++ go/pkg/cache/interface.go | 2 + go/pkg/cache/middleware/tracing.go | 11 ++++ go/pkg/cache/noop.go | 4 ++ go/pkg/database/gen/querier.go | 59 +++++++++++++++++++ go/pkg/database/interface.go | 1 + go/pkg/fault/tag.go | 10 ++++ .../v2_ratelimit_set_override/handler.go | 16 ++++- go/pkg/zen/services.go | 2 + 9 files changed, 124 insertions(+), 2 deletions(-) diff --git a/go/pkg/cache/cache.go b/go/pkg/cache/cache.go index 979df07958..838513ffe8 100644 --- a/go/pkg/cache/cache.go +++ b/go/pkg/cache/cache.go @@ -200,3 +200,24 @@ func (c cache[T]) runRefreshing() { } } + +func (c cache[T]) SWR(ctx context.Context, identifier string) (T, bool) { + + value, hit := c.Get(ctx, identifier) + + if hit == Hit { + return value, true + } + if hit == Null { + return value, false + } + + value, found := c.refreshFromOrigin(ctx, identifier) + if found { + c.Set(ctx, identifier, value) + return value, true + } + c.SetNull(ctx, identifier) + return value, false + +} diff --git a/go/pkg/cache/interface.go b/go/pkg/cache/interface.go index 3bdfc2b287..14474732d1 100644 --- a/go/pkg/cache/interface.go +++ b/go/pkg/cache/interface.go @@ -18,6 +18,8 @@ type Cache[T any] interface { // Removes the key from the cache. Remove(ctx context.Context, key string) + SWR(ctx context.Context, key string) (value T, found bool) + // Dump returns a serialized representation of the cache. Dump(ctx context.Context) ([]byte, error) diff --git a/go/pkg/cache/middleware/tracing.go b/go/pkg/cache/middleware/tracing.go index f108f5a155..d6cbe6be73 100644 --- a/go/pkg/cache/middleware/tracing.go +++ b/go/pkg/cache/middleware/tracing.go @@ -82,3 +82,14 @@ func (mw *tracingMiddleware[T]) Clear(ctx context.Context) { mw.next.Clear(ctx) } + +func (mw *tracingMiddleware[T]) SWR(ctx context.Context, key string) (T, bool) { + ctx, span := tracing.Start(ctx, "cache.SWR") + defer span.End() + span.SetAttributes(attribute.String("key", key)) + + value, found := mw.next.SWR(ctx, key) + span.SetAttributes(attribute.Bool("found", found)) + return value, found + +} diff --git a/go/pkg/cache/noop.go b/go/pkg/cache/noop.go index 8e3e94c1bd..e5b8d18cb2 100644 --- a/go/pkg/cache/noop.go +++ b/go/pkg/cache/noop.go @@ -22,6 +22,10 @@ func (c *noopCache[T]) Restore(ctx context.Context, data []byte) error { return nil } func (c *noopCache[T]) Clear(ctx context.Context) {} +func (c *noopCache[T]) SWR(ctx context.Context, key string) (T, bool) { + var t T + return t, false +} func NewNoopCache[T any]() Cache[T] { return &noopCache[T]{} diff --git a/go/pkg/database/gen/querier.go b/go/pkg/database/gen/querier.go index 6eeb1b2af0..c99f2f89c4 100644 --- a/go/pkg/database/gen/querier.go +++ b/go/pkg/database/gen/querier.go @@ -139,6 +139,65 @@ type Querier interface { // SET plan = ? // WHERE id = ? UpdateWorkspacePlan(ctx context.Context, arg UpdateWorkspacePlanParams) (sql.Result, error) + //VerifyKey + // + // WITH direct_permissions AS ( + // SELECT kp.key_id, p.name as permission_name + // FROM keys_permissions kp + // JOIN permissions p ON kp.permission_id = p.id + // ), + // role_permissions AS ( + // SELECT kr.key_id, p.name as permission_name + // FROM keys_roles kr + // JOIN roles_permissions rp ON kr.role_id = rp.role_id + // JOIN permissions p ON rp.permission_id = p.id + // ), + // all_permissions AS ( + // SELECT key_id, permission_name FROM direct_permissions + // UNION + // SELECT key_id, permission_name FROM role_permissions + // ), + // all_ratelimits AS ( + // SELECT + // key_id as target_id, + // 'key' as target_type, + // name, + // `limit`, + // duration + // FROM ratelimits + // WHERE key_id IS NOT NULL + // UNION + // SELECT + // identity_id as target_id, + // 'identity' as target_type, + // name, + // `limit`, + // duration + // FROM ratelimits + // WHERE identity_id IS NOT NULL + // ) + // SELECT + // k.id, k.key_auth_id, k.hash, k.start, k.workspace_id, k.for_workspace_id, k.name, k.owner_id, k.identity_id, k.meta, k.created_at, k.expires, k.created_at_m, k.updated_at_m, k.deleted_at_m, k.deleted_at, k.refill_day, k.refill_amount, k.last_refill_at, k.enabled, k.remaining_requests, k.ratelimit_async, k.ratelimit_limit, k.ratelimit_duration, k.environment, + // i.id, i.external_id, i.workspace_id, i.environment, i.created_at, i.updated_at, i.meta, + // JSON_ARRAYAGG( + // JSON_OBJECT( + // 'target_type', rl.target_type, + // 'name', rl.name, + // 'limit', rl.limit, + // 'duration', rl.duration + // ) + // ) as ratelimits, + // GROUP_CONCAT(DISTINCT perms.permission_name) as permissions + // FROM `keys` k + // LEFT JOIN identities i ON k.identity_id = i.id + // LEFT JOIN all_permissions perms ON k.id = perms.key_id + // LEFT JOIN all_ratelimits rl ON ( + // (rl.target_type = 'key' AND rl.target_id = k.id) OR + // (rl.target_type = 'identity' AND rl.target_id = k.identity_id) + // ) + // WHERE k.hash = ? + // GROUP BY k.id + VerifyKey(ctx context.Context, hash string) (VerifyKeyRow, error) } var _ Querier = (*Queries)(nil) diff --git a/go/pkg/database/interface.go b/go/pkg/database/interface.go index 87c797ac54..24a1c0e387 100644 --- a/go/pkg/database/interface.go +++ b/go/pkg/database/interface.go @@ -33,6 +33,7 @@ type Database interface { // InsertKey(ctx context.Context, newKey entities.Key) error FindKeyByID(ctx context.Context, keyId string) (key entities.Key, err error) FindKeyByHash(ctx context.Context, hash string) (key entities.Key, err error) + FindKeyForVerification(ctx context.Context, hash string) (key entities.KeyForVerification, err error) // UpdateKey(ctx context.Context, key entities.Key) error // SoftDeleteKey(ctx context.Context, keyId string) error // DecrementRemainingKeyUsage(ctx context.Context, keyId string) (key entities.Key, err error) diff --git a/go/pkg/fault/tag.go b/go/pkg/fault/tag.go index 8b2494c38e..3c99c8a4a1 100644 --- a/go/pkg/fault/tag.go +++ b/go/pkg/fault/tag.go @@ -16,11 +16,21 @@ const ( // An object was not found in the system. NOT_FOUND Tag = "NOT_FOUND" + UNAUTHORIZED Tag = "UNAUTHORIZED" + FORBIDDEN Tag = "FORBIDDEN" + DATABASE_ERROR Tag = "DATABASE_ERROR" INTERNAL_SERVER_ERROR Tag = "INTERNAL_SERVER_ERROR" PROTECTED_RESOURCE Tag = "PROTECTED_RESOURCE" + + // An assertion failed during runtime. + // This tag is used to indicate that a condition that should have been true + // was false, indicating a programming error. + // + // For example we assert that a field on a struct is not empty, but it is. + ASSERTION_FAILED Tag = "ASSERTION_FAILED" ) // GetTag examines an error and its chain of wrapped errors to find the first diff --git a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go index 9dc77cb32c..2218bc510e 100644 --- a/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go +++ b/go/pkg/zen/routes/v2_ratelimit_set_override/handler.go @@ -5,8 +5,10 @@ import ( "time" "github.com/unkeyed/unkey/go/api" + "github.com/unkeyed/unkey/go/hash" "github.com/unkeyed/unkey/go/pkg/entities" "github.com/unkeyed/unkey/go/pkg/fault" + "github.com/unkeyed/unkey/go/pkg/hash" "github.com/unkeyed/unkey/go/pkg/uid" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -17,9 +19,19 @@ type Response = api.V2RatelimitSetOverrideResponseBody func New(svc *zen.Services) zen.Route { return zen.NewRoute("POST", "/v2/ratelimit.setOverride", func(s *zen.Session) error { + rootKey, err := zen.Bearer(s) + if err != nil { + return err + } + + auth, err := svc.Keys.Verify(s.Context(), hash.Sha256(rootKey)) + if err != nil { + return err + } + // nolint:exhaustruct req := Request{} - err := s.BindBody(&req) + err = s.BindBody(&req) if err != nil { return fault.Wrap(err, fault.WithTag(fault.INTERNAL_SERVER_ERROR), @@ -30,7 +42,7 @@ func New(svc *zen.Services) zen.Route { overrideID := uid.New(uid.RatelimitOverridePrefix) err = svc.Database.InsertRatelimitOverride(s.Context(), entities.RatelimitOverride{ ID: overrideID, - WorkspaceID: "", + WorkspaceID: auth.AuthorizedWorkspaceID, NamespaceID: "", Identifier: "", Limit: 0, diff --git a/go/pkg/zen/services.go b/go/pkg/zen/services.go index c40f2fdb39..44eb18cc12 100644 --- a/go/pkg/zen/services.go +++ b/go/pkg/zen/services.go @@ -1,6 +1,7 @@ package zen import ( + "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/pkg/clickhouse/schema" "github.com/unkeyed/unkey/go/pkg/database" "github.com/unkeyed/unkey/go/pkg/logging" @@ -14,4 +15,5 @@ type Services struct { Logger logging.Logger Database database.Database EventBuffer EventBuffer + Keys keys.KeyService }