aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitpod.yml5
-rw-r--r--README.md53
-rw-r--r--api/hived.postman_collection.json255
-rw-r--r--api/swagger.yaml155
-rw-r--r--arbiter/.golangci.yml18
-rw-r--r--arbiter/arbiter.go73
-rw-r--r--arbiter/go.mod2
-rw-r--r--docker-compose-test.yaml116
-rw-r--r--docker-compose.yaml8
-rw-r--r--hived/.golangci.yml22
-rwxr-xr-xhived/docker-entrypoint.sh6
-rw-r--r--hived/go.mod2
-rw-r--r--hived/hived.go45
-rw-r--r--telebot/.golangci.yml19
-rw-r--r--telebot/go.mod2
-rw-r--r--telebot/telebot.go21
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
diff --git a/README.md b/README.md
index dcc2af2..8542d55 100644
--- a/README.md
+++ b/README.md
@@ -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()
}
}