Skip to content

Commit

Permalink
Implement 1forge, themoneyconverter
Browse files Browse the repository at this point in the history
  • Loading branch information
meabed committed Sep 29, 2018
1 parent f224137 commit 8641371
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 7 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ The documentation for the current branch can be found [here](#documentation).
|[Yahoo][2]|JSON / API|:heavy_check_mark:|Free|
|[Currency Layer][3]|JSON / API| :heavy_check_mark: |Paid - ApiKey|
|[Fixer.io][4]|JSON / API| :heavy_check_mark: |Paid - ApiKey|
|[The Money Converter][5]|HTML / Regex| TODO |Free|
|[1forge][7]|API| :heavy_check_mark: |Freemium / Paid - ApiKey|
|[The Money Converter][5]|HTML / Regex| :heavy_check_mark: |Free|
|[Open Exchange Rates][6]|API| TODO |Freemium / Paid - ApiKey|
|[1forge][7]|API| TODO |Freemium / Paid - ApiKey|

[1]: //google.com
[2]: //yahoo.com
Expand Down
3 changes: 3 additions & 0 deletions cmd/server/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ var Convert = func(w http.ResponseWriter, r *http.Request) {
convertRes.ConvertedAmount = convertedAmount
convertRes.OriginalAmount = convertReq.Amount

// formatted message like "1 USD is worth 3.675 AED"
convertRes.ConvertedText = fmt.Sprintf("%f %s is worth %f %s", convertRes.OriginalAmount, convertRes.From, convertRes.ConvertedAmount, convertRes.To)

currencyJsonVal, err := json.Marshal(convertRes)
if err != nil {
Logger.Panic(err)
Expand Down
1 change: 1 addition & 0 deletions cmd/server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type convertResObj struct {
OriginalAmount float64 `json:"originalAmount"`
ExchangeValue float64 `json:"exchangeValue"`
ConvertedAmount float64 `json:"convertedAmount"`
ConvertedText string `json:"convertedText"`
DateTime string `json:"dateTime"`
ExchangerName string `json:"exchangerName"`
RateFromCache bool `json:"rateFromCache"`
Expand Down
133 changes: 132 additions & 1 deletion pkg/exchanger/1forge.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,134 @@
package exchanger

//todo
import (
"fmt"
"github.com/bitly/go-simplejson"
"io/ioutil"
"log"
"net"
"net/http"
"time"
)

type oneForgeApi struct {
attributes
}

var (
oneForgeApiUrl = `https://forex.1forge.com/%s/convert?from=%s&to=%s&quantity=1&api_key=%s`
oneForgeApiHeaders = map[string][]string{
`Accept`: {`text/html,application/xhtml+xml,application/xml,application/json`},
`Accept-Encoding`: {`text`},
}
)

func (c *oneForgeApi) requestRate(from string, to string, opt ...interface{}) (*oneForgeApi, error) {

// todo add option opt to add more headers or client configurations
// free mem-leak
// optimize for memory leak
// todo optimize curl connection

url := fmt.Sprintf(oneForgeApiUrl, c.apiVersion, from, to, c.apiKey)
req, _ := http.NewRequest("GET", url, nil)

oneForgeApiHeaders[`User-Agent`] = []string{c.userAgent}
req.Header = oneForgeApiHeaders

res, err := c.Client.Do(req)

if err != nil {
return nil, err
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}

// free mem-leak
// todo discard data
c.responseBody = string(body)
return c, nil
}

func (c *oneForgeApi) GetValue() float64 {
return c.rateValue
}

func (c *oneForgeApi) GetDateTime() string {
return c.rateDate.Format(time.RFC3339)
}

func (c *oneForgeApi) GetExchangerName() string {
return c.name
}

func (c *oneForgeApi) Latest(from string, to string, opt ...interface{}) error {

_, err := c.requestRate(from, to, opt)
if err != nil {
log.Print(err)
return err
}

// if from currency is same as converted currency return value of 1
if from == to {
c.rateValue = 1
return nil
}

json, err := simplejson.NewJson([]byte(c.responseBody))

if err != nil {
log.Print(err)
return err
}

// opening price
value := json.GetPath(`value`).
MustFloat64()
// todo handle error
c.rateValue = value
c.rateDate = time.Now()
return nil
}

func NewOneForgeApi(opt map[string]string) *oneForgeApi {
keepAliveTimeout := 600 * time.Second
timeout := 5 * time.Second
defaultTransport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: keepAliveTimeout,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
}

client := &http.Client{
Transport: defaultTransport,
Timeout: timeout,
}

attr := attributes{
name: `1forge`,
Client: client,
apiVersion: `1.0.3`,
apiKey: opt[`apiKey`],
userAgent: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0`,
}

if opt[`userAgent`] != "" {
attr.userAgent = opt[`userAgent`]
}

if opt[`apiVersion`] != "" {
attr.apiVersion = opt[`apiVersion`]
}

r := &oneForgeApi{attr}
return r
}
19 changes: 18 additions & 1 deletion pkg/exchanger/1forge_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
package exchanger

//todo
import (
"github.com/me-io/go-swap/test/staticMock"
"github.com/stretchr/testify/assert"
"testing"
)

func TestOneForgeApi_Latest(t *testing.T) {
rate := NewOneForgeApi(nil)
assert.Equal(t, rate.name, `1forge`)

rate.Client.Transport = staticMock.NewTestMT()

rate.Latest(`EUR`, `EUR`)
assert.Equal(t, float64(1), rate.GetValue())

rate.Latest(`USD`, `AED`)
assert.Equal(t, float64(3.675), rate.GetValue())
}
134 changes: 133 additions & 1 deletion pkg/exchanger/themoneyconverter.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,137 @@
package exchanger

//todo
import (
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"regexp"
"strconv"
"strings"
"time"
)

// @link : https://themoneyconverter.com/AED/EUR.aspx?amount=1

type theMoneyConverterApi struct {
attributes
}

var (
theMoneyConverterApiUrl = `https://themoneyconverter.com/%s/%s.aspx?amount=1`
theMoneyConverterApiHeaders = map[string][]string{
`Accept`: {`text/html`},
}
)

func (c *theMoneyConverterApi) requestRate(from string, to string, opt ...interface{}) (*theMoneyConverterApi, error) {

// todo add option opt to add more headers or client configurations
// free mem-leak
// optimize for memory leak
// todo optimize curl connection

// format the url and replace currency
url := fmt.Sprintf(theMoneyConverterApiUrl, from, to)
// prepare the request
req, _ := http.NewRequest("GET", url, nil)
// assign the request headers
theMoneyConverterApiHeaders[`User-Agent`] = []string{c.userAgent}
req.Header = theMoneyConverterApiHeaders

// execute the request
res, err := c.Client.Do(req)

if err != nil {
return nil, err
}
defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}

// free mem-leak
// todo discard data
c.responseBody = string(body)
return c, nil
}

func (c *theMoneyConverterApi) GetValue() float64 {
return c.rateValue
}

func (c *theMoneyConverterApi) GetDateTime() string {
return c.rateDate.Format(time.RFC3339)
}

func (c *theMoneyConverterApi) GetExchangerName() string {
return c.name
}

func (c *theMoneyConverterApi) Latest(from string, to string, opt ...interface{}) error {

// todo cache layer
_, err := c.requestRate(from, to, opt)
if err != nil {
log.Print(err)
return err
}

// if from currency is same as converted currency return value of 1
if from == to {
c.rateValue = 1
return nil
}

validID := regexp.MustCompile(`(?s)output(.*)>(.*)</output>`)
stringMatches := validID.FindStringSubmatch(c.responseBody)

stringMatch := strings.TrimSpace(strings.Replace(stringMatches[2], "\n", "", -1))
stringMatch = strings.Replace(stringMatch, fmt.Sprintf("%d %s = ", 1, from), "", -1)
stringMatch = strings.Replace(stringMatch, fmt.Sprintf(" %s", to), "", -1)

c.rateValue, err = strconv.ParseFloat(stringMatch, 64)
c.rateDate = time.Now()

if err != nil {
log.Print(err)
return err
}
return nil
}

func NewTheMoneyConverterApi(opt map[string]string) *theMoneyConverterApi {

keepAliveTimeout := 600 * time.Second
timeout := 5 * time.Second
defaultTransport := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: keepAliveTimeout,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
}

client := &http.Client{
Transport: defaultTransport,
Timeout: timeout,
}

attr := attributes{
name: `themoneyconverter`,
Client: client,
userAgent: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0`,
}
if opt[`userAgent`] != "" {
attr.userAgent = opt[`userAgent`]
}

r := &theMoneyConverterApi{attr}

return r
}
19 changes: 17 additions & 2 deletions pkg/exchanger/themoneyconverter_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
package exchanger

//todo
import (
"github.com/me-io/go-swap/test/staticMock"
"github.com/stretchr/testify/assert"
"testing"
)

// @link : https://themoneyconverter.com/AED/EUR.aspx?amount=1
func TestTheMoneyConverterApi_Latest(t *testing.T) {
rate := NewTheMoneyConverterApi(nil)
assert.Equal(t, rate.name, `themoneyconverter`)

rate.Client.Transport = staticMock.NewTestMT()

rate.Latest(`EUR`, `EUR`)
assert.Equal(t, float64(1), rate.GetValue())

rate.Latest(`USD`, `AED`)
assert.Equal(t, float64(3.6725), rate.GetValue())
}
1 change: 1 addition & 0 deletions pkg/exchanger/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Exchanger interface {
}

type attributes struct {
apiVersion string
apiKey string
userAgent string
responseBody string
Expand Down
5 changes: 5 additions & 0 deletions test/staticMock/1forge_json_aed_usd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"value": 3.675,
"text": "1 USD is worth 3.675 AED",
"timestamp": 1538254496
}
9 changes: 9 additions & 0 deletions test/staticMock/themoneyconverter_html_aed_usd.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="cc-result">
<output id="res1">
1 USD = 3.67250 AED
</output>
</div>
<div class="invert-currencies">
<a href="/AED/USD.aspx" id="ivt" title="Switch currencies">invert currencies</a>
<span class="large">↓↑</span>
</div>
10 changes: 10 additions & 0 deletions test/staticMock/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ func (t *mT) RoundTrip(req *http.Request) (*http.Response, error) {
fc, _ := ioutil.ReadFile(fp)
responseBody = string(fc)
break
case host == `forex.1forge.com`:
fp, _ := filepath.Abs(tPath + `/1forge_json_aed_usd.json`)
fc, _ := ioutil.ReadFile(fp)
responseBody = string(fc)
break
case host == `themoneyconverter.com`:
fp, _ := filepath.Abs(tPath + `/themoneyconverter_html_aed_usd.html`)
fc, _ := ioutil.ReadFile(fp)
responseBody = string(fc)
break
default:

}
Expand Down

0 comments on commit 8641371

Please sign in to comment.