aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorterminaldweller <thabogre@gmail.com>2021-02-25 08:49:18 +0000
committerterminaldweller <thabogre@gmail.com>2021-02-25 08:49:18 +0000
commitff12756915d452e7b92959062ed6315adf0e424e (patch)
treebb86d9405a8a63d4865594f20a13327920485cf8
parentremoved the expression parser. using a lib now. the addalert endpoint is work... (diff)
downloadhived-ff12756915d452e7b92959062ed6315adf0e424e.tar.gz
hived-ff12756915d452e7b92959062ed6315adf0e424e.zip
added a new endpoint for changelly. added somewhat decent logging. cleaned up the code a bit. the secrets are all environment variables now since we want publicly availale CI. the api and postman docs are added. added travis integration.
Diffstat (limited to '')
-rw-r--r--.travis.yml14
-rw-r--r--Dockerfile5
-rw-r--r--README.md47
-rw-r--r--api/hived.postman_collection.json64
-rw-r--r--api/swagger.yaml101
-rw-r--r--docker-compose.yaml11
-rwxr-xr-xdocker-entrypoint.sh8
-rw-r--r--go.mod1
-rw-r--r--go.sum6
-rw-r--r--hived.go235
-rwxr-xr-xtest/endpoints.sh7
11 files changed, 414 insertions, 85 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e51bcae
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+
+env:
+ - DOCKER_COMPOSE_VERSION=1.25.0
+
+services:
+ - docker
+
+before_install:
+ - docker pull redis:6.2-alpine
+
+install:
+ - docker build -t hived ./ && docker-compose up -d
+ - ./test/endpoints.sh
diff --git a/Dockerfile b/Dockerfile
index 1a37fa9..f43cefe 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,11 +2,12 @@ FROM alpine:3.13 as builder
RUN apk update && apk upgrade
RUN apk add go git
-COPY ./go.* /hived/
+COPY go.* /hived/
RUN cd /hived && go mod download
COPY *.go /hived/
RUN cd /hived && go build
FROM alpine:3.13
COPY --from=builder /hived/hived /hived/
-ENTRYPOINT ["/hived/hived"]
+COPY ./docker-entrypoint.sh /hived/
+ENTRYPOINT ["/hived/docker-entrypoint.sh"]
diff --git a/README.md b/README.md
index 7423e43..f04619a 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,47 @@
# hived
-hived in go
+`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/>
+Currently it has 4 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/>
+
+### /addalert
+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/>
+
+### /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/>
+
+## 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/>
+
+```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/>
+Both the server itself and the redis image are alpine-based so they're pretty small.<br/>
+
+## Docs
+You can find the swagger and postman docs under `/api`.<br/>
diff --git a/api/hived.postman_collection.json b/api/hived.postman_collection.json
new file mode 100644
index 0000000..3cab1e7
--- /dev/null
+++ b/api/hived.postman_collection.json
@@ -0,0 +1,64 @@
+{
+ "info": {
+ "_postman_id": "ca2c71c1-27a5-466c-a3c7-16b71f62af34",
+ "name": "hived",
+ "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "price",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": ""
+ },
+ "response": []
+ },
+ {
+ "name": "pair",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "http://127.0.0.1:8008/pair?one=ETH&two=CAKE&multiplier=4.0",
+ "protocol": "http",
+ "host": [
+ "127",
+ "0",
+ "0",
+ "1"
+ ],
+ "port": "8008",
+ "path": [
+ "pair"
+ ],
+ "query": [
+ {
+ "key": "one",
+ "value": "ETH"
+ },
+ {
+ "key": "two",
+ "value": "CAKE"
+ },
+ {
+ "key": "multiplier",
+ "value": "4.0"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "addalert",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": ""
+ },
+ "response": []
+ }
+ ],
+ "protocolProfileBehavior": {}
+} \ No newline at end of file
diff --git a/api/swagger.yaml b/api/swagger.yaml
new file mode 100644
index 0000000..9100e82
--- /dev/null
+++ b/api/swagger.yaml
@@ -0,0 +1,101 @@
+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
+ /addalerts:
+ 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
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 4285550..0b33516 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -7,6 +7,7 @@ services:
secrets:
- tg_bot_token
- ch_api_key
+ - ch_api_secret
networks:
- hivednet
restart: unless-stopped
@@ -14,6 +15,7 @@ services:
- "8008:8008"
depends_on:
- redis
+ entrypoint: /hived/docker-entrypoint.sh
redis:
image: redis:6.2-alpine
networks:
@@ -27,10 +29,15 @@ services:
- redis-data:/data/
networks:
hivednet:
+ driver: bridge
+ routenet:
+ driver: bridge
secrets:
tg_bot_token:
- file: ./tgtoken.json
+ file: ./tgtoken
ch_api_key:
- file: ./changelly_api_key.json
+ file: ./changelly_api_key
+ ch_api_secret:
+ file: ./changelly_api_secret
volumes:
redis-data:
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 0000000..6d88ff1
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+set -e
+
+export $(cat /run/secrets/tg_bot_token)
+export $(cat /run/secrets/ch_api_key)
+export $(cat /run/secrets/ch_api_secret)
+
+"/hived/hived"
diff --git a/go.mod b/go.mod
index 54eca8b..19dc810 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,6 @@ require (
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/go-redis/redis/v8 v8.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
+ github.com/rs/zerolog v1.20.0
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 62ad285..b97736b 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,7 @@ github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/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=
@@ -33,7 +34,11 @@ github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISq
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs=
+github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
@@ -71,6 +76,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/hived.go b/hived.go
index 37bf8d0..90b4b3b 100644
--- a/hived.go
+++ b/hived.go
@@ -1,14 +1,19 @@
package main
import (
+ "bytes"
"context"
+ "crypto/hmac"
+ "crypto/sha512"
+ "encoding/hex"
"encoding/json"
+ "errors"
"flag"
"fmt"
"io/ioutil"
- "log"
"net/http"
"net/url"
+ "os"
"strconv"
"sync"
"time"
@@ -16,79 +21,43 @@ import (
"github.com/Knetic/govaluate"
"github.com/go-redis/redis/v8"
"github.com/go-telegram-bot-api/telegram-bot-api"
+ "github.com/rs/zerolog"
+ "github.com/rs/zerolog/log"
)
var flagPort = flag.String("port", "8008", "determined the port the sercice runs on")
-var flagTgTokenFile = flag.String("tgtoken", "/run/secrets/tg_bot_token", "determines the location of the telegram bot token file")
-var changelllyAPIKeyFile = flag.String("chapikey", "/run/secrets/ch_api_key", "determines the file that holds the changelly api key")
+
var alertFile = flag.String("alertfile", "/run/secrets/alerts", "determines the locaiton of the alert files")
var alertsCheckInterval = flag.Int64("alertinterval", 600., "in seconds, the amount of time between alert checks")
var redisAddress = flag.String("redisaddress", "redis:6379", "determines the address of the redis instance")
var redisPassword = flag.String("redispassword", "", "determines the password of the redis db")
var redisDB = flag.Int64("redisdb", 0, "determines the db number")
+var botChannelID = flag.Int64("botchannelid", 146328407, "determines the channel id the telgram bot should send messages to")
const cryptocomparePriceURL = "https://min-api.cryptocompare.com/data/price?"
const changellyURL = "https://api.changelly.com"
-const botChannelID = 146328407
+const TELEGRAM_BOT_TOKEN_ENV_VAR = "TELEGRAM_BOT_TOKEN"
+const CHANGELLY_API_KEY_ENV_VAR = "CHANGELLY_API_KEY"
+const CHANGELLY_API_SECRET_ENV_VAR = "CHANGELLY_API_SECRET"
var getRedisClientOnce sync.Once
var getTGBotOnce sync.Once
-type TgToken struct {
- Token string `json:"token"`
-}
-
-func getTGToken() string {
- tgTokenJsonBytes, err := ioutil.ReadFile(*flagTgTokenFile)
- if err != nil {
- log.Fatal(err)
- }
-
- var tgToken TgToken
-
- err = json.Unmarshal(tgTokenJsonBytes, &tgToken)
+func runTgBot() {
+ // bot := getTgBot()
+ token := os.Getenv(TELEGRAM_BOT_TOKEN_ENV_VAR)
+ bot, err := tgbotapi.NewBotAPI(token[1 : len(token)-1])
if err != nil {
- log.Fatal(err)
+ log.Error().Err(err)
}
- return tgToken.Token
-}
-
-func getTgBot() *tgbotapi.BotAPI {
- var tgbot *tgbotapi.BotAPI
- getTGBotOnce.Do(func() {
- tgTokenJsonBytes, err := ioutil.ReadFile(*flagTgTokenFile)
- if err != nil {
- log.Fatal(err)
- }
-
- var tgToken TgToken
-
- err = json.Unmarshal(tgTokenJsonBytes, &tgToken)
- if err != nil {
- log.Fatal(err)
- }
-
- bot, err := tgbotapi.NewBotAPI(tgToken.Token)
- if err != nil {
- log.Panic(err)
- }
-
- bot.Debug = true
- tgbot = bot
- })
- return tgbot
-}
-
-func runTgBot() {
- bot := getTgBot()
- log.Printf("Authorized on account %s", bot.Self.UserName)
+ log.Debug().Msg("authorized on account bot_bloodstalker")
update := tgbotapi.NewUpdate(0)
update.Timeout = 60
updates, err := bot.GetUpdatesChan(update)
if err != nil {
- log.Panic(err)
+ log.Error().Err(err)
}
for update := range updates {
@@ -159,7 +128,7 @@ func sendGetToCryptoCompare(
func healthHandler(w http.ResponseWriter, r *http.Request) {
}
-func cryptoHandler(w http.ResponseWriter, r *http.Request) {
+func priceHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method is not supported.", http.StatusNotFound)
}
@@ -174,10 +143,18 @@ func cryptoHandler(w http.ResponseWriter, r *http.Request) {
case "unit":
unit = value[0]
default:
- log.Fatal("bad parameters for the crypto endpoint.")
+ log.Error().Err(errors.New("bad parameters for the crypto endpoint."))
}
}
+ if name == "" || unit == "" {
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "err": "query parameters must include name and unit",
+ "isSuccessful": false})
+ log.Error().Err(errors.New("query parameters must include name and unit."))
+ return
+ }
+
var wg sync.WaitGroup
priceChan := make(chan priceChanStruct, 1)
errChan := make(chan errorChanStruct, 1)
@@ -190,10 +167,10 @@ func cryptoHandler(w http.ResponseWriter, r *http.Request) {
select {
case err := <-errChan:
if err.hasError != false {
- log.Printf(err.err.Error())
+ log.Error().Err(err.err)
}
default:
- log.Fatal("this shouldnt have happened")
+ log.Error().Err(errors.New("this shouldn't have happened'"))
}
var price priceChanStruct
@@ -201,10 +178,15 @@ func cryptoHandler(w http.ResponseWriter, r *http.Request) {
case priceCh := <-priceChan:
price = priceCh
default:
- log.Fatal("this shouldnt have happened")
+ log.Fatal().Err(errors.New("this shouldnt have happened"))
}
- json.NewEncoder(w).Encode(map[string]interface{}{"name": price.name, "price": price.price, "unit": unit})
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "name": price.name,
+ "price": price.price,
+ "unit": unit,
+ "err": "",
+ "isSuccessful": true})
}
func pairHandler(w http.ResponseWriter, r *http.Request) {
@@ -226,13 +208,16 @@ func pairHandler(w http.ResponseWriter, r *http.Request) {
case "multiplier":
multiplier, err = strconv.ParseFloat(value[0], 64)
if err != nil {
- log.Fatal(err)
+ log.Fatal().Err(err)
}
default:
- log.Fatal("bad parameters for the pair endpoint.")
+ log.Fatal().Err(errors.New("unknown parameters for the pair endpoint."))
}
}
- fmt.Println(one, two, multiplier)
+
+ if one == "" || two == "" || multiplier == 0. {
+ log.Error().Err(errors.New("the query must include one()),two and multiplier"))
+ }
var wg sync.WaitGroup
priceChan := make(chan priceChanStruct, 2)
@@ -249,10 +234,10 @@ func pairHandler(w http.ResponseWriter, r *http.Request) {
select {
case err := <-errChan:
if err.hasError != false {
- log.Printf(err.err.Error())
+ log.Error().Err(err.err)
}
default:
- log.Fatal("this shouldnt have happened")
+ log.Fatal().Err(errors.New("this shouldnt have happened"))
}
}
@@ -268,7 +253,7 @@ func pairHandler(w http.ResponseWriter, r *http.Request) {
priceTwo = price.price
}
default:
- log.Fatal("this shouldnt have happened")
+ log.Fatal().Err(errors.New("this shouldnt have happened"))
}
}
@@ -286,6 +271,7 @@ type alertsType struct {
Alerts []alertType `json:"alerts"`
}
+//FIXME
func getRedisClient() *redis.Client {
var client *redis.Client
getRedisClientOnce.Do(func() {
@@ -325,6 +311,7 @@ func getAlerts() (alertsType, error) {
return alerts, nil
}
+//not being used
func getAlertsFromRedis() (alertsType, error) {
// rdb := getRedisClient()
rdb := redis.NewClient(&redis.Options{
@@ -335,11 +322,16 @@ func getAlertsFromRedis() (alertsType, error) {
ctx := context.Background()
val, err := rdb.Get(ctx, "alert").Result()
if err != nil {
- log.Printf(err.Error())
+ log.Error().Err(err)
return alertsType{}, err
}
fmt.Println(val)
+ err = rdb.Close()
+ if err != nil {
+ log.Error().Err(err)
+ }
+
return alertsType{}, nil
}
@@ -347,7 +339,7 @@ func alertManager() {
for {
alerts, err := getAlerts()
if err != nil {
- log.Printf(err.Error())
+ log.Error().Err(err)
return
}
fmt.Println(alerts)
@@ -355,7 +347,7 @@ func alertManager() {
for i := range alerts.Alerts {
expression, err := govaluate.NewEvaluableExpression(alerts.Alerts[i].Expr)
if err != nil {
- log.Printf(err.Error())
+ log.Error().Err(err)
continue
}
@@ -381,7 +373,7 @@ func alertManager() {
log.Printf(err.err.Error())
}
default:
- log.Fatal("this shouldnt have happened")
+ log.Error().Err(errors.New("this shouldnt have happened"))
}
}
@@ -390,27 +382,28 @@ func alertManager() {
case price := <-priceChan:
parameters[price.name] = price.price
default:
- log.Fatal("this shouldnt have happened")
+ log.Error().Err(errors.New("this shouldnt have happened"))
}
}
fmt.Println("parameters:", parameters)
result, err := expression.Evaluate(parameters)
if err != nil {
- log.Println(err.Error())
+ log.Error().Err(err)
}
var resultBool bool
fmt.Println("result:", result)
resultBool = result.(bool)
if resultBool == true {
- bot, err := tgbotapi.NewBotAPI(getTGToken())
+ // bot := getTgBot()
+ token := os.Getenv(TELEGRAM_BOT_TOKEN_ENV_VAR)
+ bot, err := tgbotapi.NewBotAPI(token[1 : len(token)-1])
if err != nil {
- log.Panic(err)
+ log.Error().Err(err)
}
- // bot := getTgBot()
msgText := "notification " + alerts.Alerts[i].Expr + " has been triggered"
- msg := tgbotapi.NewMessage(botChannelID, msgText)
+ msg := tgbotapi.NewMessage(*botChannelID, msgText)
bot.Send(msg)
}
}
@@ -438,32 +431,114 @@ func addAlertHandler(w http.ResponseWriter, r *http.Request) {
json.Unmarshal(bodyBytes, &bodyJSON)
fmt.Println(bodyJSON)
+ if bodyJSON.Name == "" || bodyJSON.Expr == "" {
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "isSuccessful": false,
+ "error": "not all parameters are valid."})
+ log.Fatal().Err(errors.New("not all parameters are valid."))
+ return
+ }
+
// rdb := getRedisClient()
rdb := redis.NewClient(&redis.Options{
- Addr: *redisAddress,
- Password: *redisPassword,
- DB: int(*redisDB),
- MinIdleConns: 1,
+ Addr: *redisAddress,
+ Password: *redisPassword,
+ DB: int(*redisDB),
})
ctx := context.Background()
key := "alert:" + bodyJSON.Name
rdb.Set(ctx, bodyJSON.Name, bodyJSON.Expr, 0)
rdb.SAdd(ctx, "alertkeys", key)
- json.NewEncoder(w).Encode(map[string]interface{}{"isSuccessful": true, "error": ""})
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "isSuccessful": true,
+ "error": ""})
+
+ err = rdb.Close()
+ if err != nil {
+ log.Error().Err(err)
+ }
+}
+
+func exHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "GET" {
+ http.Error(w, "Method is not supported.", http.StatusNotFound)
+ }
+
+ apiKey := os.Getenv(CHANGELLY_API_KEY_ENV_VAR)
+ apiSecret := os.Getenv(CHANGELLY_API_SECRET_ENV_VAR)
+
+ body := struct {
+ Jsonrpc string `json:"jsonrpc"`
+ Id string `json:"id"`
+ Method string `json:"method"`
+ Params []string `json:"params"`
+ }{
+ Jsonrpc: "2.0",
+ Id: "test",
+ Method: "getCurrencies",
+ Params: nil}
+
+ bodyJSON, err := json.Marshal(body)
+ if err != nil {
+ log.Error().Err(err)
+ }
+
+ secretBytes := []byte(apiSecret[1 : len(apiSecret)-1])
+ mac := hmac.New(sha512.New, secretBytes)
+ mac.Write(bodyJSON)
+
+ client := &http.Client{}
+ req, err := http.NewRequest("POST", changellyURL, bytes.NewReader(bodyJSON))
+ if err != nil {
+ log.Error().Err(err)
+ }
+
+ macDigest := hex.EncodeToString(mac.Sum(nil))
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("api-key", apiKey[1:len(apiKey)-1])
+ req.Header.Add("sign", macDigest)
+
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Error().Err(err)
+ }
+ defer resp.Body.Close()
+
+ responseBody, err := ioutil.ReadAll(resp.Body)
+ log.Printf(string(responseBody))
+
+ responseUnmarshalled := struct {
+ Jsonrpc string `json:"jsonrpc"`
+ Id string `json:"id"`
+ Result []string `json:"result"`
+ }{}
+
+ err = json.Unmarshal(responseBody, &responseUnmarshalled)
+ if err != nil {
+ log.Error().Err(err)
+ }
+
+ json.NewEncoder(w).Encode(responseUnmarshalled)
}
func startServer() {
http.HandleFunc("/health", healthHandler)
- http.HandleFunc("/crypto", cryptoHandler)
+ http.HandleFunc("/price", priceHandler)
http.HandleFunc("/pair", pairHandler)
http.HandleFunc("/addalert", addAlertHandler)
+ http.HandleFunc("/ex", exHandler)
if err := http.ListenAndServe(":"+*flagPort, nil); err != nil {
- log.Fatal(err)
+ log.Fatal().Err(err)
}
}
+func setupLogging() {
+ zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
+}
+
func main() {
+ setupLogging()
go runTgBot()
go alertManager()
startServer()
diff --git a/test/endpoints.sh b/test/endpoints.sh
new file mode 100755
index 0000000..0c47cd6
--- /dev/null
+++ b/test/endpoints.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+set -e
+set -x
+
+curl -X GET http://127.0.0.1:8008/price?name=CAKE&unit=USD
+curl -X GET http://127.0.0.1:8008/pair?one=ETH&two=CAKE&multiplier=4.0
+curl -X POST -H "Content-Type: application/json" -d '{"name":"alert1", "expr":"ETH>CAKE"}' http://127.0.0.1:8008/addalert