diff options
Diffstat (limited to '')
-rw-r--r-- | .gitpod.Dockerfile | 10 | ||||
-rw-r--r-- | README.md | 60 | ||||
-rw-r--r-- | arbiter/.golangci.yml | 18 | ||||
-rw-r--r-- | arbiter/Dockerfile | 18 | ||||
-rw-r--r-- | arbiter/arbiter.go | 505 | ||||
-rw-r--r-- | arbiter/go.mod | 17 | ||||
-rw-r--r-- | arbiter/go.sum | 41 | ||||
-rwxr-xr-x | arbiter/tests.sh | 4 | ||||
-rw-r--r-- | docker-compose-test.yaml | 100 | ||||
-rw-r--r-- | docker-compose.yaml | 119 | ||||
-rw-r--r-- | hived/Dockerfile | 14 | ||||
-rw-r--r-- | hived/alertHandlers.go | 8 | ||||
-rwxr-xr-x | hived/docker-entrypoint.sh | 6 | ||||
-rw-r--r-- | hived/go.mod | 100 | ||||
-rw-r--r-- | hived/go.sum | 346 | ||||
-rw-r--r-- | hived/hived.go | 591 | ||||
-rw-r--r-- | hived/hived.toml | 7 | ||||
-rw-r--r-- | nginx.conf | 35 | ||||
-rw-r--r-- | telebot/.golangci.yml | 19 | ||||
-rw-r--r-- | telebot/Dockerfile | 21 | ||||
-rwxr-xr-x | telebot/docker-entrypoint.sh | 6 | ||||
-rw-r--r-- | telebot/go.mod | 22 | ||||
-rw-r--r-- | telebot/go.sum | 149 | ||||
-rw-r--r-- | telebot/telebot.go | 167 | ||||
-rwxr-xr-x | test/endpoints.sh | 35 |
25 files changed, 973 insertions, 1445 deletions
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile deleted file mode 100644 index 1810344..0000000 --- a/.gitpod.Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM alpine:3.13 as builder - -RUN apk update && apk upgrade -RUN apk add go git -COPY go.* /hived/ -RUN cd /hived && go mod download -COPY *.go /hived/ -RUN cd /hived && go build -COPY ./docker-entrypoint.sh /hived/ -# ENTRYPOINT ["/hived/docker-entrypoint.sh"] @@ -3,62 +3,4 @@ # hived -`hived` is small personal cryptocurrency server:<br/> - -- It sends notifications through telegram.<br/> - -Currently it has 5 endpoint:<br/> - -### /price - -Lets you ask for the price of the currency. You can determine the currency the value is returned in.<br/> - -### /pair - -Takes in a pair of currencies and a multiplier. Determines and returns the ratio.<br/> - -### /alert - -#### POST - -Takes in a name and a math expression containing the names of the currencies. Checks the expression periodically. Sends a message over telegram when the expression holds true.<br/> -The expression's result must be boolean. As an example:<br/> - -```Go -ETH*50>50000. -ETH*60/(DOGE*300000) < 4. -``` - -You can have as many parameters as you like. The requests for the crypto prices are all turned into individual goroutines so it's fast.<br/> -The expression evaluation is powered by [govaluate](https://github.com/Knetic/govaluate). So for a set of rules and what you can and cannot do please check the documentation over there.<br/> - -#### DELETE - -Deletes the key from the DB so you will no longer receive updates.<br/> - -#### PUT - -Updates the alert.<br/.> - -#### GET - -Fetch the alert with the given name.<br/> - -### /ex - -Gets the list of currencies that are available to be traded.<br/> - -You can check under `./test` for some examples of curl commands.<br/> - -### /health - -Returns the health status of the service.<br/> - -```sh -TELEGRAM_BOT_TOKEN="my-telegram-bot-api-key" -``` - -If you want to use docker-compose, it's as simple as running `docker-compose up`. You just need to provide the files. You can check the file names in the docker-compose file.<br/> -Both the server itself and the redis image are alpine-based so they're pretty small.<br/> - -## telebot +`hived` is a cryptocurrency api server:<br/> diff --git a/arbiter/.golangci.yml b/arbiter/.golangci.yml deleted file mode 100644 index 878e031..0000000 --- a/arbiter/.golangci.yml +++ /dev/null @@ -1,18 +0,0 @@ -run: - concurrency: 16 - timeout: 5m - modules-download-mode: readonly - allow-parallel-runners: true - allow-serial-runners: true - go: '1.22' -linters-settings: - depguard: - rules: - srcs: - listMode: "Strict" - allow: - - $gostd - - github.com/go-redis/redis/v8 - - github.com/gorilla/mux - - github.com/rs/zerolog - - github.com/rs/zerolog/log diff --git a/arbiter/Dockerfile b/arbiter/Dockerfile deleted file mode 100644 index a512798..0000000 --- a/arbiter/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM golang:1.22-alpine3.19 as builder -RUN apk update && apk upgrade -RUN apk add go git -COPY go.* /arbiter/ -RUN cd /arbiter && go mod download -COPY *.go /arbiter/ -RUN cd /arbiter && go build - -FROM alpine:3.19 as certbuilder -RUN apk add openssl -WORKDIR /certs -RUN openssl req -nodes -new -x509 -subj="/C=US/ST=Denial/L=springfield/O=Dis/CN=localhost" -keyout server.key -out server.cert - -# FROM gcr.io/distroless/static-debian10 -FROM alpine:3.19 -COPY --from=certbuilder /certs /certs -COPY --from=builder /arbiter/arbiter /arbiter/ -ENTRYPOINT ["/arbiter/arbiter"] diff --git a/arbiter/arbiter.go b/arbiter/arbiter.go deleted file mode 100644 index 6145a45..0000000 --- a/arbiter/arbiter.go +++ /dev/null @@ -1,505 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "encoding/json" - "errors" - "flag" - "io" - "net/http" - "net/url" - "os" - "os/signal" - "strconv" - "sync" - "time" - - "github.com/go-redis/redis/v8" - "github.com/gorilla/mux" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" -) - -var ( - errBadLogic = errors.New("we should not be here") - errUnexpectedParam = errors.New("got unexpected parameter") - errUnknownDeployment = errors.New("unknown deployment kind") -) - -const ( - serverDeploymentType = "SERVER_DEPLOYMENT_TYPE" - coingeckoAPIURLv3 = "https://api.coingecko.com/api/v3" - coincapAPIURLv2 = "https://api.coincap.io/v2" - getTimeout = 5 - httpClientTimeout = 5 - serverTLSReadTimeout = 15 - serverTLSWriteTimeout = 15 - defaultGracefulShutdown = 15 -) - -// https://docs.coincap.io/ -type CoinCapAssetGetResponseData struct { - ID string `json:"id"` - Rank string `json:"rank"` - Symbol string `json:"symbol"` - Name string `json:"name"` - Supply string `json:"supply"` - MaxSupply string `json:"maxSupply"` - MarketCapUsd string `json:"marketCapUsd"` - VolumeUsd24Hr string `json:"volumeUsd24Hr"` - PriceUsd string `json:"priceUsd"` - ChangePercent24Hr string `json:"changePercent24Hr"` - Vwap24Hr string `json:"vwap24Hr"` -} - -type priceResponseData struct { - Name string `json:"name"` - Price float64 `json:"price"` - Unit string `json:"unit"` - Err string `json:"err"` - IsSuccessful bool `json:"isSuccessful"` -} - -type CoinCapAssetGetResponse struct { - Data CoinCapAssetGetResponseData `json:"data"` - TimeStamp int64 `json:"timestamp"` -} - -type HTTPHandlerFunc func(http.ResponseWriter, *http.Request) - -type HTTPHandler struct { - name string - function HTTPHandlerFunc -} - -type priceChanStruct struct { - name string - price float64 -} - -type errorChanStruct struct { - hasError bool - err error -} - -func GetProxiedClient() *http.Client { - transport := &http.Transport{ - DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, - } - client := &http.Client{ - Transport: transport, - Timeout: httpClientTimeout * time.Second, - CheckRedirect: nil, - Jar: nil, - } - - return client -} - -// OWASP: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html -func addSecureHeaders(writer *http.ResponseWriter) { - (*writer).Header().Set("Cache-Control", "no-store") - (*writer).Header().Set("Content-Security-Policy", "default-src https;") - (*writer).Header().Set("Strict-Transport-Security", "max-age=63072000;") - (*writer).Header().Set("X-Content-Type-Options", "nosniff") - (*writer).Header().Set("X-Frame-Options", "DENY") - (*writer).Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") -} - -func getPriceFromCoinGecko( - ctx context.Context, - name, unit string, - wg *sync.WaitGroup, - priceChan chan<- priceChanStruct, - errChan chan<- errorChanStruct, -) { - defer wg.Done() - - priceFloat := 0. - - params := "/simple/price?ids=" + url.QueryEscape(name) + "&" + - "vs_currencies=" + url.QueryEscape(unit) - path := coingeckoAPIURLv3 + params - - client := GetProxiedClient() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } - - resp, err := client.Do(req) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - - return - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - } - - jsonBody := make(map[string]interface{}) - - err = json.Unmarshal(body, &jsonBody) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - } - - price, isOk := jsonBody[name].(map[string]interface{}) - if !isOk { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } - - log.Info().Msg(string(body)) - - priceFloat, isOk = price[unit].(float64) - if !isOk { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } - - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: false, err: nil} -} - -func getPriceFromCoinCap( - ctx context.Context, - name string, - wg *sync.WaitGroup, - priceChan chan<- priceChanStruct, - errChan chan<- errorChanStruct, -) { - defer wg.Done() - - priceFloat := 0. - - params := "/assets/" + url.QueryEscape(name) - path := coincapAPIURLv2 + params - - client := GetProxiedClient() - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } - - resp, err := client.Do(req) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - - return - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - } - - var coinCapAssetGetResponse CoinCapAssetGetResponse - - err = json.Unmarshal(body, &coinCapAssetGetResponse) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - } - - priceFloat, err = strconv.ParseFloat(coinCapAssetGetResponse.Data.PriceUsd, 64) - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err).Send() - } - - log.Info().Msg(string(body)) - - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: false, err: nil} -} - -func arbHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") - - if r.Method != http.MethodGet { - http.Error(w, "Method is not supported.", http.StatusNotFound) - } - - addSecureHeaders(&w) - - var name string - - var unit string - - params := r.URL.Query() - for key, value := range params { - switch key { - case "name": - name = value[0] - case "unit": - unit = value[0] - default: - log.Error().Err(errUnexpectedParam) - } - } - - priceChan := make(chan priceChanStruct, 1) - errChan := make(chan errorChanStruct, 1) - - var waitGroup sync.WaitGroup - - ctx, cancel := context.WithTimeout(context.Background(), getTimeout*time.Second) - defer cancel() - - waitGroup.Add(1) - - //nolint:contextcheck - getPriceFromCoinGecko(ctx, name, unit, &waitGroup, priceChan, errChan) - waitGroup.Wait() - - select { - case err := <-errChan: - if err.hasError { - log.Error().Err(err.err) - } - default: - log.Error().Err(errBadLogic).Send() - } - - var price priceChanStruct - select { - case priceCh := <-priceChan: - price = priceCh - default: - log.Error().Err(errBadLogic) - } - - responseData := priceResponseData{ - Name: price.name, - Price: price.price, - Unit: "USD", - Err: "", - IsSuccessful: true, - } - - jsonResp, err := json.Marshal(responseData) - if err != nil { - cancel() - //nolint:gocritic - log.Fatal().Err(err) - } - - _, err = w.Write(jsonResp) - if err != nil { - cancel() - log.Fatal().Err(err) - } -} - -func coincapHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method is not supported.", http.StatusNotFound) - } - - w.Header().Add("Content-Type", "application/json") - - addSecureHeaders(&w) - - var name string - - params := r.URL.Query() - for key, value := range params { - switch key { - case "name": - name = value[0] - default: - log.Error().Err(errUnexpectedParam).Send() - } - } - - priceChan := make(chan priceChanStruct, 1) - errChan := make(chan errorChanStruct, 1) - - var waitGroup sync.WaitGroup - - waitGroup.Add(1) - - ctx, cancel := context.WithTimeout(context.Background(), getTimeout*time.Second) - defer cancel() - - getPriceFromCoinCap(ctx, name, &waitGroup, priceChan, errChan) - waitGroup.Wait() - - select { - case err := <-errChan: - if err.hasError { - log.Error().Err(err.err) - } - default: - log.Error().Err(errBadLogic) - } - - var price priceChanStruct - select { - case priceCh := <-priceChan: - price = priceCh - default: - log.Error().Err(errBadLogic) - } - - responseData := priceResponseData{ - Name: price.name, - Price: price.price, - Unit: "USD", - Err: "", - IsSuccessful: true, - } - - jsonResp, err := json.Marshal(responseData) - if err != nil { - cancel() - - log.Fatal().Err(err).Send() - } - - _, err = w.Write(jsonResp) - if err != nil { - cancel() - log.Fatal().Err(err) - } -} - -func setupLogging() { - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix -} - -func startServer(gracefulWait time.Duration, - handlers []HTTPHandler, - serverDeploymentType string, port string, -) { - route := mux.NewRouter() - cfg := &tls.Config{ - MinVersion: tls.VersionTLS13, - } - - srv := &http.Server{ - Addr: "0.0.0.0:" + port, - WriteTimeout: time.Second * serverTLSWriteTimeout, - ReadTimeout: time.Second * serverTLSReadTimeout, - Handler: route, - TLSConfig: cfg, - } - - for i := range len(handlers) { - route.HandleFunc(handlers[i].name, handlers[i].function) - } - - go func() { - var certPath, keyPath string - - switch os.Getenv(serverDeploymentType) { - case "deployment": - certPath = "/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem" - keyPath = "/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem" - case "test": - certPath = "/certs/server.cert" - keyPath = "/certs/server.key" - default: - log.Error().Err(errUnknownDeployment).Send() - } - - if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil { - log.Error().Err(err) - } - }() - - c := make(chan os.Signal, 1) - - signal.Notify(c, os.Interrupt) - <-c - - ctx, cancel := context.WithTimeout(context.Background(), gracefulWait) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - log.Error().Err(err) - } - - log.Info().Msg("gracefully shut down the server") -} - -func main() { - var gracefulWait time.Duration - - var rdb *redis.Client - - flag.DurationVar( - &gracefulWait, - "gracefulwait", - time.Second*defaultGracefulShutdown, - "the duration to wait during the graceful shutdown", - ) - - flagPort := flag.String("port", "8009", "determines the port the server will listen on") - redisDB := flag.Int64("redisdb", 1, "determines the db number") - redisAddress := flag.String("redisaddress", "redis:6379", "determines the address of the redis instance") - redisPassword := flag.String("redispassword", "", "determines the password of the redis db") - flag.Parse() - - rdb = redis.NewClient(&redis.Options{ - Addr: *redisAddress, - Password: *redisPassword, - DB: int(*redisDB), - }) - defer rdb.Close() - - setupLogging() - - handlerFuncs := []HTTPHandler{ - {name: "/crypto/v1/arb/gecko", function: arbHandler}, - {name: "/crypto/v1/arb/coincap", function: coincapHandler}, - } - - startServer(gracefulWait, handlerFuncs, serverDeploymentType, *flagPort) -} diff --git a/arbiter/go.mod b/arbiter/go.mod deleted file mode 100644 index a7a87c6..0000000 --- a/arbiter/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -module arbiter - -go 1.22 - -require ( - github.com/go-redis/redis/v8 v8.11.5 - github.com/gorilla/mux v1.8.1 - github.com/rs/zerolog v1.31.0 -) - -require ( - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/sys v0.15.0 // indirect -) diff --git a/arbiter/go.sum b/arbiter/go.sum deleted file mode 100644 index 85a6437..0000000 --- a/arbiter/go.sum +++ /dev/null @@ -1,41 +0,0 @@ -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -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.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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/arbiter/tests.sh b/arbiter/tests.sh deleted file mode 100755 index be923f3..0000000 --- a/arbiter/tests.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh - -curl -k -X GET "https://localhost:8009/crypto/v1/arb/gecko?name=ethereum&unit=usd" -curl -k -X GET "https://localhost:8009/crypto/v1/arb/coincap?name=ethereum" diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 6b7367a..49ceab9 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -1,31 +1,4 @@ services: - auth: - image: auth - build: - context: ./auth - deploy: - resources: - limits: - memory: 256M - logging: - driver: "json-file" - options: - max-size: "100m" - networks: - - authnet - restart: unless-stopped - ports: - - "127.0.0.1:8091:8090" - depends_on: - - nginx - volumes: - - pb-vault:/auth/pb_data - cap_drop: - - ALL - environment: - - SERVER_DEPLOYMENT_TYPE=deployment - entrypoint: ["/auth/auth"] - command: ["serve", "--http=0.0.0.0:8090"] nginx: image: nginx:stable deploy: @@ -37,9 +10,9 @@ services: options: max-size: "100m" ports: - - "127.0.0.1:8090:443" + - "127.0.0.1:10008:443" networks: - - authnet + - apinet restart: unless-stopped cap_drop: - ALL @@ -50,9 +23,11 @@ services: - SETUID - NET_BIND_SERVICE volumes: - - ./auth/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ss_certs/server.cert:/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem:ro - ./ss_certs/server.key:/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem:ro + depends_on: + - hived hived: image: hived build: @@ -70,66 +45,23 @@ services: networks: - apinet - dbnet - - telenet ports: - - "127.0.0.1:10008:8008" + - "127.0.0.1:10009:8090" + entrypoint: ["/hived/hived"] + command: ["serve", "--http=0.0.0.0:8090"] depends_on: - keydb - - telebot - entrypoint: ["/hived/docker-entrypoint.sh"] cap_drop: - ALL environment: - SERVER_DEPLOYMENT_TYPE=test - - HIVED_PRICE_SOURCE=cmc + - HIVED_PRICE_SOURCE=cryptocompare - CMC_API_KEY= - POLYGON_API_KEY= - telebot: - image: telebot - build: - context: ./telebot - deploy: - resources: - limits: - memory: 256M - logging: - driver: "json-file" - options: - max-size: "100m" - secrets: - - tg_bot_token - networks: - - telenet - ports: - - "127.0.0.1:10009:8000" - entrypoint: ["/telebot/docker-entrypoint.sh"] - cap_drop: - - ALL - environment: - - SERVER_DEPLOYMENT_TYPE=test - arbiter: - image: arbiter - build: - context: ./arbiter - deploy: - resources: - limits: - memory: 256M - logging: - driver: "json-file" - options: - max-size: "100m" - networks: - - apinet - - dbnet - - telenet - ports: - - "127.0.0.1:8009:8009" - entrypoint: ["/arbiter/arbiter"] - cap_drop: - - ALL - environment: - - SERVER_DEPLOYMENT_TYPE=test + - CRYPTOCOMPARE_API_KEY= + - TELEGRAM_BOT_TOKEN= + volumes: + - ./hived/hived.toml:/hived/hived.toml keydb: image: eqalpha/keydb:alpine_x86_64_v6.3.4 deploy: @@ -146,12 +78,10 @@ services: - "127.0.0.1:6380:6379" environment: - ALLOW_EMPTY_PASSWORD=yes - # volumes: - # - keydb-data:/data/ + volumes: + - keydb-data:/data/ networks: - authnet: dbnet: - telenet: apinet: secrets: tg_bot_token: diff --git a/docker-compose.yaml b/docker-compose.yaml index 23e7d32..65d853a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,80 +1,95 @@ services: + nginx: + image: nginx:stable + deploy: + resources: + limits: + memory: 128M + logging: + driver: "json-file" + options: + max-size: "100m" + ports: + - "127.0.0.1:10008:443" + networks: + - apinet + restart: unless-stopped + cap_drop: + - ALL + cap_add: + - CHOWN + - DAC_OVERRIDE + - SETGID + - SETUID + - NET_BIND_SERVICE + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + - /etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem:/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem:ro + - /etc/letsencrypt/live/api.terminaldweller.com/privkey.pem:/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem:ro + depends_on: + - hived hived: image: hived build: context: ./hived + deploy: + resources: + limits: + memory: 256M + logging: + driver: "json-file" + options: + max-size: "100m" secrets: - tg_bot_token networks: - - mainnet - - telenet - restart: unless-stopped + - apinet + - dbnet ports: - - "8007:8008" + - "127.0.0.1:10009:8090" + entrypoint: ["/hived/hived"] + command: ["serve", "--http=0.0.0.0:8090"] depends_on: - keydb - - telebot - entrypoint: ["/hived/docker-entrypoint.sh"] - volumes: - - /etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem:/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem - - /etc/letsencrypt/live/api.terminaldweller.com/privkey.pem:/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem cap_drop: - ALL environment: - - SERVER_DEPLOYMENT_TYPE=deployment + - SERVER_DEPLOYMENT_TYPE=test + - HIVED_PRICE_SOURCE=cryptocompare + - CMC_API_KEY= - POLYGON_API_KEY= - CRYPTOCOMPARE_API_KEY= - - HIVED_PRICE_SOURCE= - telebot: - image: telebot - build: - context: ./telebot - secrets: - - tg_bot_token - networks: - - telenet - restart: unless-stopped - ports: - - "127.0.0.1:9009:8000" - entrypoint: ["/telebot/docker-entrypoint.sh"] - cap_drop: - - ALL - environment: - - SERVER_DEPLOYMENT_TYPE=deployment + - TELEGRAM_BOT_TOKEN= + volumes: + - ./hived/hived.toml:/hived/hived.toml keydb: image: eqalpha/keydb:alpine_x86_64_v6.3.4 + deploy: + resources: + limits: + memory: 256M + logging: + driver: "json-file" + options: + max-size: "100m" networks: - - mainnet - restart: unless-stopped + - dbnet ports: - - "127.0.0.1:6379:6379" + - "127.0.0.1:6380:6379" environment: - ALLOW_EMPTY_PASSWORD=yes volumes: - - redis-data:/data/ - arbiter: - image: arbiter - build: - context: ./arbiter - networks: - - mainnet - ports: - - "8009:8009" - entrypoint: ["/arbiter/arbiter"] - volumes: - - /etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem:/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem - - /etc/letsencrypt/live/api.terminaldweller.com/privkey.pem:/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem - cap_drop: - - ALL - environment: - - SERVER_DEPLOYMENT_TYPE=deployment + - keydb-data:/data/ networks: - mainnet: - driver: bridge - telenet: - driver: bridge + dbnet: + apinet: secrets: tg_bot_token: file: ./tgtoken + polygon_api_key: + file: ./polygon_api_key + cmc_api_key: + file: ./cmc_api_key volumes: - redis-data: + keydb-data: + p diff --git a/hived/Dockerfile b/hived/Dockerfile index 64e4789..a9c84ac 100644 --- a/hived/Dockerfile +++ b/hived/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine3.19 as builder +FROM golang:1.22-alpine3.20 as builder RUN apk update && apk upgrade RUN apk add go git COPY go.* /hived/ @@ -6,14 +6,6 @@ RUN cd /hived && go mod download COPY *.go /hived/ RUN cd /hived && go build -FROM alpine:3.19 as certbuilder -RUN apk add openssl -WORKDIR /certs -RUN openssl req -nodes -new -x509 -subj="/C=US/ST=Denial/L=springfield/O=Dis/CN=localhost" -keyout server.key -out server.cert - -# FROM gcr.io/distroless/static-debian10 -FROM alpine:3.19 -COPY --from=certbuilder /certs /certs +FROM alpine:3.20 COPY --from=builder /hived/hived /hived/ -COPY ./docker-entrypoint.sh /hived/ -ENTRYPOINT ["/hived/docker-entrypoint.sh"] +ENTRYPOINT ["/hived/hived"] diff --git a/hived/alertHandlers.go b/hived/alertHandlers.go index fd49835..6fb7614 100644 --- a/hived/alertHandlers.go +++ b/hived/alertHandlers.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/labstack/echo/v5" "github.com/rs/zerolog/log" ) @@ -166,7 +167,10 @@ func (alertHandler Handler) HandleAlertGet(writer http.ResponseWriter, request * } } -func alertHandler(writer http.ResponseWriter, request *http.Request) { +func (aw appWrapper) alertHandler(echoCtx echo.Context) error { + writer := echoCtx.Response().Writer + request := echoCtx.Request() + addSecureHeaders(&writer) handler := Handler{rdb: rdb} @@ -185,4 +189,6 @@ func alertHandler(writer http.ResponseWriter, request *http.Request) { default: http.Error(writer, "Method is not supported.", http.StatusNotFound) } + + return nil } diff --git a/hived/docker-entrypoint.sh b/hived/docker-entrypoint.sh deleted file mode 100755 index 0e41826..0000000 --- a/hived/docker-entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -ex - -export "$(cat /run/secrets/tg_bot_token)" - -"/hived/hived" "$@" diff --git a/hived/go.mod b/hived/go.mod index dead3e3..d9b8a4e 100644 --- a/hived/go.mod +++ b/hived/go.mod @@ -3,33 +3,97 @@ module hived go 1.22 require ( + github.com/BurntSushi/toml v0.3.1 github.com/Knetic/govaluate v3.0.0+incompatible github.com/go-redis/redis/v8 v8.11.5 - github.com/gorilla/mux v1.8.1 - github.com/polygon-io/client-go v1.16.4 + github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible + github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 + github.com/pocketbase/pocketbase v0.22.13 github.com/rs/zerolog v1.31.0 github.com/terminaldweller/grpc v1.0.3 - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.1 ) require ( + github.com/AlecAivazis/survey/v2 v2.3.7 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2 v1.27.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.16 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 // indirect + github.com/aws/smithy-go v1.20.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/go-playground/form/v4 v4.2.1 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/go-resty/resty/v2 v2.11.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/disintegration/imaging v1.6.2 // indirect + github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/domodwyer/mailyak/v3 v3.6.2 // indirect + github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d // indirect + github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/ganigeorgiev/fexpr v0.4.1 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pocketbase/dbx v1.10.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.opencensus.io v0.24.0 // indirect + gocloud.dev v0.37.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/image v0.16.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.182.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect + modernc.org/libc v1.51.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.30.0 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/hived/go.sum b/hived/go.sum index 2a70c3b..0bc72a3 100644 --- a/hived/go.sum +++ b/hived/go.sum @@ -1,13 +1,79 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= +cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= +cloud.google.com/go/auth v0.4.2 h1:sb0eyLkhRtpq5jA+a8KWw0W70YcdVca7KJ8TM0AFYDg= +cloud.google.com/go/auth v0.4.2/go.mod h1:Kqvlz1cf1sNA0D+sYJnkPQOP+JMHkuHeIgVmCRtZOLc= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s= +github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo= +github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg= +github.com/aws/aws-sdk-go-v2/config v1.27.16 h1:knpCuH7laFVGYTNd99Ns5t+8PuRjDn4HnnZK48csipM= +github.com/aws/aws-sdk-go-v2/config v1.27.16/go.mod h1:vutqgRhDUktwSge3hrC3nkuirzkJ4E/mLj5GvI0BQas= +github.com/aws/aws-sdk-go-v2/credentials v1.17.16 h1:7d2QxY83uYl0l58ceyiSpxg9bSbStqBC6BeEeHEchwo= +github.com/aws/aws-sdk-go-v2/credentials v1.17.16/go.mod h1:Ae6li/6Yc6eMzysRL2BXlPYvnrLLBg3D11/AmOjw50k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21 h1:1v8Ii0MRVGYB/sdhkbxrtolCA7Tp+lGh+5OJTs5vmZ8= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.21/go.mod h1:cxdd1rc8yxCjKz28hi30XN1jDXr2DxZvD44vLxTz/bg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7 h1:/FUtT3xsoHO3cfh+I/kCbcMCN98QZRsiFet/V8QkWSs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.7/go.mod h1:MaCAgWpGooQoCWZnMur97rGn5dp350w2+CeiV5406wE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9 h1:UXqEWQI0n+q0QixzU0yUUQBZXRd5037qdInTIHFTl98= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.9/go.mod h1:xP6Gq6fzGZT8w/ZN+XvGMZ2RU1LeEs7b2yUP5DN8NY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7 h1:uO5XR6QGBcmPyo2gxofYJLFkcVQ4izOoGDNenlZhTEk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.7/go.mod h1:feeeAYfAcwTReM6vbwjEyDmiGho+YgBhaFULuXDW8kc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3 h1:57NtjG+WLims0TxIQbjTqebZUKDM03DfM11ANAekW0s= +github.com/aws/aws-sdk-go-v2/service/s3 v1.54.3/go.mod h1:739CllldowZiPPsDFcJHNF4FXrVxaSGVnZ9Ez9Iz9hc= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 h1:aD7AGQhvPuAxlSUfo0CWU7s6FpkbyykMhGYMvlqTjVs= +github.com/aws/aws-sdk-go-v2/service/sso v1.20.9/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 h1:Pav5q3cA260Zqez42T9UhIlsd9QeypszRPwC9LdSSsQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 h1:69tpbPED7jKPyzMcrwSvhWcJ9bPnZsZs18NT40JwM0g= +github.com/aws/aws-sdk-go-v2/service/sts v1.28.10/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -16,41 +82,78 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= +github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw= +github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c h1:hLoodLRD4KLWIH8eyAQCLcH8EqIrjac7fCkp/fHnvuQ= +github.com/dop251/goja_nodejs v0.0.0-20231122114759-e84d9a924c5c/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +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/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k= +github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/form/v4 v4.2.1 h1:HjdRDKO0fftVMU5epjPW2SOREcZ6/wLUzEobqUGJuPw= -github.com/go-playground/form/v4 v4.2.1/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= -github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= +github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -63,30 +166,71 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/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/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +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= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= -github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4= +github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -96,93 +240,134 @@ github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5h github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polygon-io/client-go v1.16.4 h1:3hxFXAVOtbQ9EfzyI65f5qNMWYAAJ/SoBktk4al0YAY= -github.com/polygon-io/client-go v1.16.4/go.mod h1:jkG484PfMaqiubeFdoT38nrMOx1zclJcjccxDc/BEsE= +github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA= +github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= +github.com/pocketbase/pocketbase v0.22.13 h1:6703U9aByDueyVCuASqTfvMUrH2Beq26WiohOXp/vk4= +github.com/pocketbase/pocketbase v0.22.13/go.mod h1:QrZElN3ifiRf9aCHv9ks6JGWYdY3nLO6uuf5/0cLXiI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/terminaldweller/grpc v1.0.3 h1:yCRm0HKRD4M87CBmmO5KjkSFRq4X2lzHLJRH1ApzeiE= github.com/terminaldweller/grpc v1.0.3/go.mod h1:pYpuXZw8rHZwTABVEVZEfErFr+PyEhAaSrFm7y1yvTo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro= +gocloud.dev v0.37.0/go.mod h1:7/O4kqdInCNsc6LqgmuFnS0GRew4XNNYWpA44yQnwco= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -190,27 +375,38 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.182.0 h1:if5fPvudRQ78GeRx3RayIoiuV7modtErPIZC/T2bIvE= +google.golang.org/api v0.182.0/go.mod h1:cGhjy4caqA5yXRzEhkHI8Y9mfyC2VLTlER2l08xaqtM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -223,13 +419,17 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -237,3 +437,29 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= +modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= +modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.51.0 h1:kjSHjz1guHbI5iRdi6nEr/wIKSN6X4vzLd6TJMN+lHA= +modernc.org/libc v1.51.0/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= +modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/hived/hived.go b/hived/hived.go index f2764a4..c714cfd 100644 --- a/hived/hived.go +++ b/hived/hived.go @@ -2,35 +2,41 @@ package main import ( "context" - "crypto/tls" "encoding/json" "errors" "flag" "fmt" + "io" "net/http" "net/url" "os" - "os/signal" + "path/filepath" "strconv" + "strings" "sync" "time" + "github.com/BurntSushi/toml" "github.com/Knetic/govaluate" "github.com/go-redis/redis/v8" - "github.com/gorilla/mux" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" + "github.com/pocketbase/pocketbase/plugins/ghupdate" + "github.com/pocketbase/pocketbase/plugins/jsvm" + "github.com/pocketbase/pocketbase/plugins/migratecmd" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - pb "github.com/terminaldweller/grpc/telebot/v1" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/timestamppb" ) const ( cryptocomparePriceURL = "https://min-api.cryptocompare.com/data/price?" polygonCryptoTickerURL = "https://api.polygon.io/v2/snapshot/locale/global/markets/crypto/tickers" cmcCryptoTickerURL = "https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest" - telegramBotTokenEnvVar = "TELEGRAM_BOT_TOKEN" //nolint: gosec - serverDeploymentType = "SERVER_DEPLOYMENT_TYPE" + coingeckoAPIURLv3 = "https://api.coingecko.com/api/v3" + coincapAPIURLv2 = "https://api.coincap.io/v2" httpClientTimeout = 5 getTimeout = 5 serverTLSReadTimeout = 15 @@ -61,6 +67,86 @@ var ( errUnknownDeploymentKind = errors.New("unknown deployment kind") ) +type HivedConfig struct { + KeydbAddress string `toml:"keydbAddress"` + KeydbPassword string `toml:"keydbPassword"` + KeydbDB int `toml:"keydbDB"` + AlertsCheckInterval int64 `toml:"alertsCheckInterval"` + TickerCheckInterval int64 `toml:"tickerCheckInterval"` + CacheDuration int64 `toml:"cacheDuration"` + TelegramChannelID int64 `toml:"telegramChannelID"` +} + +type appWrapper struct { + app *pocketbase.PocketBase +} + +// https://docs.coincap.io/ +type CoinCapAssetGetResponseData struct { + ID string `json:"id"` + Rank string `json:"rank"` + Symbol string `json:"symbol"` + Name string `json:"name"` + Supply string `json:"supply"` + MaxSupply string `json:"maxSupply"` + MarketCapUsd string `json:"marketCapUsd"` + VolumeUsd24Hr string `json:"volumeUsd24Hr"` + PriceUsd string `json:"priceUsd"` + ChangePercent24Hr string `json:"changePercent24Hr"` + Vwap24Hr string `json:"vwap24Hr"` +} + +type RootCmds struct { + hooksDir string + hooksWatch bool + hooksPool int + hooksPoolSize int + migrationsDir string + automigrate bool + publicDir string + indexFallback bool + queryTimeout int +} + +type priceResponseData struct { + Name string `json:"name"` + Price float64 `json:"price"` + Unit string `json:"unit"` + Err string `json:"err"` + IsSuccessful bool `json:"isSuccessful"` +} + +type CoinCapAssetGetResponse struct { + Data CoinCapAssetGetResponseData `json:"data"` + TimeStamp int64 `json:"timestamp"` +} + +type HTTPHandlerFunc func(http.ResponseWriter, *http.Request) + +type HTTPHandler struct { + name string + function HTTPHandlerFunc +} + +func getTGBot() *tgbotapi.BotAPI { + token := os.Getenv("TELEGRAM_BOT_TOKEN") + fmt.Println("YYY:", token) + + bot, err := tgbotapi.NewBotAPI(token) + if err != nil { + log.Fatal().Err(err).Send() + } + + return bot +} + +func sendMessage(bot *tgbotapi.BotAPI, msgText string, channelID int64) error { + msg := tgbotapi.NewMessage(channelID, msgText) + _, err := bot.Send(msg) + + return err +} + func GetProxiedClient() *http.Client { transport := &http.Transport{ DisableKeepAlives: true, @@ -86,30 +172,13 @@ func addSecureHeaders(writer *http.ResponseWriter) { (*writer).Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") } -func sendToTg(address, msg string, channelID int64) { - conn, err := grpc.Dial(address, grpc.WithInsecure()) - if err != nil { - log.Fatal().Err(err) - } - defer conn.Close() - - c := pb.NewNotificationServiceClient(conn) +func sendToTg(msg string, channelID int64) { + tgbotapi := getTGBot() - ctx, cancel := context.WithTimeout(context.Background(), telegramTimeout*time.Second) - defer cancel() - - response, err := c.Notify( - ctx, - &pb.NotificationRequest{ - NotificationText: msg, - ChannelId: channelID, - RequestTime: timestamppb.Now(), - }) + err := sendMessage(tgbotapi, msg, channelID) if err != nil { - log.Error().Err(err) + log.Info().Err(err) } - - log.Info().Msg(fmt.Sprintf("%v", response)) } type priceChanStruct struct { @@ -175,6 +244,8 @@ func getPriceFromCryptoCompare( "tsyms=" + url.QueryEscape(unit) path := cryptocomparePriceURL + params + log.Print(path) + client := GetProxiedClient() req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) @@ -326,7 +397,160 @@ func getPriceFromCMC( errChan <- errorChanStruct{hasError: false, err: nil} } -func PriceHandler(writer http.ResponseWriter, request *http.Request) { +func GetPriceFromCoinGecko( + ctx context.Context, + name, unit string, + wg *sync.WaitGroup, + priceChan chan<- priceChanStruct, + errChan chan<- errorChanStruct, +) { + defer wg.Done() + + priceFloat := 0. + + params := "/simple/price?ids=" + url.QueryEscape(name) + "&" + + "vs_currencies=" + url.QueryEscape(unit) + path := coingeckoAPIURLv3 + params + + client := GetProxiedClient() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err) + + return + } + + resp, err := client.Do(req) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + } + + jsonBody := make(map[string]interface{}) + + err = json.Unmarshal(body, &jsonBody) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + } + + price, isOk := jsonBody[name].(map[string]interface{}) + if !isOk { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err) + + return + } + + log.Info().Msg(string(body)) + + priceFloat, isOk = price[unit].(float64) + if !isOk { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err) + + return + } + + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: false, err: nil} +} + +func GetPriceFromCoinCap( + ctx context.Context, + name string, + wg *sync.WaitGroup, + priceChan chan<- priceChanStruct, + errChan chan<- errorChanStruct, +) { + defer wg.Done() + + priceFloat := 0. + + params := "/assets/" + url.QueryEscape(name) + path := coincapAPIURLv2 + params + + client := GetProxiedClient() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err) + + return + } + + resp, err := client.Do(req) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err) + } + + var coinCapAssetGetResponse CoinCapAssetGetResponse + + err = json.Unmarshal(body, &coinCapAssetGetResponse) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + } + + priceFloat, err = strconv.ParseFloat(coinCapAssetGetResponse.Data.PriceUsd, 64) + if err != nil { + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: true, err: err} + + log.Error().Err(err).Send() + } + + log.Info().Msg(string(body)) + + priceChan <- priceChanStruct{name: name, price: priceFloat} + errChan <- errorChanStruct{hasError: false, err: nil} +} + +func (aw appWrapper) PriceHandler(echoCtx echo.Context) error { + writer := echoCtx.Response().Writer + request := echoCtx.Request() writer.Header().Add("Content-Type", "application/json") if request.Method != http.MethodGet { @@ -361,7 +585,7 @@ func PriceHandler(writer http.ResponseWriter, request *http.Request) { http.Error(writer, "internal server error", http.StatusInternalServerError) } - return + return nil } var waitGroup sync.WaitGroup @@ -407,19 +631,26 @@ func PriceHandler(writer http.ResponseWriter, request *http.Request) { if err != nil { log.Error().Err(err) http.Error(writer, "internal server error", http.StatusInternalServerError) + + return err } + + return nil } -func PairHandler(w http.ResponseWriter, r *http.Request) { +func (aw appWrapper) PairHandler(echoCtx echo.Context) error { var err error - w.Header().Add("Content-Type", "application/json") + writer := echoCtx.Response().Writer + request := echoCtx.Request() - if r.Method != http.MethodGet { - http.Error(w, "Method is not supported.", http.StatusNotFound) + writer.Header().Add("Content-Type", "application/json") + + if request.Method != http.MethodGet { + http.Error(writer, "Method is not supported.", http.StatusNotFound) } - addSecureHeaders(&w) + addSecureHeaders(&writer) var one string @@ -427,7 +658,7 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { var multiplier float64 - params := r.URL.Query() + params := request.URL.Query() for key, value := range params { switch key { case "one": @@ -456,7 +687,7 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { defer close(priceChan) defer close(errChan) - ctx, cancel := context.WithTimeout(r.Context(), getTimeout*time.Second) + ctx, cancel := context.WithTimeout(request.Context(), getTimeout*time.Second) defer cancel() waitGroup.Add(2) //nolint: mnd,gomnd @@ -500,11 +731,15 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { log.Info().Msg(fmt.Sprintf("%v", ratio)) - err = json.NewEncoder(w).Encode(map[string]interface{}{"ratio": ratio}) + err = json.NewEncoder(writer).Encode(map[string]interface{}{"ratio": ratio}) if err != nil { log.Error().Err(err).Send() - http.Error(w, "internal server error", http.StatusInternalServerError) + http.Error(writer, "internal server error", http.StatusInternalServerError) + + return err } + + return nil } type alertType struct { @@ -563,7 +798,7 @@ func getTickers() tickersType { return tickers } -func alertManagerWorker(alert alertType) { +func alertManagerWorker(alert alertType, tgChannelID int64) { expression, err := govaluate.NewEvaluableExpression(alert.Expr) if err != nil { log.Error().Err(err) @@ -633,47 +868,44 @@ func alertManagerWorker(alert alertType) { return } - token := os.Getenv(telegramBotTokenEnvVar) msgText := "notification " + alert.Expr + " has been triggered" - tokenInt, err := strconv.ParseInt(token[1:len(token)-1], 10, 64) - if err == nil { log.Error().Err(err) } - sendToTg("telebot:8000", msgText, tokenInt) + sendToTg(msgText, tgChannelID) } -func alertManager(alertsCheckInterval int64) { +func alertManager(alertsCheckInterval int64, tgChannelID int64) { for { alerts := getAlerts() log.Info().Msg(fmt.Sprintf("%v", alerts)) for alertIndex := range alerts.Alerts { - go alertManagerWorker(alerts.Alerts[alertIndex]) + go alertManagerWorker(alerts.Alerts[alertIndex], tgChannelID) } time.Sleep(time.Second * time.Duration(alertsCheckInterval)) } } -func tickerManager(tickerCheckInterval int64) { +func tickerManager(tickerCheckInterval int64, tgChannelID int64) { for { tickers := getTickers() log.Info().Msg(fmt.Sprintf("%v", tickers)) for tickerIndex := range tickers.Tickers { - go tickerManagerWorker(tickers.Tickers[tickerIndex]) + go tickerManagerWorker(tickers.Tickers[tickerIndex], tgChannelID) } time.Sleep(time.Second * time.Duration(tickerCheckInterval)) } } -func tickerManagerWorker(ticker tickerType) { +func tickerManagerWorker(ticker tickerType, tgChannelID int64) { var waitGroup sync.WaitGroup priceChan := make(chan priceChanStruct, 1) @@ -707,17 +939,11 @@ func tickerManagerWorker(ticker tickerType) { log.Error().Err(errBadLogic) } - token := os.Getenv(telegramBotTokenEnvVar) msgText := "ticker: " + ticker.Name + ":" + strconv.FormatFloat(price.price, 'f', -1, 64) - tokenInt, err := strconv.ParseInt(token[1:len(token)-1], 10, 64) log.Print(msgText) - if err == nil { - log.Error().Err(err) - } - - sendToTg("telebot:8000", msgText, tokenInt) + sendToTg(msgText, tgChannelID) } type addAlertJSONType struct { @@ -729,7 +955,10 @@ type tickerJSONType struct { Name string `json:"name"` } -func tickerHandler(writer http.ResponseWriter, request *http.Request) { +func (aw appWrapper) tickerHandler(echoCtx echo.Context) error { + writer := echoCtx.Response().Writer + request := echoCtx.Request() + addSecureHeaders(&writer) handler := Handler{rdb: rdb} @@ -748,9 +977,13 @@ func tickerHandler(writer http.ResponseWriter, request *http.Request) { default: http.Error(writer, "Method is not supported.", http.StatusNotFound) } + + return nil } -func healthHandler(writer http.ResponseWriter, request *http.Request) { +func (aw appWrapper) healthHandler(echoCtx echo.Context) error { + writer := echoCtx.Response().Writer + request := echoCtx.Request() var RedisError string var HivedError string @@ -799,117 +1032,197 @@ func healthHandler(writer http.ResponseWriter, request *http.Request) { if request.Method != http.MethodGet { http.Error(writer, "internal server error", http.StatusInternalServerError) log.Error().Err(err) + + return err } + + return nil } -func robotsHandler(writer http.ResponseWriter, _ *http.Request) { - writer.Header().Add("Content-Type", "text/plain") - addSecureHeaders(&writer) +func setupLogging() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix +} - _, err := writer.Write([]byte("User-Agents: *\nDisallow: /\n")) +func (aw appWrapper) postHandler(context echo.Context) error { + user, pass, ok := context.Request().BasicAuth() + if !ok { + return context.JSON(http.StatusUnauthorized, "unauthorized") //nolint: wrapcheck + } + + userRecord, err := aw.app.Dao().FindAuthRecordByUsername("users", user) if err != nil { - log.Error().Err(err) + return context.JSON(http.StatusUnauthorized, "unauthorized") //nolint: wrapcheck } - http.Error(writer, "internal server error", http.StatusInternalServerError) -} + if !userRecord.ValidatePassword(pass) { + return context.JSON(http.StatusUnauthorized, "unauthorized") //nolint: wrapcheck + } -func startServer(gracefulWait time.Duration, flagPort string) { - router := mux.NewRouter() + return context.JSON(http.StatusOK, "OK") //nolint: wrapcheck +} - cfg := &tls.Config{ - MinVersion: tls.VersionTLS13, - PreferServerCipherSuites: true, +func defaultPublicDir() string { + if strings.HasPrefix(os.Args[0], os.TempDir()) { + return "./pb_public" } - srv := &http.Server{ - Addr: "0.0.0.0:" + flagPort, - WriteTimeout: time.Second * serverTLSWriteTimeout, - ReadTimeout: time.Second * serverTLSReadTimeout, - Handler: router, - TLSConfig: cfg, - } + return filepath.Join(os.Args[0], "../pb_public") +} - router.HandleFunc("/crypto/v1/health", healthHandler) - router.HandleFunc("/crypto/v1/price", PriceHandler) - router.HandleFunc("/crypto/v1/pair", PairHandler) - router.HandleFunc("/crypto/v1/alert", alertHandler) - router.HandleFunc("/crypto/v1/ticker", tickerHandler) - router.HandleFunc("/crypto/v1/robots.txt", robotsHandler) +func setRootCmds(app *pocketbase.PocketBase) RootCmds { + var rootCmds RootCmds - go func() { - var certPath, keyPath string + app.RootCmd.PersistentFlags().StringVar( + &rootCmds.hooksDir, + "hooksDir", + "", + "the directory with the JS app hooks", + ) - switch os.Getenv(serverDeploymentType) { - case "deployment": - certPath = "/etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem" - keyPath = "/etc/letsencrypt/live/api.terminaldweller.com/privkey.pem" - case "test": - certPath = "/certs/server.cert" - keyPath = "/certs/server.key" - default: - log.Fatal().Err(errUnknownDeploymentKind) - } + app.RootCmd.PersistentFlags().BoolVar( + &rootCmds.hooksWatch, + "hooksWatch", + true, + "auto restart the app on pb_hooks file change", + ) - if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil { - log.Fatal().Err(err).Send() - } - }() + app.RootCmd.PersistentFlags().IntVar( + &rootCmds.hooksPool, + "hooksPool", + 25, + "the total prewarm goja.Runtime instances for the JS app hooks execution", + ) - c := make(chan os.Signal, 1) + app.RootCmd.PersistentFlags().StringVar( + &rootCmds.migrationsDir, + "migrationsDir", + "", + "the directory with the user defined migrations", + ) - signal.Notify(c, os.Interrupt) - <-c + app.RootCmd.PersistentFlags().BoolVar( + &rootCmds.automigrate, + "automigrate", + true, + "enable/disable auto migrations", + ) - ctx, cancel := context.WithTimeout(context.Background(), gracefulWait) - defer cancel() + app.RootCmd.PersistentFlags().StringVar( + &rootCmds.publicDir, + "publicDir", + defaultPublicDir(), + "the directory to serve static files", + ) - if err := srv.Shutdown(ctx); err != nil { - log.Error().Err(err) - } else { - log.Info().Msg("gracefully shut down the server") - } + app.RootCmd.PersistentFlags().BoolVar( + &rootCmds.indexFallback, + "indexFallback", + true, + "fallback the request to index.html on missing static path (eg. when pretty urls are used with SPA)", + ) + + app.RootCmd.PersistentFlags().IntVar( + &rootCmds.queryTimeout, + "queryTimeout", + 30, + "the default SELECT queries timeout in seconds", + ) + + return rootCmds } -func setupLogging() { - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix +func startPocketbaseApp() { + app := pocketbase.New() + + aw := appWrapper{app: app} + + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + e.Router.POST("/", aw.postHandler) + e.Router.GET("/health", aw.healthHandler) + e.Router.GET("/api/crypto/v1/price", aw.PriceHandler) + e.Router.GET("/api/crypto/v1/pair", aw.PairHandler) + + e.Router.GET("/api/crypto/v1/alert", aw.alertHandler) + e.Router.PUT("/api/crypto/v1/alert", aw.alertHandler) + e.Router.POST("/api/crypto/v1/alert", aw.alertHandler) + e.Router.PATCH("/api/crypto/v1/alert", aw.alertHandler) + e.Router.DELETE("/api/crypto/v1/alert", aw.alertHandler) + + e.Router.GET("/api/crypto/v1/ticker", aw.tickerHandler) + e.Router.PUT("/api/crypto/v1/ticker", aw.tickerHandler) + e.Router.POST("/api/crypto/v1/ticker", aw.tickerHandler) + e.Router.PATCH("/api/crypto/v1/ticker", aw.tickerHandler) + e.Router.DELETE("/api/crypto/v1/ticker", aw.tickerHandler) + + return nil + }) + + rootCmds := setRootCmds(app) + + err := app.RootCmd.ParseFlags(os.Args[1:]) + if err != nil { + log.Fatal().Err(err) + } + + jsvm.MustRegister(app, jsvm.Config{ + MigrationsDir: rootCmds.migrationsDir, + HooksDir: rootCmds.hooksDir, + HooksWatch: rootCmds.hooksWatch, + HooksPoolSize: rootCmds.hooksPoolSize, + }) + + migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{ + TemplateLang: migratecmd.TemplateLangJS, + Automigrate: rootCmds.automigrate, + Dir: rootCmds.migrationsDir, + }) + + ghupdate.MustRegister(app, app.RootCmd, ghupdate.Config{}) + + app.OnAfterBootstrap().PreAdd(func(_ *core.BootstrapEvent) error { + app.Dao().ModelQueryTimeout = time.Duration(rootCmds.queryTimeout) * time.Second + + return nil + }) + + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(rootCmds.publicDir), rootCmds.indexFallback)) + + return nil + }) + + if err := app.Start(); err != nil { + log.Fatal().Err(err) + } } func main() { - var gracefulWait time.Duration - - flagPort := flag.String("port", "8008", "determined the port the sercice runs on") - redisAddress := flag.String("redisaddress", "redis:6379", "determines the address of the redis instance") - redisPassword := flag.String("redispassword", "", "determines the password of the redis db") - redisDB := flag.Int64("redisdb", 0, "determines the db number") - alertsCheckInterval := flag.Int64( - "alertinterval", - alertCheckIntervalDefault, - "in seconds, the amount of time between alert checks") - tickerCheckInterval := flag.Int64( - "interval", - tickerCheckIntervalDefault, - "in seconds, the amount of time between alert checks") - - flag.DurationVar( - &gracefulWait, "gracefulwait", - time.Second*defaultGracefulShutdown, - "the duration to wait during the graceful shutdown") - - flag.Parse() + data, err := os.ReadFile("/hived/hived.toml") + if err != nil { + log.Fatal().Err(err) + } + + var config HivedConfig + + _, err = toml.Decode(string(data), &config) + if err != nil { + log.Fatal().Err(err) + } + + fmt.Println("XXX:", config) rdb = redis.NewClient(&redis.Options{ - Addr: *redisAddress, - Password: *redisPassword, - DB: int(*redisDB), + Addr: config.KeydbAddress, + Password: config.KeydbPassword, + DB: config.KeydbDB, }) defer rdb.Close() setupLogging() - go alertManager(*alertsCheckInterval) + go alertManager(config.AlertsCheckInterval, config.TelegramChannelID) - go tickerManager(*tickerCheckInterval) + go tickerManager(config.TickerCheckInterval, config.TelegramChannelID) - startServer(gracefulWait, *flagPort) + startPocketbaseApp() } diff --git a/hived/hived.toml b/hived/hived.toml new file mode 100644 index 0000000..abddb19 --- /dev/null +++ b/hived/hived.toml @@ -0,0 +1,7 @@ +keydbAddress = "keydb:6379" +keydbPassword = "" +keydbDB = 0 +alertsCheckInterval = 600 +tickerCheckInterval = 600 +cacheDuration = 600 +telegramChannelID = diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..f6afdd2 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,35 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + server_tokens off; + limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m; + server { + listen 443 ssl; + keepalive_timeout 60; + charset utf-8; + ssl_certificate /etc/letsencrypt/live/api.terminaldweller.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.terminaldweller.com/privkey.pem; + ssl_ciphers HIGH:!aNULL:!MD5:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_protocols TLSv1.3; + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 1d; + ssl_session_tickets off; + ssl_prefer_server_ciphers on; + sendfile on; + tcp_nopush on; + add_header X-Content-Type-Options "nosniff" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options SAMEORIGIN always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer"; + fastcgi_hide_header X-Powered-By; + + error_page 401 403 404 /404.html; + location / { + proxy_pass http://hived:8090; + } + } +} diff --git a/telebot/.golangci.yml b/telebot/.golangci.yml deleted file mode 100644 index c31a774..0000000 --- a/telebot/.golangci.yml +++ /dev/null @@ -1,19 +0,0 @@ -run: - concurrency: 16 - timeout: 5m - modules-download-mode: readonly - allow-parallel-runners: true - allow-serial-runners: true - go: '1.22' -linters-settings: - depguard: - rules: - srcs: - listMode: "Strict" - allow: - - $gostd - - github.com/go-telegram-bot-api/telegram-bot-api - - github.com/rs/zerolog/log - - github.com/terminaldweller/grpc/telebot/v1 - - golang.org/x/net/proxy - - google.golang.org/grpc diff --git a/telebot/Dockerfile b/telebot/Dockerfile deleted file mode 100644 index 5b573f1..0000000 --- a/telebot/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM golang:1.22-alpine3.19 as builder -RUN apk update && apk upgrade -RUN apk add go git -COPY go.* /telebot/ -RUN cd /telebot && go mod download -COPY *.go /telebot/ -RUN cd /telebot && go build - -FROM alpine:3.19 as certbuilder -RUN apk add openssl -WORKDIR /certs -RUN openssl req -nodes -new -x509 -subj="/C=US/ST=Denial/L=springfield/O=Dis/CN=localhost" -keyout server.key -out server.cert - -# FROM gcr.io/distroless/static-debian10 -FROM alpine:3.19 -COPY --from=certbuilder /certs /certs -COPY --from=builder /telebot/telebot /telebot/ -COPY ./docker-entrypoint.sh /telebot/ -ENV ALL_PROXY= -ENV HTTPS_PROXY= -ENTRYPOINT ["/telebot/docker-entrypoint.sh"] diff --git a/telebot/docker-entrypoint.sh b/telebot/docker-entrypoint.sh deleted file mode 100755 index c2a6d0a..0000000 --- a/telebot/docker-entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env sh -set -ex - -export $(cat /run/secrets/tg_bot_token) - -"/telebot/telebot" "$@" diff --git a/telebot/go.mod b/telebot/go.mod deleted file mode 100644 index 32444e9..0000000 --- a/telebot/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -module telebot - -go 1.22 - -require ( - github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible - github.com/rs/zerolog v1.31.0 - github.com/terminaldweller/grpc v1.0.3 - golang.org/x/net v0.19.0 - google.golang.org/grpc v1.59.0 -) - -require ( - github.com/golang/protobuf v1.5.3 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/technoweenie/multipartstreamer v1.0.1 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect - google.golang.org/protobuf v1.31.0 // indirect -) diff --git a/telebot/go.sum b/telebot/go.sum deleted file mode 100644 index d720e6d..0000000 --- a/telebot/go.sum +++ /dev/null @@ -1,149 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -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.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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/terminaldweller/grpc v1.0.3 h1:yCRm0HKRD4M87CBmmO5KjkSFRq4X2lzHLJRH1ApzeiE= -github.com/terminaldweller/grpc v1.0.3/go.mod h1:pYpuXZw8rHZwTABVEVZEfErFr+PyEhAaSrFm7y1yvTo= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/telebot/telebot.go b/telebot/telebot.go deleted file mode 100644 index 91eebcb..0000000 --- a/telebot/telebot.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "net" - "net/http" - "os" - "strconv" - "time" - - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" - "github.com/rs/zerolog/log" - pb "github.com/terminaldweller/grpc/telebot/v1" - "golang.org/x/net/proxy" - "google.golang.org/grpc" -) - -var botChannelID = flag.Int64( - "botchannelid", - 146328407, //nolint: mnd,gomnd - "determines the channel id the telgram bot should send messages to") - -const ( - telegramBotTokenEnvVar = "TELEGRAM_BOT_TOKEN" //nolint: gosec - httpClientTimeout = 5 -) - -type server struct { - pb.UnimplementedNotificationServiceServer -} - -func GetProxiedClient() (*http.Client, error) { - var isProxied bool - - proxyURL := os.Getenv("ALL_PROXY") - if proxyURL == "" { - proxyURL = os.Getenv("HTTPS_PROXY") - } - - if proxyURL == "" { - isProxied = false - } - - var dialerProxy proxy.Dialer - - var dialer net.Dialer - - var err error - - if isProxied { - dialerProxy, err = proxy.SOCKS5("tcp", proxyURL, nil, proxy.Direct) - if err != nil { - return nil, fmt.Errorf("[GetProxiedClient] : %w", err) - } - } else { - dialer = net.Dialer{ - Timeout: 5 * time.Second, //nolint: mnd,gomnd - } - - if err != nil { - return nil, fmt.Errorf("[GetProxiedClient] : %w", err) - } - } - - dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { - if isProxied { - netConn, err := dialerProxy.Dial(network, address) - if err == nil { - return netConn, nil - } - - return netConn, fmt.Errorf("[dialContext] : %w", err) - } else { - netConn, err := dialer.Dial(network, address) - if err == nil { - return netConn, nil - } - - return netConn, fmt.Errorf("[dialContext] : %w", err) - } - } - - transport := &http.Transport{ - DialContext: dialContext, - DisableKeepAlives: true, - } - client := &http.Client{ - Transport: transport, - Timeout: httpClientTimeout * time.Second, - CheckRedirect: nil, - Jar: nil, - } - - return client, nil -} - -func getTGBot() *tgbotapi.BotAPI { - token := os.Getenv(telegramBotTokenEnvVar) - - // client, err := GetProxiedClient() - // if err != nil { - // log.Fatal().Err(err) - // } - - // bot, err := tgbotapi.NewBotAPIWithClient(token[1:len(token)-1], client) - bot, err := tgbotapi.NewBotAPI(token[1 : len(token)-1]) - if err != nil { - log.Fatal().Err(err).Send() - } - - return bot -} - -func sendMessage(bot *tgbotapi.BotAPI, msgText string, channelID int64) error { - msg := tgbotapi.NewMessage(channelID, msgText) - _, err := bot.Send(msg) - - return err -} - -func (s *server) Notify( - ctx context.Context, - NotificationRequest *pb.NotificationRequest, -) (*pb.NotificationResponse, error) { - var err error - - tgbotapi := getTGBot() - - if NotificationRequest.ChannelId == 0 { - err = sendMessage(tgbotapi, NotificationRequest.NotificationText, *botChannelID) - } else { - err = sendMessage(tgbotapi, NotificationRequest.NotificationText, NotificationRequest.ChannelId) - } - - if err != nil { - return &pb.NotificationResponse{Error: err.Error(), IsOK: false}, err - } - - return &pb.NotificationResponse{Error: "", IsOK: true}, nil -} - -func startServer(port uint16) { - listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - log.Fatal().Err(err).Send() - } - - var opts []grpc.ServerOption - - grpcServer := grpc.NewServer(opts...) - pb.RegisterNotificationServiceServer(grpcServer, &server{}) - - if err := grpcServer.Serve(listener); err != nil { - log.Fatal().Err(err).Send() - } -} - -func main() { - flagPort := flag.String("port", "8000", "determines the port the service runs on") - flag.Parse() - - port, _ := strconv.Atoi(*flagPort) - - startServer(uint16(port)) -} diff --git a/test/endpoints.sh b/test/endpoints.sh index 2b73614..0e7a96c 100755 --- a/test/endpoints.sh +++ b/test/endpoints.sh @@ -1,22 +1,23 @@ -#!/usr/bin/env sh -set -e -set -x +#!/bin/sh +set -ex -curl -k -X GET "https://localhost:10008/crypto/v1/price?name=PEPE&unit=USD" -curl -k -X GET "https://localhost:10008/crypto/v1/pair?one=ETH&two=CAKE&multiplier=4.0" -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"alert1", "expr":"ETH>CAKE"}' https://localhost:10008/crypto/v1/alert +# curl -k -X GET "https://localhost:8009/crypto/v1/arb/gecko?name=ethereum&unit=usd" +# curl -k -X GET "https://localhost:8009/crypto/v1/arb/coincap?name=ethereum" +curl -k -X GET "https://localhost:10008/api/crypto/v1/price?name=PEPE&unit=USD" +curl -k -X GET "https://localhost:10008/api/crypto/v1/pair?one=ETH&two=CAKE&multiplier=4.0" +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"alert1", "expr":"ETH>CAKE"}' https://localhost:10008/api/crypto/v1/alert # alert -curl -k -X GET -H "Content-Type: application/json" "https://localhost:10008/crypto/v1/alert?key=alert1" -curl -k -X DELETE -H "Content-Type: application/json" "https://localhost:10008/crypto/v1/alert?key=alert1" -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"alert1", "expr":"ETH>CAKE"}' https://localhost:10008/crypto/v1/alert +curl -k -X GET -H "Content-Type: application/json" "https://localhost:10008/api/crypto/v1/alert?key=alert1" +curl -k -X DELETE -H "Content-Type: application/json" "https://localhost:10008/api/crypto/v1/alert?key=alert1" +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"alert1", "expr":"ETH>CAKE"}' https://localhost:10008/api/crypto/v1/alert # ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"ETH"}' https://localhost:10008/crypto/v1/ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"BTC"}' https://localhost:10008/crypto/v1/ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"XMR"}' https://localhost:10008/crypto/v1/ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"STX"}' https://localhost:10008/crypto/v1/ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"LINK"}' https://localhost:10008/crypto/v1/ticker -curl -k -X POST -H "Content-Type: application/json" -d '{"name":"PEPE"}' https://localhost:10008/crypto/v1/ticker -curl -k -X GET -H "Content-Type: application/json" https://localhost:10008/crypto/v1/ticker?key=ETH -curl -k -X DELETE -H "Content-Type: application/json" https://localhost:10008/crypto/v1/ticker?key=ETH +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"ETH"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"BTC"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"XMR"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"STX"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"LINK"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X POST -H "Content-Type: application/json" -d '{"name":"PEPE"}' https://localhost:10008/api/crypto/v1/ticker +curl -k -X GET -H "Content-Type: application/json" https://localhost:10008/api/crypto/v1/ticker?key=ETH +curl -k -X DELETE -H "Content-Type: application/json" https://localhost:10008/api/crypto/v1/ticker?key=ETH |