github.com/grupokindynos/coins-explorer@v0.0.0-20210507172551-fa8983d19250/fiat/coingecko.go (about) 1 package fiat 2 3 import ( 4 "encoding/json" 5 "errors" 6 "github.com/grupokindynos/coins-explorer/db" 7 "io/ioutil" 8 "net/http" 9 "strconv" 10 "time" 11 12 "github.com/golang/glog" 13 ) 14 15 // Coingecko is a structure that implements RatesDownloaderInterface 16 type Coingecko struct { 17 url string 18 coin string 19 httpTimeoutSeconds time.Duration 20 timeFormat string 21 } 22 23 // NewCoinGeckoDownloader creates a coingecko structure that implements the RatesDownloaderInterface 24 func NewCoinGeckoDownloader(url string, coin string, timeFormat string) RatesDownloaderInterface { 25 return &Coingecko{ 26 url: url, 27 coin: coin, 28 httpTimeoutSeconds: 15 * time.Second, 29 timeFormat: timeFormat, 30 } 31 } 32 33 // makeRequest retrieves the response from Coingecko API at the specified date. 34 // If timestamp is nil, it fetches the latest market data available. 35 func (cg *Coingecko) makeRequest(timestamp *time.Time) ([]byte, error) { 36 requestURL := cg.url + "/coins/" + cg.coin 37 if timestamp != nil { 38 requestURL += "/history" 39 } 40 41 req, err := http.NewRequest("GET", requestURL, nil) 42 if err != nil { 43 glog.Errorf("Error creating a new request for %v: %v", requestURL, err) 44 return nil, err 45 } 46 req.Close = true 47 req.Header.Set("Content-Type", "application/json") 48 49 // Add query parameters 50 q := req.URL.Query() 51 52 // Add a unix timestamp to query parameters to get uncached responses 53 currentTimestamp := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) 54 q.Add("current_timestamp", currentTimestamp) 55 56 if timestamp == nil { 57 q.Add("market_data", "true") 58 q.Add("localization", "false") 59 q.Add("tickers", "false") 60 q.Add("community_data", "false") 61 q.Add("developer_data", "false") 62 } else { 63 timestampFormatted := timestamp.Format(cg.timeFormat) 64 q.Add("date", timestampFormatted) 65 } 66 req.URL.RawQuery = q.Encode() 67 68 client := &http.Client{ 69 Timeout: cg.httpTimeoutSeconds, 70 } 71 resp, err := client.Do(req) 72 if err != nil { 73 return nil, err 74 } 75 defer resp.Body.Close() 76 if resp.StatusCode != http.StatusOK { 77 return nil, errors.New("Invalid response status: " + string(resp.Status)) 78 } 79 bodyBytes, err := ioutil.ReadAll(resp.Body) 80 if err != nil { 81 return nil, err 82 } 83 return bodyBytes, nil 84 } 85 86 // GetData gets fiat rates from API at the specified date and returns a CurrencyRatesTicker 87 // If timestamp is nil, it will download the current fiat rates. 88 func (cg *Coingecko) getTicker(timestamp *time.Time) (*db.CurrencyRatesTicker, error) { 89 dataTimestamp := timestamp 90 if timestamp == nil { 91 timeNow := time.Now() 92 dataTimestamp = &timeNow 93 } 94 dataTimestampUTC := dataTimestamp.UTC() 95 ticker := &db.CurrencyRatesTicker{Timestamp: &dataTimestampUTC} 96 bodyBytes, err := cg.makeRequest(timestamp) 97 if err != nil { 98 return nil, err 99 } 100 101 type FiatRatesResponse struct { 102 MarketData struct { 103 Prices map[string]float64 `json:"current_price"` 104 } `json:"market_data"` 105 } 106 107 var data FiatRatesResponse 108 err = json.Unmarshal(bodyBytes, &data) 109 if err != nil { 110 glog.Errorf("Error parsing FiatRates response: %v", err) 111 return nil, err 112 } 113 ticker.Rates = data.MarketData.Prices 114 return ticker, nil 115 } 116 117 // MarketDataExists checks if there's data available for the specific timestamp. 118 func (cg *Coingecko) marketDataExists(timestamp *time.Time) (bool, error) { 119 resp, err := cg.makeRequest(timestamp) 120 if err != nil { 121 glog.Error("Error getting market data: ", err) 122 return false, err 123 } 124 type FiatRatesResponse struct { 125 MarketData struct { 126 Prices map[string]interface{} `json:"current_price"` 127 } `json:"market_data"` 128 } 129 var data FiatRatesResponse 130 err = json.Unmarshal(resp, &data) 131 if err != nil { 132 glog.Errorf("Error parsing Coingecko response: %v", err) 133 return false, err 134 } 135 return len(data.MarketData.Prices) != 0, nil 136 }