diff --git a/conf/ezbookkeeping.ini b/conf/ezbookkeeping.ini index 1cb5d2ce..89585531 100644 --- a/conf/ezbookkeeping.ini +++ b/conf/ezbookkeeping.ini @@ -300,7 +300,6 @@ custom_map_tile_server_default_zoom_level = 14 # "reserve_bank_of_australia", # "czech_national_bank" # "national_bank_of_poland" -# "monetary_authority_of_singapore" data_source = euro_central_bank # Requesting exchange rates data timeout (0 - 4294967295 milliseconds) diff --git a/pkg/exchangerates/exchange_rates_datasource_container.go b/pkg/exchangerates/exchange_rates_datasource_container.go index f12e6c49..fd26323f 100644 --- a/pkg/exchangerates/exchange_rates_datasource_container.go +++ b/pkg/exchangerates/exchange_rates_datasource_container.go @@ -32,9 +32,6 @@ func InitializeExchangeRatesDataSource(config *settings.Config) error { } else if config.ExchangeRatesDataSource == settings.NationalBankOfPolandDataSource { Container.Current = &NationalBankOfPolandDataSource{} return nil - } else if config.ExchangeRatesDataSource == settings.MonetaryAuthorityOfSingaporeDataSource { - Container.Current = &MonetaryAuthorityOfSingaporeDataSource{} - return nil } return errs.ErrInvalidExchangeRatesDataSource diff --git a/pkg/exchangerates/monetary_authority_of_singapore_datasource.go b/pkg/exchangerates/monetary_authority_of_singapore_datasource.go deleted file mode 100644 index a6dece03..00000000 --- a/pkg/exchangerates/monetary_authority_of_singapore_datasource.go +++ /dev/null @@ -1,179 +0,0 @@ -package exchangerates - -import ( - "encoding/json" - "math" - "strings" - "time" - - "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/errs" - "github.com/mayswind/ezbookkeeping/pkg/log" - "github.com/mayswind/ezbookkeeping/pkg/models" - "github.com/mayswind/ezbookkeeping/pkg/utils" - "github.com/mayswind/ezbookkeeping/pkg/validators" -) - -const monetaryAuthorityOfSingaporeExchangeRateUrl = "https://eservices.mas.gov.sg/api/action/datastore/search.json?resource_id=95932927-c8bc-4e7a-b484-68a66a24edfe&sort=end_of_day+desc&limit=1" -const monetaryAuthorityOfSingaporeExchangeRateReferenceUrl = "https://eservices.mas.gov.sg/Statistics/msb/ExchangeRates.aspx" -const monetaryAuthorityOfSingaporeDataSource = "Monetary Authority of Singapore" -const monetaryAuthorityOfSingaporeBaseCurrency = "SGD" - -const monetaryAuthorityOfSingaporeDataUpdateDateFormat = "2006-01-02 15" -const monetaryAuthorityOfSingaporeDataUpdateDateTimezone = "Asia/Singapore" - -// MonetaryAuthorityOfSingaporeDataSource defines the structure of exchange rates data source of Monetary Authority of Singapore -type MonetaryAuthorityOfSingaporeDataSource struct { - ExchangeRatesDataSource -} - -// MonetaryAuthorityOfSingaporeExchangeRateData represents the whole data from Monetary Authority of Singapore -type MonetaryAuthorityOfSingaporeExchangeRateData struct { - Success bool `json:"success"` - Result *MonetaryAuthorityOfSingaporeResult `json:"result"` -} - -// MonetaryAuthorityOfSingaporeResult represents the actual result from Monetary Authority of Singapore -type MonetaryAuthorityOfSingaporeResult struct { - Records []MonetaryAuthorityOfSingaporeRecord `json:"records"` -} - -// MonetaryAuthorityOfSingaporeRecord represents the record from Monetary Authority of Singapore -type MonetaryAuthorityOfSingaporeRecord map[string]string - -// ToLatestExchangeRateResponse returns a view-object according to original data from Monetary Authority of Singapore -func (e *MonetaryAuthorityOfSingaporeExchangeRateData) ToLatestExchangeRateResponse(c *core.Context) *models.LatestExchangeRateResponse { - if !e.Success { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] response is not success") - return nil - } - - if e.Result == nil { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] result is null") - return nil - } - - if len(e.Result.Records) < 1 { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] records is empty") - return nil - } - - lastDayRecord := e.Result.Records[0] - exchangeRates := make(models.LatestExchangeRateSlice, 0, len(lastDayRecord)) - latestUpdateDate := "" - - for key, value := range lastDayRecord { - if key == "end_of_day" { - latestUpdateDate = value - continue - } - - exchangeRate := e.parseExchangeRateResponse(c, key, value) - - if exchangeRate == nil { - continue - } - - exchangeRates = append(exchangeRates, exchangeRate) - } - - timezone, err := time.LoadLocation(monetaryAuthorityOfSingaporeDataUpdateDateTimezone) - - if err != nil { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", monetaryAuthorityOfSingaporeDataUpdateDateTimezone) - return nil - } - - updateDateTime := latestUpdateDate + " 12" // These rates are the average of buying and selling interbank rates quoted around midday in Singapore - updateTime, err := time.ParseInLocation(monetaryAuthorityOfSingaporeDataUpdateDateFormat, updateDateTime, timezone) - - if err != nil { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", updateDateTime) - return nil - } - - latestExchangeRateResp := &models.LatestExchangeRateResponse{ - DataSource: monetaryAuthorityOfSingaporeDataSource, - ReferenceUrl: monetaryAuthorityOfSingaporeExchangeRateReferenceUrl, - UpdateTime: updateTime.Unix(), - BaseCurrency: monetaryAuthorityOfSingaporeBaseCurrency, - ExchangeRates: exchangeRates, - } - - return latestExchangeRateResp -} - -func (e *MonetaryAuthorityOfSingaporeExchangeRateData) parseExchangeRateResponse(c *core.Context, key string, value string) *models.LatestExchangeRate { - if !strings.Contains(key, "_") { - return nil - } - - items := strings.Split(key, "_") - - if len(items) < 2 { - return nil - } - - fromCurrencyCode := strings.ToUpper(items[0]) - toCurrencyCode := strings.ToUpper(items[1]) - - if _, exists := validators.AllCurrencyNames[fromCurrencyCode]; !exists { - return nil - } - - if toCurrencyCode != monetaryAuthorityOfSingaporeBaseCurrency { - return nil - } - - rate, err := utils.StringToFloat64(value) - - if err != nil { - log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] failed to parse rate, rate is %s", value) - return nil - } - - if rate <= 0 { - log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] rate is invalid, rate is %s", value) - return nil - } - - finalRate := 1 / rate - - if math.IsInf(finalRate, 0) { - return nil - } - - if len(items) == 3 && items[2] == "100" { - finalRate = finalRate * 100 - } - - return &models.LatestExchangeRate{ - Currency: fromCurrencyCode, - Rate: utils.Float64ToString(finalRate), - } -} - -// GetRequestUrls returns the Monetary Authority of Singapore data source urls -func (e *MonetaryAuthorityOfSingaporeDataSource) GetRequestUrls() []string { - return []string{monetaryAuthorityOfSingaporeExchangeRateUrl} -} - -// Parse returns the common response entity according to the Monetary Authority of Singapore data source raw response -func (e *MonetaryAuthorityOfSingaporeDataSource) Parse(c *core.Context, content []byte) (*models.LatestExchangeRateResponse, error) { - monetaryAuthorityOfSingaporeData := &MonetaryAuthorityOfSingaporeExchangeRateData{} - err := json.Unmarshal(content, monetaryAuthorityOfSingaporeData) - - if err != nil { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse json data, content is %s, because %s", string(content), err.Error()) - return nil, errs.ErrFailedToRequestRemoteApi - } - - latestExchangeRateResponse := monetaryAuthorityOfSingaporeData.ToLatestExchangeRateResponse(c) - - if latestExchangeRateResponse == nil { - log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content)) - return nil, errs.ErrFailedToRequestRemoteApi - } - - return latestExchangeRateResponse, nil -} diff --git a/pkg/exchangerates/monetary_authority_of_singapore_datasource_test.go b/pkg/exchangerates/monetary_authority_of_singapore_datasource_test.go deleted file mode 100644 index 23eb585c..00000000 --- a/pkg/exchangerates/monetary_authority_of_singapore_datasource_test.go +++ /dev/null @@ -1,206 +0,0 @@ -package exchangerates - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/gin-gonic/gin" - "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/models" -) - -const monetaryAuthorityOfSingaporeMinimumRequiredContent = "{\n" + - " \"success\": true,\n" + - " \"result\": {\n" + - " \"records\": [\n" + - " {\n" + - " \"end_of_day\": \"2023-05-26\",\n" + - " \"usd_sgd\": \"1.3528\",\n" + - " \"cny_sgd_100\": \"19.16\"\n" + - " }\n" + - " ]\n" + - " }\n" + - "}" - -func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractBaseCurrency(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent)) - assert.Equal(t, nil, err) - assert.Equal(t, "SGD", actualLatestExchangeRateResponse.BaseCurrency) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractExchangeRates(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent)) - assert.Equal(t, nil, err) - assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ - Currency: "USD", - Rate: "0.7392075694855116", - }) - assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ - Currency: "CNY", - Rate: "5.219206680584551", - }) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_BlankContent(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - _, err := dataSource.Parse(context, []byte("")) - assert.NotEqual(t, nil, err) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_EmptyJsonObject(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - _, err := dataSource.Parse(context, []byte("{}")) - assert.NotEqual(t, nil, err) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_ResponseSuccessIsFalseObject(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - _, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": false,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " {\n"+ - " \"end_of_day\": \"2023-05-26\",\n"+ - " \"usd_sgd\": \"1.3528\",\n"+ - " \"cny_sgd_100\": \"19.16\"\n"+ - " }\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.NotEqual(t, nil, err) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_NoResultContent(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - _, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true\n"+ - "}")) - assert.NotEqual(t, nil, err) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRecordContent(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - _, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.NotEqual(t, nil, err) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_TargetCurrencyIsNotBaseCurrency(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " {\n"+ - " \"end_of_day\": \"2023-05-26\",\n"+ - " \"usd_cny\": \"1\""+ - " }\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.Equal(t, nil, err) - assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_InvalidCurrency(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " {\n"+ - " \"end_of_day\": \"2023-05-26\",\n"+ - " \"xxx_sgd\": \"1.3528\""+ - " }\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.Equal(t, nil, err) - assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRate(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " {\n"+ - " \"end_of_day\": \"2023-05-26\",\n"+ - " \"usd_sgd\": \"\""+ - " }\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.Equal(t, nil, err) - assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) -} - -func TestMonetaryAuthorityOfSingaporeDataSource_InvalidRate(t *testing.T) { - dataSource := &MonetaryAuthorityOfSingaporeDataSource{} - context := &core.Context{ - Context: &gin.Context{}, - } - - actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+ - " \"success\": true,\n"+ - " \"result\": {\n"+ - " \"records\": [\n"+ - " {\n"+ - " \"end_of_day\": \"2023-05-26\",\n"+ - " \"usd_sgd\": null"+ - " }\n"+ - " ]\n"+ - " }\n"+ - "}")) - assert.Equal(t, nil, err) - assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) -} diff --git a/pkg/settings/setting.go b/pkg/settings/setting.go index 87e4fed6..294c513a 100644 --- a/pkg/settings/setting.go +++ b/pkg/settings/setting.go @@ -100,12 +100,11 @@ const ( // Exchange rates data source types const ( - EuroCentralBankDataSource string = "euro_central_bank" - BankOfCanadaDataSource string = "bank_of_canada" - ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia" - CzechNationalBankDataSource string = "czech_national_bank" - NationalBankOfPolandDataSource string = "national_bank_of_poland" - MonetaryAuthorityOfSingaporeDataSource string = "monetary_authority_of_singapore" + EuroCentralBankDataSource string = "euro_central_bank" + BankOfCanadaDataSource string = "bank_of_canada" + ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia" + CzechNationalBankDataSource string = "czech_national_bank" + NationalBankOfPolandDataSource string = "national_bank_of_poland" ) const ( @@ -843,8 +842,6 @@ func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectio config.ExchangeRatesDataSource = CzechNationalBankDataSource } else if dataSource == NationalBankOfPolandDataSource { config.ExchangeRatesDataSource = NationalBankOfPolandDataSource - } else if dataSource == MonetaryAuthorityOfSingaporeDataSource { - config.ExchangeRatesDataSource = MonetaryAuthorityOfSingaporeDataSource } else { return errs.ErrInvalidExchangeRatesDataSource }