diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..8cff4df537ee02f65bfb276b16f110045be517f1 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/configuration/ExchangeRateInitializer.java @@ -0,0 +1,39 @@ +package cz.muni.pa165.banking.application.configuration; + +import cz.muni.pa165.banking.domain.money.exchange.ExchangeRateService; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Currency; +import java.util.List; + +@Component +public class ExchangeRateInitializer { + + private final Logger logger = LoggerFactory.getLogger(ExchangeRateInitializer.class); + + private final ExchangeRateService exchangeRateService; + + @Value("#{'${banking.apps.rates.initial.currencies}'.split(',')}") + private List<String> currencies; + + public ExchangeRateInitializer(ExchangeRateService exchangeRateService) { + this.exchangeRateService = exchangeRateService; + } + + @PostConstruct + void initialize() { + for (String currency : currencies) { + try { + // ignore result, just call service to trigger mechanism to cache data + exchangeRateService.getRate(Currency.getInstance(currency), Currency.getInstance(currency)); + } catch (Exception e) { + logger.warn(String.format("Initializing exchange rates for %s failed. API might be unavailable!", currency)); + } + } + } + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java index e625165d8618b02c5faf37edfde0338cd87ca6c9..ff6cb7887ba981c17aa3a5b13b602b403dc14060 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/messaging/ProcessProducer.java @@ -3,13 +3,10 @@ package cz.muni.pa165.banking.application.messaging; import com.fasterxml.jackson.databind.ObjectMapper; import cz.muni.pa165.banking.domain.messaging.MessageProducer; import cz.muni.pa165.banking.domain.messaging.ProcessRequest; -import cz.muni.pa165.banking.domain.transaction.TransactionType; import cz.muni.pa165.banking.exception.ServerError; -import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Service; import java.util.Map; -import java.util.UUID; @Service public class ProcessProducer implements MessageProducer { @@ -22,12 +19,6 @@ public class ProcessProducer implements MessageProducer { this.messagingService = messagingService; this.mapper = mapper; } - - @PostConstruct - void testing() { - ProcessRequest data = new ProcessRequest(UUID.randomUUID(), TransactionType.DEPOSIT); - send(data); - } @Override public void send(ProcessRequest data) { diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java deleted file mode 100644 index f832677ac863837007b4204d933050a48432f934..0000000000000000000000000000000000000000 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/Dummy.java +++ /dev/null @@ -1,20 +0,0 @@ -package cz.muni.pa165.banking.application.proxy; - -import cz.muni.pa165.banking.account.query.CustomerServiceApi; -import jakarta.annotation.PostConstruct; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class Dummy { - - @Autowired - private CustomerServiceApi customerServiceApi; - - @PostConstruct - public void test() { - var jj = customerServiceApi.getBalance("id1"); - System.out.println("jozo"); - } - -} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java deleted file mode 100644 index 62e21b8cfc298e1426dc6df7719376b443ad0600..0000000000000000000000000000000000000000 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/ExchangeRatesApi.java +++ /dev/null @@ -1,10 +0,0 @@ -package cz.muni.pa165.banking.application.proxy; - -// some proxy for a REST API, which contains real-time data about the currency rates -// e.g. https://exchangeratesapi.io/ -// https://nordicapis.com/10-apis-for-currency-exchange-rates/ -public interface ExchangeRatesApi { - - // TODO nejake methods po vybere a nastudovani API - -} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..e5f4ba4daa30f50a81492d364a6dfd19a1d7cea0 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRateResponseProcessor.java @@ -0,0 +1,46 @@ +package cz.muni.pa165.banking.application.proxy.rate; + +import cz.muni.pa165.banking.exception.UnexpectedValueException; + +import java.math.BigDecimal; +import java.util.Currency; +import java.util.HashMap; +import java.util.Map; + +public class ExchangeRateResponseProcessor { + + public static Map<Currency, BigDecimal> process(Map<String, Object> response) { + if (!response.get("result").equals("success")) { + throw new UnexpectedValueException("Exchange rate API unavailable"); + } + + Map<String, Object> rates = (Map<String, Object>) response.get("conversion_rates"); + + Map<Currency, BigDecimal> result = new HashMap<>(); + for (String currencyCode : rates.keySet()) { + Currency currency; + try { + currency = Currency.getInstance(currencyCode); + } catch (Exception e) { + continue; + } + Object value = rates.get(currencyCode); + + BigDecimal rate; + if (value instanceof Integer val) { + rate = BigDecimal.valueOf(val); + } else if (value instanceof Double val) { + rate = BigDecimal.valueOf(val); + } else { + String strVal = value.toString(); + double val = Double.parseDouble(strVal); + rate = BigDecimal.valueOf(val); + } + + result.put(currency, rate); + } + + return result; + } + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java new file mode 100644 index 0000000000000000000000000000000000000000..f95e7581bf8b50a2ecce57a636848be9eee54228 --- /dev/null +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/proxy/rate/ExchangeRatesApi.java @@ -0,0 +1,16 @@ +package cz.muni.pa165.banking.application.proxy.rate; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Map; + +// https://app.exchangerate-api.com +@FeignClient(name = "ExchangeRateApi", url = "${banking.apps.rates.url}") +public interface ExchangeRatesApi { + + @GetMapping("/latest/{currency}") + Map<String, Object> getRatesOfCurrency(@RequestParam String currency); + +} diff --git a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java index fb0786f00141256104b45ed72d121537d958b834..4a0c3cad66afc4f126bee51e2567ba3a30b300e9 100644 --- a/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java +++ b/transaction-processor/src/main/java/cz/muni/pa165/banking/application/service/ExchangeRateServiceImpl.java @@ -1,25 +1,34 @@ package cz.muni.pa165.banking.application.service; +import cz.muni.pa165.banking.application.proxy.rate.ExchangeRateResponseProcessor; +import cz.muni.pa165.banking.application.proxy.rate.ExchangeRatesApi; import cz.muni.pa165.banking.domain.money.exchange.ExchangeRateService; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Currency; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @Service public class ExchangeRateServiceImpl implements ExchangeRateService { - // TODO call external API containing current information about exchange rates -> Milestone2 - // either actual API containing real data, or custom 'mock' API containing static data - -// private final ExchangeRatesApi proxy; + private final ExchangeRatesApi exchangeRatesApi; + + Map<Currency, Map<Currency, BigDecimal>> exchangeRates = new ConcurrentHashMap<>(); -// public ExchangeRateServiceImpl(ExchangeRatesApi proxy) { -// this.proxy = proxy; -// } + public ExchangeRateServiceImpl(ExchangeRatesApi exchangeRatesApi) { + this.exchangeRatesApi = exchangeRatesApi; + } @Override public BigDecimal getRate(Currency base, Currency target) { - return BigDecimal.ONE; + if (!exchangeRates.containsKey(base)) { + Map<String, Object> response = exchangeRatesApi.getRatesOfCurrency(base.getCurrencyCode()); + exchangeRates.put(base, ExchangeRateResponseProcessor.process(response)); + } + + return exchangeRates.get(base).get(target); } + } diff --git a/transaction-processor/src/main/resources/application.yaml b/transaction-processor/src/main/resources/application.yaml index e901a7e85a223446b84f1254289f06810e7b04f3..5ab250940aa6ffa21489f70fb7f734693a19f96a 100644 --- a/transaction-processor/src/main/resources/application.yaml +++ b/transaction-processor/src/main/resources/application.yaml @@ -11,6 +11,11 @@ banking: host: localhost port: 8081 url: ${banking.apps.query.host}:${banking.apps.query.port} + rates: + api-key: 5f40db3a892e886bda8a6967 + url: https://v6.exchangerate-api.com/v6/${banking.apps.rates.api-key} + initial: + currencies: CZK, EUR, USD spring: application: