Skip to content
Snippets Groups Projects
Commit e09987ec authored by Abdulhadi Al-Sayed's avatar Abdulhadi Al-Sayed
Browse files

random push

parent 9fa3e479
No related branches found
No related tags found
No related merge requests found
...@@ -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 HandleHistoryGet(w http.ResponseWriter, r *http.Request) { func handleHistoryGet(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 := GetHistory(beginDate, endDate, currencyCode, "") // last parameter empty because not part of request result, err := GetExchangeData(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:
HandleHistoryGet(w, r) handleHistoryGet(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)
}
}
} }
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
country := (countries)[0] c := (countries)[0]
// Extract currency code // Extract currency code
currencyCode := country.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
...@@ -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 BASEURL = "https://api.exchangeratesapi.io/history?start_at=%s&end_at=%s&symbols=%s&base=%s" const HISTORYURL = "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(resData.Body).Decode(&result) err := json.NewDecoder(data.Body).Decode(&result)
if err != nil { // Error handling decoding if err != nil { // Error handling decoding
return nil, err return nil, err
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment