diff options
-rw-r--r-- | .travis.yml | 14 | ||||
-rw-r--r-- | Dockerfile | 5 | ||||
-rw-r--r-- | README.md | 47 | ||||
-rw-r--r-- | api/hived.postman_collection.json | 64 | ||||
-rw-r--r-- | api/swagger.yaml | 101 | ||||
-rw-r--r-- | docker-compose.yaml | 11 | ||||
-rwxr-xr-x | docker-entrypoint.sh | 8 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 6 | ||||
-rw-r--r-- | hived.go | 235 | ||||
-rwxr-xr-x | test/endpoints.sh | 7 |
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 @@ -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"] @@ -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" @@ -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 ) @@ -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= @@ -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 |