diff options
Diffstat (limited to '')
-rw-r--r-- | .gitpod.yml | 5 | ||||
-rw-r--r-- | README.md | 53 | ||||
-rw-r--r-- | api/hived.postman_collection.json | 255 | ||||
-rw-r--r-- | api/swagger.yaml | 155 | ||||
-rw-r--r-- | arbiter/.golangci.yml | 18 | ||||
-rw-r--r-- | arbiter/arbiter.go | 73 | ||||
-rw-r--r-- | arbiter/go.mod | 2 | ||||
-rw-r--r-- | docker-compose-test.yaml | 116 | ||||
-rw-r--r-- | docker-compose.yaml | 8 | ||||
-rw-r--r-- | hived/.golangci.yml | 22 | ||||
-rwxr-xr-x | hived/docker-entrypoint.sh | 6 | ||||
-rw-r--r-- | hived/go.mod | 2 | ||||
-rw-r--r-- | hived/hived.go | 45 | ||||
-rw-r--r-- | telebot/.golangci.yml | 19 | ||||
-rw-r--r-- | telebot/go.mod | 2 | ||||
-rw-r--r-- | telebot/telebot.go | 21 |
16 files changed, 243 insertions, 559 deletions
diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index f15c853..0000000 --- a/.gitpod.yml +++ /dev/null @@ -1,5 +0,0 @@ -image: - file: .gitpod.Dockerfile - -tasks: - - init: go mod download @@ -1,81 +1,64 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/terminaldweller/hived)](https://goreportcard.com/report/github.com/terminaldweller/hived) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/1e67ac7026904cddb55ede7097995ad8)](https://www.codacy.com/gh/terminaldweller/hived/dashboard?utm_source=github.com&utm_medium=referral&utm_content=terminaldweller/hived&utm_campaign=Badge_Grade) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/1e67ac7026904cddb55ede7097995ad8)](https://www.codacy.com/gh/terminaldweller/hived/dashboard?utm_source=github.com&utm_medium=referral&utm_content=terminaldweller/hived&utm_campaign=Badge_Grade) # hived -`hived` is the second version of my personal cryptocurrency server:<br/> -* hived is currently using redis as its DB because its tiny and fast.<br/> -* It sends notifications through telegram.<br/> -* telebot and hived talk with grpc.<br/> + +`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/> -## How to Run -Before you can run this, you need a [telegram bot token](https://core.telegram.org/bots#6-botfather) and a [changelly](https://changelly.com/) API key.<br/> -The keys are put in files and then given to Docker as secrets.The docker entrypoint script then exports these as environment variables.<br/> +Returns the health status of the service.<br/> ```sh TELEGRAM_BOT_TOKEN="my-telegram-bot-api-key" ``` -And -```sh -CHANGELLY_API_KEY:"my-changelly-api-key" -``` -And -```sh -CHANGELLY_API_SECRET:"my-changelly-api-secret" -``` -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/> + +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 -`telebot` is the service that handles sending notifications through telegram. telebot uses grpc.<br/> -You can find the grpc repo for it [here](https://github.com/terminaldweller/grpc).<br/> - -## Gitpod -`hived` is gitpod-ready. Gitpod might need to install some go lsp tools once it is loaded. You will get prompted for those.<br/> - -## Docs -You can find the swagger and postman docs under `/api`.<br/> - -## TODO -* ~~fix travis~~ -* add unit tests -* ~~fix `hived -help` crashing~~ -* ~~haproxy~~ -* ~~turn the telegram bot into its own microservice~~ -* update openapi3.0 spec and postman -* ~~telegram bot's endpoint should be gRPC~~ diff --git a/api/hived.postman_collection.json b/api/hived.postman_collection.json deleted file mode 100644 index f025c5b..0000000 --- a/api/hived.postman_collection.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "info": { - "_postman_id": "75c431f8-b05d-4706-a6a8-e7ba5b36b2fe", - "name": "hived", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "price", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/price?name=CAKE&unit=USD", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "price" - ], - "query": [ - { - "key": "name", - "value": "CAKE" - }, - { - "key": "unit", - "value": "USD" - } - ] - } - }, - "response": [] - }, - { - "name": "pair", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/pair?one=BNB&two=CAKE&multiplier=41.56", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "pair" - ], - "query": [ - { - "key": "one", - "value": "BNB" - }, - { - "key": "two", - "value": "CAKE" - }, - { - "key": "multiplier", - "value": "41.56" - } - ] - } - }, - "response": [] - }, - { - "name": "alert-post", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"alert4\",\r\n \"expr\":\"ETH>CAKE\"\r\n}" - }, - "url": { - "raw": "http://127.0.0.1:8008/alert", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "alert" - ] - } - }, - "response": [] - }, - { - "name": "alert-put", - "request": { - "method": "PUT", - "header": [], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"alert4\",\r\n \"expr\":\"ETH<CAKE\"\r\n}" - }, - "url": { - "raw": "http://127.0.0.1:8008/alert", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "alert" - ] - } - }, - "response": [] - }, - { - "name": "alert-patch", - "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{\r\n \"name\":\"alert4\",\r\n \"expr\":\"ETH==CAKE\"\r\n}" - }, - "url": { - "raw": "http://127.0.0.1:8008/alert", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "alert" - ] - } - }, - "response": [] - }, - { - "name": "alert-delete", - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/alert?key=alert4", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "alert" - ], - "query": [ - { - "key": "key", - "value": "alert4" - } - ] - } - }, - "response": [] - }, - { - "name": "alert-get", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/alert?key=alert4", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "alert" - ], - "query": [ - { - "key": "key", - "value": "alert4" - } - ] - } - }, - "response": [] - }, - { - "name": "ex", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/ex", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "ex" - ] - } - }, - "response": [] - }, - { - "name": "health", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://127.0.0.1:8008/health", - "protocol": "http", - "host": [ - "127", - "0", - "0", - "1" - ], - "port": "8008", - "path": [ - "health" - ] - } - }, - "response": [] - } - ] -}
\ No newline at end of file diff --git a/api/swagger.yaml b/api/swagger.yaml deleted file mode 100644 index fb59e08..0000000 --- a/api/swagger.yaml +++ /dev/null @@ -1,155 +0,0 @@ -openapi: 3.0.0 -info: - version: 1.0.0-oas3 - title: hived - description: hived's API -servers: - - description: SwaggerHub API Auto Mocking - url: 'https://virtserver.swaggerhub.com/xashmith/hived/0.1' -paths: - /price: - get: - description: Returns the price of the crypto - parameters: - - name: name - in: query - description: the symbol of the cryptocurrency - schema: - type: string - - name: unit - in: query - description: the unit the return the price in - schema: - type: string - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - required: - - name - - unit - - price - properties: - name: - type: string - unit: - type: string - price: - type: number - /pair: - get: - description: Returns the ratio of one to two multiplied by a value - parameters: - - name: one - in: query - description: the name of the currency that's going to be multiplied - schema: - type: string - - name: two - in: query - description: the name of the second currency - schema: - type: string - - name: multiplier - in: query - description: the amount hte first currency is going to be multiplied - schema: - type: number - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - required: - - ratio - properties: - ratio: - type: number - /alerts: - post: - description: Add alerts to the alertmanager's list - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - name - - expr - properties: - name: - type: string - expr: - type: string - responses: - '200': - description: successful update - content: - application/json: - schema: - type: object - properties: - err: - type: string - isSuccessful: - type: boolean - delete: - description: Remove an alert from alertmanager's list - parameters: - - name : id - in: query - description: the name of the alert that should be deleted - schema: - type: string - responses: - '200': - description: successful delete - content: - application/json: - schema: - type: object - properties: - err: - type: string - isSuccessful: - type: string - /ex: - get: - description: Returns the list of currencies that are available for trade - responses: - '200': - description: seccussful update - content: - application/json: - schema: - type: object - properties: - err: - type: string - isSuccessful: - type: boolean - tradaeble: - type: array - items: - type: string - /health: - get: - description: Returns the health status of hived - responses: - '200': - description: successful response - content: - application/json: - schema: - type: object - properties: - isOK: - type: boolean - Err: - type: string diff --git a/arbiter/.golangci.yml b/arbiter/.golangci.yml new file mode 100644 index 0000000..878e031 --- /dev/null +++ b/arbiter/.golangci.yml @@ -0,0 +1,18 @@ +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/arbiter.go b/arbiter/arbiter.go index 8e2c999..6145a45 100644 --- a/arbiter/arbiter.go +++ b/arbiter/arbiter.go @@ -83,7 +83,7 @@ type errorChanStruct struct { err error } -func GetProxiedClient() (*http.Client, error) { +func GetProxiedClient() *http.Client { transport := &http.Transport{ DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, @@ -95,7 +95,7 @@ func GetProxiedClient() (*http.Client, error) { Jar: nil, } - return client, nil + return client } // OWASP: https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html @@ -108,22 +108,6 @@ func addSecureHeaders(writer *http.ResponseWriter) { (*writer).Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") } -// get price from binance. -// func getPriceFromBinance(name, unit string, -// wg *sync.WaitGroup, -// priceChan chan<- priceChanStruct, -// errChan chan<- errorChanStruct) { - -// } - -// get price from kucoin. -// func getPriceFromKu(name, uni string, -// wg *sync.WaitGroup, -// priceChan chan<- priceChanStruct, -// errChan chan<- errorChanStruct) { - -// } - func getPriceFromCoinGecko( ctx context.Context, name, unit string, @@ -139,15 +123,7 @@ func getPriceFromCoinGecko( "vs_currencies=" + url.QueryEscape(unit) path := coingeckoAPIURLv3 + params - client, err := GetProxiedClient() - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } + client := GetProxiedClient() req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) if err != nil { @@ -164,7 +140,7 @@ func getPriceFromCoinGecko( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() return } @@ -175,7 +151,7 @@ func getPriceFromCoinGecko( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() } jsonBody := make(map[string]interface{}) @@ -185,7 +161,7 @@ func getPriceFromCoinGecko( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() } price, isOk := jsonBody[name].(map[string]interface{}) @@ -216,7 +192,7 @@ func getPriceFromCoinGecko( func getPriceFromCoinCap( ctx context.Context, - name, unit string, + name string, wg *sync.WaitGroup, priceChan chan<- priceChanStruct, errChan chan<- errorChanStruct, @@ -228,15 +204,7 @@ func getPriceFromCoinCap( params := "/assets/" + url.QueryEscape(name) path := coincapAPIURLv2 + params - client, err := GetProxiedClient() - if err != nil { - priceChan <- priceChanStruct{name: name, price: priceFloat} - errChan <- errorChanStruct{hasError: true, err: err} - - log.Error().Err(err) - - return - } + client := GetProxiedClient() req, err := http.NewRequestWithContext(ctx, http.MethodGet, path, nil) if err != nil { @@ -253,7 +221,7 @@ func getPriceFromCoinCap( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() return } @@ -274,7 +242,7 @@ func getPriceFromCoinCap( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() } priceFloat, err = strconv.ParseFloat(coinCapAssetGetResponse.Data.PriceUsd, 64) @@ -282,7 +250,7 @@ func getPriceFromCoinCap( priceChan <- priceChanStruct{name: name, price: priceFloat} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() } log.Info().Msg(string(body)) @@ -336,7 +304,7 @@ func arbHandler(w http.ResponseWriter, r *http.Request) { log.Error().Err(err.err) } default: - log.Error().Err(errBadLogic) + log.Error().Err(errBadLogic).Send() } var price priceChanStruct @@ -380,17 +348,13 @@ func coincapHandler(w http.ResponseWriter, r *http.Request) { 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) + log.Error().Err(errUnexpectedParam).Send() } } @@ -404,8 +368,7 @@ func coincapHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), getTimeout*time.Second) defer cancel() - //nolint:contextcheck - getPriceFromCoinCap(ctx, name, unit, &waitGroup, priceChan, errChan) + getPriceFromCoinCap(ctx, name, &waitGroup, priceChan, errChan) waitGroup.Wait() select { @@ -436,8 +399,8 @@ func coincapHandler(w http.ResponseWriter, r *http.Request) { jsonResp, err := json.Marshal(responseData) if err != nil { cancel() - //nolint:gocritic - log.Fatal().Err(err) + + log.Fatal().Err(err).Send() } _, err = w.Write(jsonResp) @@ -468,7 +431,7 @@ func startServer(gracefulWait time.Duration, TLSConfig: cfg, } - for i := 0; i < len(handlers); i++ { + for i := range len(handlers) { route.HandleFunc(handlers[i].name, handlers[i].function) } @@ -483,7 +446,7 @@ func startServer(gracefulWait time.Duration, certPath = "/certs/server.cert" keyPath = "/certs/server.key" default: - log.Error().Err(errUnknownDeployment) + log.Error().Err(errUnknownDeployment).Send() } if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil { diff --git a/arbiter/go.mod b/arbiter/go.mod index 0946e53..a7a87c6 100644 --- a/arbiter/go.mod +++ b/arbiter/go.mod @@ -1,6 +1,6 @@ module arbiter -go 1.22.3 +go 1.22 require ( github.com/go-redis/redis/v8 v8.11.5 diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 613ee61..6b7367a 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -1,15 +1,78 @@ 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: + resources: + limits: + memory: 128M + logging: + driver: "json-file" + options: + max-size: "100m" + ports: + - "127.0.0.1:8090:443" + networks: + - authnet + restart: unless-stopped + cap_drop: + - ALL + cap_add: + - CHOWN + - DAC_OVERRIDE + - SETGID + - SETUID + - NET_BIND_SERVICE + volumes: + - ./auth/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 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 + - apinet + - dbnet - telenet ports: - - "10008:8008" + - "127.0.0.1:10008:8008" depends_on: - keydb - telebot @@ -18,19 +81,27 @@ services: - ALL environment: - SERVER_DEPLOYMENT_TYPE=test - - POLYGON_API_KEY= - HIVED_PRICE_SOURCE=cmc - 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: - - "10009:8000" + - "127.0.0.1:10009:8000" entrypoint: ["/telebot/docker-entrypoint.sh"] cap_drop: - ALL @@ -40,10 +111,20 @@ services: image: arbiter build: context: ./arbiter + deploy: + resources: + limits: + memory: 256M + logging: + driver: "json-file" + options: + max-size: "100m" networks: - - mainnet + - apinet + - dbnet + - telenet ports: - - "8009:8009" + - "127.0.0.1:8009:8009" entrypoint: ["/arbiter/arbiter"] cap_drop: - ALL @@ -51,21 +132,34 @@ services: - SERVER_DEPLOYMENT_TYPE=test 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 + - dbnet ports: - - "6380:6379" + - "127.0.0.1:6380:6379" environment: - ALLOW_EMPTY_PASSWORD=yes # volumes: # - keydb-data:/data/ networks: - mainnet: - driver: bridge + authnet: + dbnet: telenet: - driver: bridge + apinet: secrets: tg_bot_token: file: ./tgtoken + polygon_api_key: + file: ./polygon_api_key + cmc_api_key: + file: ./cmc_api_key volumes: keydb-data: + pb-vault: diff --git a/docker-compose.yaml b/docker-compose.yaml index f12160f..23e7d32 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,8 +4,6 @@ services: build: context: ./hived secrets: - - ch_api_key - - ch_api_secret - tg_bot_token networks: - mainnet @@ -25,6 +23,8 @@ services: environment: - SERVER_DEPLOYMENT_TYPE=deployment - POLYGON_API_KEY= + - CRYPTOCOMPARE_API_KEY= + - HIVED_PRICE_SOURCE= telebot: image: telebot build: @@ -76,9 +76,5 @@ networks: secrets: tg_bot_token: file: ./tgtoken - ch_api_key: - file: ./changelly_api_key - ch_api_secret: - file: ./changelly_api_secret volumes: redis-data: diff --git a/hived/.golangci.yml b/hived/.golangci.yml new file mode 100644 index 0000000..5e62ebd --- /dev/null +++ b/hived/.golangci.yml @@ -0,0 +1,22 @@ +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/Knetic/govaluate + - github.com/go-redis/redis/v8 + - github.com/gorilla/mux + - github.com/rs/zerolog + - github.com/rs/zerolog/log + - github.com/terminaldweller/grpc/telebot/v1 + - google.golang.org/grpc + - google.golang.org/protobuf/types/known/timestamppb diff --git a/hived/docker-entrypoint.sh b/hived/docker-entrypoint.sh index 932c9c9..0e41826 100755 --- a/hived/docker-entrypoint.sh +++ b/hived/docker-entrypoint.sh @@ -1,8 +1,6 @@ -#!/usr/bin/env sh +#!/bin/sh set -ex -export $(cat /run/secrets/tg_bot_token) -export $(cat /run/secrets/ch_api_key) -export $(cat /run/secrets/ch_api_secret) +export "$(cat /run/secrets/tg_bot_token)" "/hived/hived" "$@" diff --git a/hived/go.mod b/hived/go.mod index fe360c4..dead3e3 100644 --- a/hived/go.mod +++ b/hived/go.mod @@ -1,6 +1,6 @@ module hived -go 1.22.3 +go 1.22 require ( github.com/Knetic/govaluate v3.0.0+incompatible diff --git a/hived/hived.go b/hived/hived.go index 6d3cdfb..f2764a4 100644 --- a/hived/hived.go +++ b/hived/hived.go @@ -137,9 +137,9 @@ func getPrice(ctx context.Context, case "cryptocompare": getPriceFromCryptoCompare(ctx, name, unit, waitGroup, priceChan, errChan) case "polygon": - getPriceFromPolygon(ctx, name, unit, waitGroup, priceChan, errChan) + getPriceFromPolygon(ctx, name, waitGroup, priceChan, errChan) case "cmc": - getPriceFromCMC(ctx, name, unit, waitGroup, priceChan, errChan) + getPriceFromCMC(ctx, name, waitGroup, priceChan, errChan) } } else { priceChan <- priceChanStruct{name: name, price: val} @@ -159,7 +159,7 @@ func getPriceFromCryptoCompareErrorHandler( priceChan <- priceChanStruct{name: name, price: defaultPrice} errChan <- errorChanStruct{hasError: true, err: err} - log.Error().Err(err) + log.Error().Err(err).Send() } func getPriceFromCryptoCompare( @@ -184,6 +184,10 @@ func getPriceFromCryptoCompare( return } + apiKey := os.Getenv("CRYPTOCOMPARE_API_KEY") + + req.Header.Set("Apikey", apiKey) + resp, err := client.Do(req) if err != nil { getPriceFromCryptoCompareErrorHandler(err, name, priceChan, errChan) @@ -201,7 +205,6 @@ func getPriceFromCryptoCompare( // add a price cache err = rdb.Set(ctx, name+"_price", jsonBody[unit], time.Duration(*cacheDuration*redisCacheDurationMultiplier)).Err() - if err != nil { log.Error().Err(err) } @@ -212,7 +215,7 @@ func getPriceFromCryptoCompare( func getPriceFromPolygon( ctx context.Context, - name, unit string, + name string, wg *sync.WaitGroup, priceChan chan<- priceChanStruct, errChan chan<- errorChanStruct, @@ -254,9 +257,9 @@ func getPriceFromPolygon( log.Print(jsonBody) price := jsonBody.Ticker.Min.O + // add a price cache err = rdb.Set(ctx, name+"_price", price, time.Duration(*cacheDuration*redisCacheDurationMultiplier)).Err() - if err != nil { log.Error().Err(err) } @@ -267,7 +270,7 @@ func getPriceFromPolygon( func getPriceFromCMC( ctx context.Context, - name, unit string, + name string, wg *sync.WaitGroup, priceChan chan<- priceChanStruct, errChan chan<- errorChanStruct, @@ -315,7 +318,6 @@ func getPriceFromCMC( } err = rdb.Set(ctx, name+"_price", price, time.Duration(*cacheDuration*redisCacheDurationMultiplier)).Err() - if err != nil { log.Error().Err(err) } @@ -448,8 +450,8 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { var waitGroup sync.WaitGroup - priceChan := make(chan priceChanStruct, 2) //nolint: gomnd - errChan := make(chan errorChanStruct, 2) //nolint: gomnd + priceChan := make(chan priceChanStruct, 2) //nolint: mnd,gomnd + errChan := make(chan errorChanStruct, 2) //nolint: mnd,gomnd defer close(priceChan) defer close(errChan) @@ -457,14 +459,14 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), getTimeout*time.Second) defer cancel() - waitGroup.Add(2) //nolint: gomnd + waitGroup.Add(2) //nolint: mnd,gomnd go getPrice(ctx, one, "USD", &waitGroup, priceChan, errChan) go getPrice(ctx, two, "USD", &waitGroup, priceChan, errChan) waitGroup.Wait() - for i := 0; i < 2; i++ { + for range 2 { select { case err := <-errChan: if err.hasError { @@ -479,7 +481,7 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { var priceTwo float64 - for i := 0; i < 2; i++ { + for range 2 { select { case price := <-priceChan: if price.name == one { @@ -500,7 +502,7 @@ func PairHandler(w http.ResponseWriter, r *http.Request) { err = json.NewEncoder(w).Encode(map[string]interface{}{"ratio": ratio}) if err != nil { - log.Error().Err(err) + log.Error().Err(err).Send() http.Error(w, "internal server error", http.StatusInternalServerError) } } @@ -561,7 +563,6 @@ func getTickers() tickersType { return tickers } -// FIXME- there is a crash here func alertManagerWorker(alert alertType) { expression, err := govaluate.NewEvaluableExpression(alert.Expr) if err != nil { @@ -590,18 +591,18 @@ func alertManagerWorker(alert alertType) { waitGroup.Wait() - for i := 0; i < len(vars); i++ { + for range len(vars) { select { case err := <-errChan: if err.hasError { log.Printf(err.err.Error()) } default: - log.Error().Err(errBadLogic) + log.Error().Err(errBadLogic).Send() } } - for i := 0; i < len(vars); i++ { + for range len(vars) { select { case price := <-priceChan: parameters[price.name] = price.price @@ -640,6 +641,7 @@ func alertManagerWorker(alert alertType) { if err == nil { log.Error().Err(err) } + sendToTg("telebot:8000", msgText, tokenInt) } @@ -709,11 +711,12 @@ func tickerManagerWorker(ticker tickerType) { msgText := "ticker: " + ticker.Name + ":" + strconv.FormatFloat(price.price, 'f', -1, 64) tokenInt, err := strconv.ParseInt(token[1:len(token)-1], 10, 64) - fmt.Println(msgText) + log.Print(msgText) if err == nil { log.Error().Err(err) } + sendToTg("telebot:8000", msgText, tokenInt) } @@ -799,7 +802,7 @@ func healthHandler(writer http.ResponseWriter, request *http.Request) { } } -func robotsHandler(writer http.ResponseWriter, r *http.Request) { +func robotsHandler(writer http.ResponseWriter, _ *http.Request) { writer.Header().Add("Content-Type", "text/plain") addSecureHeaders(&writer) @@ -849,7 +852,7 @@ func startServer(gracefulWait time.Duration, flagPort string) { } if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil { - log.Fatal().Err(err) + log.Fatal().Err(err).Send() } }() diff --git a/telebot/.golangci.yml b/telebot/.golangci.yml new file mode 100644 index 0000000..c31a774 --- /dev/null +++ b/telebot/.golangci.yml @@ -0,0 +1,19 @@ +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/go.mod b/telebot/go.mod index 89df502..32444e9 100644 --- a/telebot/go.mod +++ b/telebot/go.mod @@ -1,6 +1,6 @@ module telebot -go 1.22.3 +go 1.22 require ( github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible diff --git a/telebot/telebot.go b/telebot/telebot.go index d4a8255..91eebcb 100644 --- a/telebot/telebot.go +++ b/telebot/telebot.go @@ -17,10 +17,9 @@ import ( "google.golang.org/grpc" ) -// FIXME-the client should provide the channel ID. var botChannelID = flag.Int64( "botchannelid", - 146328407, //nolint: gomnd + 146328407, //nolint: mnd,gomnd "determines the channel id the telgram bot should send messages to") const ( @@ -34,6 +33,7 @@ type server struct { func GetProxiedClient() (*http.Client, error) { var isProxied bool + proxyURL := os.Getenv("ALL_PROXY") if proxyURL == "" { proxyURL = os.Getenv("HTTPS_PROXY") @@ -43,19 +43,22 @@ func GetProxiedClient() (*http.Client, error) { isProxied = false } - var dialer_proxy proxy.Dialer + var dialerProxy proxy.Dialer + var dialer net.Dialer + var err error if isProxied { - dialer_proxy, err = proxy.SOCKS5("tcp", proxyURL, nil, proxy.Direct) + 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, + Timeout: 5 * time.Second, //nolint: mnd,gomnd } + if err != nil { return nil, fmt.Errorf("[GetProxiedClient] : %w", err) } @@ -63,7 +66,7 @@ func GetProxiedClient() (*http.Client, error) { dialContext := func(ctx context.Context, network, address string) (net.Conn, error) { if isProxied { - netConn, err := dialer_proxy.Dial(network, address) + netConn, err := dialerProxy.Dial(network, address) if err == nil { return netConn, nil } @@ -104,7 +107,7 @@ func getTGBot() *tgbotapi.BotAPI { // 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) + log.Fatal().Err(err).Send() } return bot @@ -141,7 +144,7 @@ func (s *server) Notify( func startServer(port uint16) { listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) if err != nil { - log.Fatal().Err(err) + log.Fatal().Err(err).Send() } var opts []grpc.ServerOption @@ -150,7 +153,7 @@ func startServer(port uint16) { pb.RegisterNotificationServiceServer(grpcServer, &server{}) if err := grpcServer.Serve(listener); err != nil { - log.Fatal().Err(err) + log.Fatal().Err(err).Send() } } |