Files
services/currency/handler/currency.go
2021-08-27 09:55:06 +01:00

254 lines
6.8 KiB
Go

package handler
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"time"
"github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger"
pb "github.com/micro/services/currency/proto"
"github.com/patrickmn/go-cache"
)
var (
re = regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)
)
type Currency struct {
Api string
Cache *cache.Cache
}
func (c *Currency) Codes(ctx context.Context, req *pb.CodesRequest, rsp *pb.CodesResponse) error {
// try the cache
if codes, ok := c.Cache.Get("codes"); ok {
rsp.Codes = codes.([]*pb.Code)
return nil
}
resp, err := http.Get(c.Api + "/codes")
if err != nil {
logger.Errorf("Failed to get codes: %v\n", err)
return errors.InternalServerError("currency.codes", "failed to get codes")
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
logger.Errorf("Failed to get codes (non 200): %d %v\n", resp.StatusCode, string(b))
return errors.InternalServerError("currency.codes", "failed to get codes")
}
var respBody map[string]interface{}
if err := json.Unmarshal(b, &respBody); err != nil {
logger.Errorf("Failed to unmarshal codes: %v\n", err)
return errors.InternalServerError("currency.codes", "failed to get codes")
}
codes, ok := respBody["supported_codes"].([]interface{})
if !ok {
logger.Errorf("Failed to convert rates to map[string]interface{}: %v\n", ok)
return errors.InternalServerError("currency.rates", "failed to get rates")
}
for _, code := range codes {
c := code.([]interface{})
rsp.Codes = append(rsp.Codes, &pb.Code{
Name: c[0].(string),
Currency: c[1].(string),
})
}
// set for a period of time
c.Cache.Set("codes", rsp.Codes, time.Hour)
return nil
}
func (c *Currency) History(ctx context.Context, req *pb.HistoryRequest, rsp *pb.HistoryResponse) error {
if len(req.Code) == 0 {
return errors.BadRequest("currency.rates", "missing code")
}
if len(req.Code) != 3 {
return errors.BadRequest("currency.rates", "code is invalid")
}
if len(req.Date) == 0 {
return errors.BadRequest("currency.history", "missing date")
}
if !re.MatchString(req.Date) {
return errors.BadRequest("currency.history", "invalid date")
}
// try the cache
if rates, ok := c.Cache.Get("history:" + req.Code + req.Date); ok {
rsp.Code = req.Code
rsp.Date = req.Date
rsp.Rates = rates.(map[string]float64)
return nil
}
parts := strings.Split(req.Date, "-")
resp, err := http.Get(fmt.Sprintf("%s/history/%s/%s/%s/%s", c.Api, req.Code, parts[0], parts[1], parts[2]))
if err != nil {
logger.Errorf("Failed to get historic rates: %v\n", err)
return errors.InternalServerError("currency.history", "failed to get history")
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
logger.Errorf("Failed to get historic rates (non 200): %d %v\n", resp.StatusCode, string(b))
return errors.InternalServerError("currency.history", "failed to get history")
}
var respBody map[string]interface{}
if err := json.Unmarshal(b, &respBody); err != nil {
logger.Errorf("Failed to unmarshal historic rates: %v\n", err)
return errors.InternalServerError("currency.history", "failed to get history")
}
rates, ok := respBody["conversion_rates"].(map[string]interface{})
if !ok {
logger.Errorf("Failed to convert historic rates to map[string]interface{}: %v\n", ok)
return errors.InternalServerError("currency.history", "failed to get history")
}
rsp.Code = req.Code
rsp.Date = req.Date
rsp.Rates = make(map[string]float64)
for code, rate := range rates {
rsp.Rates[code], _ = rate.(float64)
}
// set for a period of time
c.Cache.Set("history:"+req.Code+req.Date, rsp.Rates, time.Hour*24)
return nil
}
func (c *Currency) Rates(ctx context.Context, req *pb.RatesRequest, rsp *pb.RatesResponse) error {
if len(req.Code) == 0 {
return errors.BadRequest("currency.rates", "missing code")
}
if len(req.Code) != 3 {
return errors.BadRequest("currency.rates", "code is invalid")
}
// try the cache
if rates, ok := c.Cache.Get("rates:" + req.Code); ok {
rsp.Code = req.Code
rsp.Rates = rates.(map[string]float64)
return nil
}
resp, err := http.Get(c.Api + "/latest/" + req.Code)
if err != nil {
logger.Errorf("Failed to get rates: %v\n", err)
return errors.InternalServerError("currency.rates", "failed to get rates")
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
logger.Errorf("Failed to get rates (non 200): %d %v\n", resp.StatusCode, string(b))
return errors.InternalServerError("currency.rates", "failed to get rates")
}
var respBody map[string]interface{}
if err := json.Unmarshal(b, &respBody); err != nil {
logger.Errorf("Failed to unmarshal rates: %v\n", err)
return errors.InternalServerError("currency.rates", "failed to get rates")
}
rates, ok := respBody["conversion_rates"].(map[string]interface{})
if !ok {
logger.Errorf("Failed to convert rates to map[string]interface{}: %v\n", ok)
return errors.InternalServerError("currency.rates", "failed to get rates")
}
rsp.Code = req.Code
rsp.Rates = make(map[string]float64)
for code, rate := range rates {
rsp.Rates[code], _ = rate.(float64)
}
// set for a period of time
c.Cache.Set("rates:"+req.Code, rsp.Rates, cache.DefaultExpiration)
return nil
}
func (c *Currency) Convert(ctx context.Context, req *pb.ConvertRequest, rsp *pb.ConvertResponse) error {
if len(req.From) != 3 {
return errors.BadRequest("currency.convert", "invalid from code")
}
if len(req.To) != 3 {
return errors.BadRequest("currency.convert", "invalid to code")
}
uri := fmt.Sprintf("%s/pair/%s/%s", c.Api, req.From, req.To)
// try the cache
if req.Amount == 0 {
rate, ok := c.Cache.Get("pair:" + req.From + req.To)
if ok {
rsp.From = req.From
rsp.To = req.To
rsp.Rate = rate.(float64)
return nil
}
}
if req.Amount > 0.0 {
uri = fmt.Sprintf("%s/%v", uri, req.Amount)
}
resp, err := http.Get(uri)
if err != nil {
logger.Errorf("Failed to convert: %v\n", err)
return errors.InternalServerError("currency.convert", "failed to convert")
}
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode != 200 {
logger.Errorf("Failed to get convert (non 200): %d %v\n", resp.StatusCode, string(b))
return errors.InternalServerError("currency.convert", "failed to convert")
}
var respBody map[string]interface{}
if err := json.Unmarshal(b, &respBody); err != nil {
logger.Errorf("Failed to unmarshal conversion: %v\n", err)
return errors.InternalServerError("currency.convet", "failed to convert")
}
rsp.From = req.From
rsp.To = req.To
rsp.Rate, _ = respBody["conversion_rate"].(float64)
rsp.Amount, _ = respBody["conversion_result"].(float64)
// save for a period of time
c.Cache.Set("pair:"+req.From+req.To, rsp.Rate, cache.DefaultExpiration)
return nil
}