Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
fix(converter): fix errors in api
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Mar 3, 2019
1 parent edb709b commit 25985f5
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 48 deletions.
96 changes: 60 additions & 36 deletions back/src/money/application/CurrencyConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ExchangeRate } from '../domain/ExchangeRate.entity'
import { ExchangeRateRepository } from '../domain/ExchangeRateRepository'
import { ExchangeRateApi } from '../insfrastructure/ExchangeRateApi'
import { Option } from 'tsoption'
import { ConversationFailedException } from './exception/ConversationFailedException'

@Injectable()
export class CurrencyConverter {
Expand All @@ -28,64 +29,87 @@ export class CurrencyConverter {
}

const normalizedDate = startOfHour(when)
const normalizedNowDate = startOfHour(new Date())

const tryTo = (promiseRate: Promise<Option<ExchangeRate>>) => async (
e: Error,
) => {
const optionalRate = await promiseRate

if (optionalRate.nonEmpty()) {
return optionalRate.get().rate
}

throw e
}

const rate = await this.getExchangeRate(from, to, normalizedDate)
.catch(tryTo(this.exchangeRateRepo.findClosest(from, to, normalizedDate)))
.catch(() => this.getExchangeRate(from, to, new Date()))
.catch(tryTo(this.exchangeRateRepo.findLast(from, to)))
const rate = await this.getExistRate(from, to, normalizedDate)
.catch(() => this.getActualRate(from, to, normalizedDate))
.catch(() => this.getClosestExistRate(from, to, normalizedDate))
.catch(() => this.getActualRate(from, to, normalizedNowDate))
.catch(() => this.getLastExistRate(from, to))

return Math.round(amount * rate)
}

private async getExchangeRate(
private async getActualRate(
from: Currency,
to: Currency,
when: Date,
): Promise<number> {
const existRate = await this.exchangeRateRepo.find(from, to, when)
const MIN_DAY_FOR_HISTORY_TRANSACTION = 2

if (existRate.nonEmpty()) {
return existRate.get().rate
}
const rateIsOld =
Math.abs(differenceInDays(when, new Date())) >
MIN_DAY_FOR_HISTORY_TRANSACTION

const actualRate = await this.fetchExchangeRate(from, to, when)
const actualRate = await (rateIsOld
? this.exchangeRateApi.getHistoryExchangeRate(from, to, when)
: this.exchangeRateApi.getExchangeRate(from, to))

const newRate = new ExchangeRate(from, to, when, actualRate)
if (actualRate.nonEmpty()) {
const newRate = new ExchangeRate(from, to, when, actualRate.get())

await this.entitySaver.save(newRate).catch(() => {
// Okay, rate not saved
})
await this.entitySaver.save(newRate).catch(() => {
// Okay, rate not saved
})

return newRate.rate
}

return newRate.rate
throw new ConversationFailedException(from, to, when)
}

private async fetchExchangeRate(
private async getExistRate(
from: Currency,
to: Currency,
when: Date,
): Promise<number> {
const MIN_DAY_FOR_HISTORY_TRANSACTION = 2
return this.getOrThrow(
this.exchangeRateRepo.find(from, to, when),
new ConversationFailedException(from, to, when),
)
}

const rateIsOld =
Math.abs(differenceInDays(when, new Date())) >
MIN_DAY_FOR_HISTORY_TRANSACTION
private async getClosestExistRate(
from: Currency,
to: Currency,
when: Date,
): Promise<number> {
return this.getOrThrow(
this.exchangeRateRepo.findClosest(from, to, when),
new ConversationFailedException(from, to, when),
)
}

const rate = await (rateIsOld
? this.exchangeRateApi.getHistoryExchangeRate(from, to, when)
: this.exchangeRateApi.getExchangeRate(from, to))
private async getLastExistRate(
from: Currency,
to: Currency,
): Promise<number> {
return this.getOrThrow(
this.exchangeRateRepo.findLast(from, to),
new ConversationFailedException(from, to, new Date()),
)
}

private async getOrThrow(
promiseRate: Promise<Option<ExchangeRate>>,
error: ConversationFailedException,
): Promise<number> {
const rate = await promiseRate

if (rate.nonEmpty()) {
return rate.get().rate
}

return rate
throw error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { format } from 'date-fns'

import { Currency } from '@shared/enum/Currency'

export class ConversationFailedException extends Error {
public constructor(
public readonly from: Currency,
public readonly to: Currency,
public readonly at: Date,
) {
super(
`Currency conversation from ${from} to ${to} failed for ${format(
at,
'YYYY-MM-DD',
)}`,
)
}
}
23 changes: 12 additions & 11 deletions back/src/money/insfrastructure/ExchangeRateApi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common'
import Axios from 'axios'
import { Option } from 'tsoption'
import { format } from 'date-fns'

import { Configuration } from '@back/config/Configuration'
import { Currency } from '@shared/enum/Currency'
import { format, differenceInDays, subDays } from 'date-fns'
import { ExchangeRateApiException } from './ExchangeRateApiException'

interface PromiseCacheMap {
[key: string]: Promise<number>
[key: string]: Promise<Option<number>>
}

interface Query {
Expand All @@ -26,16 +26,18 @@ export class ExchangeRateApi {
this.apiKey = config.getStringOrElse('MANNY_API_KEY', '')
}

public getExchangeRate(from: Currency, to: Currency): Promise<number> {
public getExchangeRate(
from: Currency,
to: Currency,
): Promise<Option<number>> {
const query = `${from}_${to}`

if (!this.simplePromises[query]) {
this.simplePromises[query] = this.request({ query })
.then(results => results[query])
.then(rate => parseFloat(rate.val))
.catch(() => {
throw new ExchangeRateApiException()
})
.then(rate => Option.of(rate))
.catch(() => Option.of(null))
}

return this.simplePromises[query]
Expand All @@ -45,7 +47,7 @@ export class ExchangeRateApi {
from: Currency,
to: Currency,
when: Date,
): Promise<number> {
): Promise<Option<number>> {
const date = format(when, 'YYYY-MM-DD')
const query = `${from}_${to}`

Expand All @@ -59,9 +61,8 @@ export class ExchangeRateApi {
.then(results => results[query])
.then(dateData => dateData[date])
.then(rate => parseFloat(rate.val))
.catch(() => {
throw new ExchangeRateApiException()
})
.then(rate => Option.of(rate))
.catch(() => Option.of(null))
}

return this.historyPromises[fullQuery]
Expand Down
1 change: 0 additions & 1 deletion back/src/money/insfrastructure/ExchangeRateApiException.ts

This file was deleted.

0 comments on commit 25985f5

Please sign in to comment.