Browse Source

Add exchange rate conversion

Daniel Bohry 1 year ago
parent
commit
e5adf22584

+ 1 - 1
.github/workflows/buildAndRelease.yml

@@ -71,7 +71,7 @@ jobs:
             docker pull lhamacorp/stocks-be:latest
             docker stop stocks-be || true
             docker rm stocks-be || true
-            docker run -d --name stocks-be -p 42902:8080 -e mongo=${{ secrets.MONGO }} -e database=${{ secrets.DATABASE }} -e provider=${{ secrets.PROVIDER_URL }} -e key=${{ secrets.PROVIDER_KEY }} lhamacorp/stocks-be:latest
+            docker run -d --name stocks-be -p 42902:8080 -e mongo=${{ secrets.MONGO }} -e database=${{ secrets.DATABASE }} -e stock_provider=${{ secrets.STOCK_PROVIDER_URL }} -e stock_key=${{ secrets.STOCK_PROVIDER_KEY }} -e exchange_provider=${{ secrets.EXCHANGE_PROVIDER_URL }} -e exchange_key=${{ secrets.EXCHANGE_PROVIDER_KEY }} lhamacorp/stocks-be:latest
 
   cleanup:
     name: Clean ups

+ 10 - 0
pom.xml

@@ -33,6 +33,16 @@
             <version>4.0.2</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springdoc</groupId>
             <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>

+ 15 - 0
src/main/java/com/danielbohry/stocks/client/ExchangeRateClient.java

@@ -0,0 +1,15 @@
+package com.danielbohry.stocks.client;
+
+import com.danielbohry.stocks.service.ExchangeService.ExchangeRateResponse;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@FeignClient(name = "exchangeClient", url = "${clients.exchange.url}")
+public interface ExchangeRateClient {
+
+    @GetMapping("{token}/latest/{symbol}")
+    ExchangeRateResponse getRate(@PathVariable("symbol") String symbol,
+                                 @PathVariable("token") String apiKey);
+
+}

+ 23 - 0
src/main/java/com/danielbohry/stocks/config/CacheConfig.java

@@ -0,0 +1,23 @@
+package com.danielbohry.stocks.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+@Configuration
+@EnableCaching
+public class CacheConfig {
+
+    @Bean
+    public CacheManager cacheManager() {
+        CaffeineCacheManager cacheManager = new CaffeineCacheManager("exchangeRates");
+        cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(4, TimeUnit.HOURS));
+        return cacheManager;
+    }
+
+}

+ 47 - 0
src/main/java/com/danielbohry/stocks/service/ExchangeService.java

@@ -0,0 +1,47 @@
+package com.danielbohry.stocks.service;
+
+import com.danielbohry.stocks.client.ExchangeRateClient;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class ExchangeService {
+
+    private final ExchangeRateClient client;
+    private final String key;
+
+    public ExchangeService(ExchangeRateClient client, @Value("${clients.exchange.key}") String key) {
+        this.client = client;
+        this.key = key;
+    }
+
+    @Cacheable(value = "exchangeRates", key = "#currency")
+    public ExchangeRateResponse getCurrentRate(String currency) {
+        return client.getRate(currency, key);
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class ExchangeRateResponse {
+        @JsonProperty("result")
+        private String result;
+        @JsonProperty("base_code")
+        private String code;
+        @JsonProperty("conversion_rates")
+        private Map<String, BigDecimal> conversionRates;
+    }
+
+}

+ 28 - 5
src/main/java/com/danielbohry/stocks/service/PortfolioService.java

@@ -6,7 +6,7 @@ import com.danielbohry.stocks.domain.Stock;
 import com.danielbohry.stocks.exception.BadRequestException;
 import com.danielbohry.stocks.exception.NotFoundException;
 import com.danielbohry.stocks.repository.PortfolioRepository;
-import lombok.AllArgsConstructor;
+import com.danielbohry.stocks.service.ExchangeService.ExchangeRateResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -16,15 +16,24 @@ import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
 
+import static java.math.RoundingMode.HALF_UP;
 import static java.time.LocalDateTime.now;
 
 @Service
-@AllArgsConstructor
 @Slf4j
 public class PortfolioService {
 
     private final PortfolioRepository repository;
     private final StockService stockService;
+    private final ExchangeService exchangeService;
+
+    public PortfolioService(PortfolioRepository repository,
+                            StockService stockService,
+                            ExchangeService exchangeService) {
+        this.repository = repository;
+        this.stockService = stockService;
+        this.exchangeService = exchangeService;
+    }
 
     public List<Portfolio> getAll() {
         return repository.findAll();
@@ -34,14 +43,28 @@ public class PortfolioService {
         Portfolio portfolio = repository.findById(id)
                 .orElseThrow(() -> new NotFoundException("No portfolio found with id: " + id));
 
+        ExchangeRateResponse exchangeRate = exchangeService.getCurrentRate("USD");
+
+        BigDecimal brlRatio = exchangeRate.getConversionRates().get("BRL");
+        BigDecimal eurRatio = exchangeRate.getConversionRates().get("EUR");
+
         log.info("Getting portfolio [{}]", id);
         List<Stock> updatedStocks = portfolio.getStocks().stream()
-                .map(stock -> {
+                .peek(stock -> {
                     Quote quote = stockService.getStockQuote(stock.getCode());
                     stock.setName(quote.getName());
-                    stock.setPrice(quote.getPrice());
+
+                    switch (quote.getCurrency()) {
+                        case "BRL":
+                            stock.setPrice(quote.getPrice().divide(brlRatio, HALF_UP));
+                            break;
+                        case "EUR":
+                            stock.setPrice(quote.getPrice().divide(eurRatio, HALF_UP));
+                        default:
+                            stock.setPrice(quote.getPrice());
+                    }
+
                     stock.setTotal(stock.getPrice().multiply(new BigDecimal(stock.getQuantity())));
-                    return stock;
                 }).toList();
 
         portfolio.setStocks(updatedStocks);

+ 5 - 2
src/main/resources/application.yml

@@ -3,8 +3,11 @@ server:
 
 clients:
   stock:
-    url: ${provider:}
-    key: ${key:}
+    url: ${stock_provider:}
+    key: ${stock_key:}
+  exchange:
+    url: ${exchange_provider:}
+    key: ${exchange_key:}
 
 spring:
   data:

+ 5 - 4
src/test/java/service/PortfolioServiceTest.java

@@ -1,13 +1,11 @@
 package service;
 
 import com.danielbohry.stocks.App;
-import com.danielbohry.stocks.client.StockClient;
 import com.danielbohry.stocks.domain.Portfolio;
 import com.danielbohry.stocks.domain.Stock;
 import com.danielbohry.stocks.repository.PortfolioRepository;
-import com.danielbohry.stocks.repository.QuoteRepository;
 import com.danielbohry.stocks.repository.StockRepository;
-import com.danielbohry.stocks.repository.StockRepository.StockInfoResponse;
+import com.danielbohry.stocks.service.ExchangeService;
 import com.danielbohry.stocks.service.PortfolioService;
 import com.danielbohry.stocks.service.StockService;
 import org.junit.jupiter.api.AfterEach;
@@ -35,12 +33,15 @@ public class PortfolioServiceTest {
     @Mock
     private StockRepository stockRepository;
 
+    @Mock
+    private ExchangeService exchangeService;
+
     private PortfolioService portfolioService;
 
     @BeforeEach
     public void setup() {
         StockService stockService = new StockService(stockRepository);
-        portfolioService = new PortfolioService(portfolioRepository, stockService);
+        portfolioService = new PortfolioService(portfolioRepository, stockService, exchangeService);
     }
 
     @AfterEach