mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
254 lines
6.8 KiB
Go
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
|
|
}
|