Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
E
exchangeserve
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Abdulhadi Al-Sayed
exchangeserve
Commits
e09987ec
Commit
e09987ec
authored
Feb 26, 2021
by
Abdulhadi Al-Sayed
Browse files
Options
Downloads
Patches
Plain Diff
random push
parent
9fa3e479
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
api_exchange.go
+90
-14
90 additions, 14 deletions
api_exchange.go
country.go
+105
-2
105 additions, 2 deletions
country.go
currency.go
+36
-15
36 additions, 15 deletions
currency.go
with
231 additions
and
31 deletions
api_exchange.go
+
90
−
14
View file @
e09987ec
...
@@ -6,9 +6,27 @@ import (
...
@@ -6,9 +6,27 @@ import (
"github.com/go-chi/chi"
"github.com/go-chi/chi"
"io"
"io"
"net/http"
"net/http"
"strconv"
"strings"
"strings"
)
)
// p is a shortened function for extracting URL parameters
func
p
(
r
*
http
.
Request
,
key
string
)
string
{
return
chi
.
URLParam
(
r
,
key
)
}
// getLimit converts string number into int and returns int, for limiting option
func
getLimit
(
s
string
)
int
{
// convert string number to an int and handle error for non digit characters
if
n
,
err
:=
strconv
.
Atoi
(
s
);
err
==
nil
{
return
n
}
else
{
// return '20' to fixed limit
return
20
}
}
//
func
resWithData
(
w
io
.
Writer
,
response
map
[
string
]
interface
{})
{
func
resWithData
(
w
io
.
Writer
,
response
map
[
string
]
interface
{})
{
// handle JSON objects
// handle JSON objects
err
:=
json
.
NewEncoder
(
w
)
.
Encode
(
response
)
err
:=
json
.
NewEncoder
(
w
)
.
Encode
(
response
)
...
@@ -17,9 +35,8 @@ func resWithData(w io.Writer, response map[string]interface{}) {
...
@@ -17,9 +35,8 @@ func resWithData(w io.Writer, response map[string]interface{}) {
}
}
}
}
// handleHistoryGet utility function, package level, to handle GET request to history route
// handleHistoryGet utility function, package level, to handle GET request to history route
func
H
andleHistoryGet
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
func
h
andleHistoryGet
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
// Set response to be of JSON type
// Set response to be of JSON type
http
.
Header
.
Add
(
w
.
Header
(),
"content-type"
,
"application/json"
)
http
.
Header
.
Add
(
w
.
Header
(),
"content-type"
,
"application/json"
)
parts
:=
strings
.
Split
(
r
.
URL
.
Path
,
"/"
)
parts
:=
strings
.
Split
(
r
.
URL
.
Path
,
"/"
)
...
@@ -28,10 +45,6 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
...
@@ -28,10 +45,6 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
http
.
Error
(
w
,
"Malformed URL"
,
http
.
StatusBadRequest
)
http
.
Error
(
w
,
"Malformed URL"
,
http
.
StatusBadRequest
)
return
return
}
}
// shortened function for extracting URL parameters
p
:=
func
(
r
*
http
.
Request
,
key
string
)
string
{
return
chi
.
URLParam
(
r
,
key
)
}
// extract URL parameters
// extract URL parameters
countryName
:=
p
(
r
,
"country_name"
)
countryName
:=
p
(
r
,
"country_name"
)
beginDate
:=
p
(
r
,
"b_year"
)
+
"-"
+
p
(
r
,
"b_month"
)
+
"-"
+
p
(
r
,
"b_day"
)
beginDate
:=
p
(
r
,
"b_year"
)
+
"-"
+
p
(
r
,
"b_month"
)
+
"-"
+
p
(
r
,
"b_day"
)
...
@@ -46,7 +59,7 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
...
@@ -46,7 +59,7 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
}
}
// Request currency history based on date period and currency code
// Request currency history based on date period and currency code
result
,
err
:=
Get
History
(
beginDate
,
endDate
,
currencyCode
,
""
)
// last parameter empty because not part of request
result
,
err
:=
Get
ExchangeData
(
beginDate
,
endDate
,
currencyCode
,
""
)
// last parameter empty because not part of request
if
err
!=
nil
{
// Error handling bad history request and json decoding
if
err
!=
nil
{
// Error handling bad history request and json decoding
// In case of no server response, reply with 404
// In case of no server response, reply with 404
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusNotFound
)
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusNotFound
)
...
@@ -58,14 +71,63 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
...
@@ -58,14 +71,63 @@ func HandleHistoryGet(w http.ResponseWriter, r *http.Request) {
resWithData
(
w
,
result
)
resWithData
(
w
,
result
)
}
}
// handleBorderGet utility function, package level, to handle GET request to border route
func
handleBorderGet
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
// Set response to be of JSON type
http
.
Header
.
Add
(
w
.
Header
(),
"content-type"
,
"application/json"
)
parts
:=
strings
.
Split
(
r
.
URL
.
Path
,
"/"
)
// error handling
if
len
(
parts
)
!=
5
||
parts
[
3
]
!=
"exchangeborder"
{
http
.
Error
(
w
,
"Malformed URL"
,
http
.
StatusBadRequest
)
return
}
// extract URL parameters
countryName
:=
p
(
r
,
"country_name"
)
// Extract optional 'limit' parameter
number
:=
r
.
URL
.
Query
()
.
Get
(
"limit"
)
// initiate fixed limit with most possible neighbouring countries
limit
:=
20
// r.URL.Query()["limit"] returns an array of items, so we need to choose the first item
if
len
(
number
)
>
0
{
// checks if limit parameter has a value
limit
=
getLimit
(
number
)
}
// Request currencyBase for countryName
currencyBase
,
err
:=
GetCurrency
(
countryName
)
if
err
!=
nil
{
// Error handling bad request parameter for countryName
// In case of no server response, reply with 404
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusNotFound
)
// Error could also be a 400, but we print that only internally
fmt
.
Println
(
"HTTP status: "
+
err
.
Error
())
}
currencyCode
,
err
:=
GetNeighbour
(
countryName
,
limit
)
if
err
!=
nil
{
// Error handling bad request parameter for countryName
// In case of no server response, reply with 404
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusNotFound
)
// Error could also be a 400, but we print that only internally
fmt
.
Println
(
"HTTP status: "
+
err
.
Error
())
}
// Request currency history based on date period and currency code
result
,
err
:=
GetExchangeData
(
""
,
""
,
currencyCode
,
currencyBase
)
if
err
!=
nil
{
// Error handling bad history request and json decoding
// In case of no server response, reply with 404
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusNotFound
)
// Error could also be a 400 or failure in decoding, but we print that only internally
fmt
.
Println
(
"HTTP/JSON status: "
+
err
.
Error
())
}
// Send result for processing
resWithData
(
w
,
result
)
}
// HandlerHistory main handler for route related to `/exchangehistory` requests
// HandlerHistory main handler for route related to `/exchangehistory` requests
func
HandlerHistory
()
func
(
http
.
ResponseWriter
,
*
http
.
Request
)
{
func
HandlerHistory
()
func
(
http
.
ResponseWriter
,
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
httpHandler
:=
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
switch
r
.
Method
{
switch
r
.
Method
{
case
http
.
MethodGet
:
case
http
.
MethodGet
:
H
andleHistoryGet
(
w
,
r
)
h
andleHistoryGet
(
w
,
r
)
case
http
.
MethodPost
:
case
http
.
MethodPost
:
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
case
http
.
MethodPut
:
case
http
.
MethodPut
:
...
@@ -74,6 +136,20 @@ func HandlerHistory() func (http.ResponseWriter, *http.Request) {
...
@@ -74,6 +136,20 @@ func HandlerHistory() func (http.ResponseWriter, *http.Request) {
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
}
}
}
}
}
return
httpHandler
// HandlerBorder main handler for route related to `/exchangeborder` requests
func
HandlerBorder
()
func
(
http
.
ResponseWriter
,
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
switch
r
.
Method
{
case
http
.
MethodGet
:
handleBorderGet
(
w
,
r
)
case
http
.
MethodPost
:
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
case
http
.
MethodPut
:
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
case
http
.
MethodDelete
:
http
.
Error
(
w
,
"Not implemented"
,
http
.
StatusNotImplemented
)
}
}
}
}
This diff is collapsed.
Click to expand it.
country.go
+
105
−
2
View file @
e09987ec
package
exchange
package
exchange
import
(
import
(
"encoding/json"
"fmt"
"github.com/alediaferia/gocountries"
"github.com/alediaferia/gocountries"
"net/http"
)
)
type
Country
[]
struct
{
Name
string
`json:"name"`
TopLevelDomain
[]
string
`json:"topLevelDomain"`
Alpha2Code
string
`json:"alpha2Code"`
Alpha3Code
string
`json:"alpha3Code"`
CallingCodes
[]
string
`json:"callingCodes"`
Capital
string
`json:"capital"`
AltSpellings
[]
string
`json:"altSpellings"`
Region
string
`json:"region"`
Subregion
string
`json:"subregion"`
Population
int
`json:"population"`
Latlng
[]
float64
`json:"latlng"`
Demonym
string
`json:"demonym"`
Area
float64
`json:"area"`
Gini
float64
`json:"gini"`
Timezones
[]
string
`json:"timezones"`
Borders
[]
string
`json:"borders"`
NativeName
string
`json:"nativeName"`
NumericCode
string
`json:"numericCode"`
Currencies
[]
struct
{
Code
string
`json:"code"`
Name
string
`json:"name"`
Symbol
string
`json:"symbol"`
}
`json:"currencies"`
Languages
[]
struct
{
Iso6391
string
`json:"iso639_1"`
Iso6392
string
`json:"iso639_2"`
Name
string
`json:"name"`
NativeName
string
`json:"nativeName"`
}
`json:"languages"`
Translations
struct
{
De
string
`json:"de"`
Es
string
`json:"es"`
Fr
string
`json:"fr"`
Ja
string
`json:"ja"`
It
string
`json:"it"`
Br
string
`json:"br"`
Pt
string
`json:"pt"`
Nl
string
`json:"nl"`
Hr
string
`json:"hr"`
Fa
string
`json:"fa"`
}
`json:"translations"`
Flag
string
`json:"flag"`
RegionalBlocs
[]
struct
{
Acronym
string
`json:"acronym"`
Name
string
`json:"name"`
OtherAcronyms
[]
interface
{}
`json:"otherAcronyms"`
OtherNames
[]
interface
{}
`json:"otherNames"`
}
`json:"regionalBlocs"`
Cioc
string
`json:"cioc"`
}
/*
/*
GetCurrency returns a string of specified Country's currency code e.g.(NOK, USD, EUR...)
GetCurrency returns a string of specified Country's currency code e.g.(NOK, USD, EUR...)
*/
*/
...
@@ -11,8 +66,56 @@ func GetCurrency(countryName string) (string, error) {
...
@@ -11,8 +66,56 @@ func GetCurrency(countryName string) (string, error) {
// Query for structs of possible countries
// Query for structs of possible countries
countries
,
err
:=
gocountries
.
CountriesByName
(
countryName
)
countries
,
err
:=
gocountries
.
CountriesByName
(
countryName
)
// Extract first country
// Extract first country
c
ountry
:=
(
countries
)[
0
]
c
:=
(
countries
)[
0
]
// Extract currency code
// Extract currency code
currencyCode
:=
c
ountry
.
Currencies
[
0
]
currencyCode
:=
c
.
Currencies
[
0
]
return
currencyCode
,
err
return
currencyCode
,
err
}
}
/*
GetNeighbour returns a string of specified Country's Neighbours' currency codes
* limit parameter is for restricting the amount of currencies returned
*/
func
GetNeighbour
(
countryName
string
,
limit
int
)
(
string
,
error
)
{
var
borderURL
=
"https://restcountries.eu/rest/v2/alpha?codes="
var
countries
Country
// Holds JSON object values
// Query for structs of possible countries
country
,
err
:=
gocountries
.
CountriesByName
(
countryName
)
// Extract first country
c
:=
country
[
0
]
// Extract currency code
neighbourAlpha
:=
make
([]
string
,
limit
)
neighbourAlpha
=
c
.
Borders
[
:
]
// parse neighbour alpha codes and append to API call URL
for
i
,
a
:=
range
neighbourAlpha
{
if
(
i
!=
len
(
neighbourAlpha
)
-
1
)
{
// If not last element in array
borderURL
+=
a
+
";"
}
else
{
borderURL
+=
a
// Avoid appending with ';' at the end
}
}
// Using http API for restcountriesAPI because gocountries pckg does not support searching by country code
// Insert parameters into borderURL for request
resData
,
err
:=
http
.
Get
(
fmt
.
Sprintf
(
borderURL
))
if
err
!=
nil
{
// Error handling data
return
""
,
err
}
defer
resData
.
Body
.
Close
()
// Closing body after finishing read
// Decoding body
err
=
json
.
NewDecoder
(
resData
.
Body
)
.
Decode
(
&
countries
)
if
err
!=
nil
{
fmt
.
Println
(
"Decoding: "
+
err
.
Error
())
return
""
,
err
}
// Make string value of neighbour country currencies for return
currencyCodes
:=
""
for
i
,
a
:=
range
countries
{
if
(
i
!=
len
(
countries
)
-
1
)
{
// If not last element in array
currencyCodes
+=
a
.
Currencies
[
0
]
.
Code
+
","
}
else
{
currencyCodes
+=
a
.
Currencies
[
0
]
.
Code
// Avoid appending with ',' at the end
}
}
return
currencyCodes
,
nil
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
currency.go
+
36
−
15
View file @
e09987ec
...
@@ -12,28 +12,49 @@ URL list for 'Foreign exchange rates API' to be modified to query needs
...
@@ -12,28 +12,49 @@ URL list for 'Foreign exchange rates API' to be modified to query needs
order: (startDate, endDate, currencyCode, currencyBase)
order: (startDate, endDate, currencyCode, currencyBase)
NOTE: 'symbols' and 'base' parameters can be omitted as query will return default values for these
NOTE: 'symbols' and 'base' parameters can be omitted as query will return default values for these
*/
*/
const
BASE
URL
=
"https://api.exchangeratesapi.io/history?start_at=%s&end_at=%s&symbols=%s&base=%s"
const
HISTORY
URL
=
"https://api.exchangeratesapi.io/history?start_at=%s&end_at=%s&symbols=%s&base=%s"
const
CURRENCYURL
=
"https://api.exchangeratesapi.io/latest?&symbols=%s&base=%s"
/*
/*
GetHistory returns a map of a decoded json object with
GetExchangeData returns a map of a decoded json object with
specified history of exchange rates based on date specified.
specified history of exchange rates based on date specified
Optionally specify a certain country's currency or a base currency for comparison
or specify multiple countries' currencies and a base currency for comparison.
* Date parameters are mandatory when querying currency history.
* CurrencyCode is optional for history.
* CurrencyBase is optional.
*/
*/
func
GetHistory
(
startDate
,
endDate
,
currencyCode
,
currencyBase
string
)
(
map
[
string
]
interface
{},
error
)
{
func
GetExchangeData
(
startDate
,
endDate
,
currencyCode
,
currencyBase
string
)
(
map
[
string
]
interface
{},
error
)
{
var
result
=
make
(
map
[
string
]
interface
{})
// Body object
if
startDate
==
""
||
endDate
==
""
{
// Request for currency
// Insert parameters into BASEURL for request
// Insert parameters into CURRENCYURL for request
resData
,
err
:=
http
.
Get
(
fmt
.
Sprintf
(
BASEURL
,
startDate
,
endDate
,
currencyCode
,
currencyBase
))
resData
,
err
:=
http
.
Get
(
fmt
.
Sprintf
(
CURRENCYURL
,
currencyCode
,
currencyBase
))
if
err
!=
nil
{
// Error handling data
return
nil
,
err
}
return
Decode
(
resData
)
}
else
{
// Request for history
// Insert parameters into HISTORYURL for request
resData
,
err
:=
http
.
Get
(
fmt
.
Sprintf
(
HISTORYURL
,
startDate
,
endDate
,
currencyCode
,
currencyBase
))
if
err
!=
nil
{
// Error handling data
if
err
!=
nil
{
// Error handling data
return
nil
,
err
return
nil
,
err
}
}
defer
resData
.
Body
.
Close
()
// Closing body after finishing read
return
Decode
(
resData
)
if
resData
.
StatusCode
!=
200
{
// Error handling HTTP request
}
e
:=
errors
.
New
(
resData
.
Status
)
}
// TODO make this function return a struct instead of a map, no random placement
// of JSON arrays and keys like map handles it
// Decode returns a decoded map from a decoded JSON
func
Decode
(
data
*
http
.
Response
)
(
map
[
string
]
interface
{},
error
)
{
var
result
=
make
(
map
[
string
]
interface
{})
// Body object
defer
data
.
Body
.
Close
()
// Closing body after finishing read
if
data
.
StatusCode
!=
200
{
// Error handling HTTP request
e
:=
errors
.
New
(
data
.
Status
)
return
nil
,
e
return
nil
,
e
}
}
// Decoding body
// Decoding body
err
=
json
.
NewDecoder
(
resD
ata
.
Body
)
.
Decode
(
&
result
)
err
:
=
json
.
NewDecoder
(
d
ata
.
Body
)
.
Decode
(
&
result
)
if
err
!=
nil
{
// Error handling decoding
if
err
!=
nil
{
// Error handling decoding
return
nil
,
err
return
nil
,
err
}
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment