Daniel Bohry 1 жил өмнө
parent
commit
3425891f59

+ 18 - 0
.github/workflows/build.yml

@@ -0,0 +1,18 @@
+name: Build and Test
+
+on:
+  pull_request:
+    branches: [ main ]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Build jar
+      run: chmod +x ./mvnw && ./mvnw clean jar:jar
+    - name: Run tests
+      run: ./mvnw test

+ 27 - 0
.github/workflows/buildAndRelease.yml

@@ -0,0 +1,27 @@
+name: Release
+
+on:
+  push:
+    branches: [ main ]
+
+jobs:
+
+  build:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Build jar
+      run: chmod +x ./mvnw && ./mvnw clean jar:jar
+    - name: Run tests
+      run: ./mvnw test
+    - name: Build the Docker image
+      run: ./mvnw spring-boot:build-image
+    - name: Login to Docker Hub
+      uses: docker/login-action@v2
+      with:
+        username: ${{ secrets.DOCKERHUB_USERNAME }}
+        password: ${{ secrets.DOCKERHUB_TOKEN }}
+    - name: Push image
+      run: docker push lhamacorp/stocks-be

+ 4 - 0
Dockerfile

@@ -0,0 +1,4 @@
+FROM openjdk:17-jdk-slim as BuildJava
+ARG JAR_FILE=target/stocks-0.1-SNAPSHOT.jar
+COPY ${JAR_FILE} app.jar
+ENTRYPOINT ["java","-jar","/app.jar"]

+ 2 - 2
pom.xml

@@ -9,8 +9,8 @@
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
     <groupId>com.danielbohry</groupId>
-    <artifactId>stocks</artifactId>
-    <version>0.0.1-SNAPSHOT</version>
+    <artifactId>stocks-be</artifactId>
+    <version>0.1</version>
     <name>stocks-be</name>
     <description>stocks-be</description>
     <properties>

+ 6 - 25
src/main/java/com/danielbohry/stocks/client/StockClient.java

@@ -1,37 +1,18 @@
 package com.danielbohry.stocks.client;
 
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import com.danielbohry.stocks.service.StockService.StockResponse;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import java.util.List;
+
 @FeignClient(name = "stockClient", url = "${clients.stock.url}")
 public interface StockClient {
 
-    @GetMapping("query")
-    StockResponse getStockInfo(@RequestParam("function") String function,
-                               @RequestParam("symbol") String symbol,
-                               @RequestParam("apikey") String apiKey);
+    @GetMapping("daily/{symbol}/prices")
+    List<StockResponse> getStockInfo(@RequestParam("symbol") String symbol,
+                                     @RequestParam("token") String apiKey);
 
-    @Data
-    @AllArgsConstructor
-    @NoArgsConstructor
-    @JsonIgnoreProperties(ignoreUnknown = true)
-    class StockResponse {
-        @JsonProperty("Symbol")
-        private String symbol;
-        @JsonProperty("Name")
-        private String name;
-        @JsonProperty("Country")
-        private String country;
-        @JsonProperty("Sector")
-        private String sector;
-        @JsonProperty("50DayMovingAverage")
-        private String averagePrice;
-    }
 
 }

+ 21 - 0
src/main/java/com/danielbohry/stocks/config/FeignClientConfiguration.java

@@ -0,0 +1,21 @@
+package com.danielbohry.stocks.config;
+
+import feign.Retryer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class FeignClientConfiguration {
+
+    @Bean
+    public Retryer retryer() {
+        return new Retryer.Default(100, 2000, 3);
+    }
+
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+
+}

+ 21 - 0
src/main/java/com/danielbohry/stocks/domain/Quote.java

@@ -0,0 +1,21 @@
+package com.danielbohry.stocks.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Quote {
+
+    @Id
+    private String code;
+    private BigDecimal price;
+    private LocalDateTime updatedAt;
+
+}

+ 2 - 1
src/main/java/com/danielbohry/stocks/domain/Stock.java

@@ -12,6 +12,7 @@ public class Stock {
     private String code;
     private String name;
     private int quantity;
-    private BigDecimal value;
+    private BigDecimal price;
     private BigDecimal total;
+
 }

+ 14 - 0
src/main/java/com/danielbohry/stocks/repository/QuoteRepository.java

@@ -0,0 +1,14 @@
+package com.danielbohry.stocks.repository;
+
+import com.danielbohry.stocks.domain.Quote;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface QuoteRepository extends MongoRepository<Quote, String> {
+
+    Optional<Quote> findByCode(String code);
+
+}

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

@@ -1,7 +1,7 @@
 package com.danielbohry.stocks.service;
 
-import com.danielbohry.stocks.client.StockClient.StockResponse;
 import com.danielbohry.stocks.domain.Portfolio;
+import com.danielbohry.stocks.domain.Quote;
 import com.danielbohry.stocks.domain.Stock;
 import com.danielbohry.stocks.exception.NotFoundException;
 import com.danielbohry.stocks.repository.PortfolioRepository;
@@ -33,15 +33,10 @@ public class PortfolioService {
 
         List<Stock> updatedStocks = portfolio.getStocks().stream()
                 .peek(stock -> {
-                    StockResponse info = stockService.getStockInfo(stock.getCode());
-
-                    log.info("Stock info [{}]", info);
-
-                    if (info.getSymbol() != null) {
-                        stock.setName(info.getName());
-                        stock.setValue(new BigDecimal(info.getAveragePrice()));
-                        stock.setTotal(stock.getValue().multiply(new BigDecimal(stock.getQuantity())));
-                    }
+                    Quote quote = stockService.getStockQuote(stock.getCode());
+                    stock.setName(quote.getCode());
+                    stock.setPrice(quote.getPrice());
+                    stock.setTotal(stock.getPrice().multiply(new BigDecimal(stock.getQuantity())));
                 }).toList();
 
         portfolio.setStocks(updatedStocks);

+ 54 - 5
src/main/java/com/danielbohry/stocks/service/StockService.java

@@ -1,19 +1,68 @@
 package com.danielbohry.stocks.service;
 
 import com.danielbohry.stocks.client.StockClient;
+import com.danielbohry.stocks.domain.Quote;
+import com.danielbohry.stocks.repository.QuoteRepository;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
-import org.springframework.cache.annotation.Cacheable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+
+import static java.time.LocalDateTime.now;
+
 @Service
-@AllArgsConstructor
+@Slf4j
 public class StockService {
 
+    private final QuoteRepository repository;
     private final StockClient client;
+    private final String key;
+
+    public StockService(QuoteRepository repository,
+                        StockClient client,
+                        @Value("${clients.stock.key}") String key) {
+        this.repository = repository;
+        this.client = client;
+        this.key = key;
+    }
+
+    public Quote getStockQuote(String code) {
+        Quote quote = repository.findByCode(code).orElse(new Quote(code, null, null));
+        quote.setPrice(getLastPrice(quote));
+        quote.setUpdatedAt(now());
+
+        repository.save(quote);
+
+        return quote;
+    }
+
+    private BigDecimal getLastPrice(Quote quote) {
+        if (quote.getPrice() == null) {
+            log.info("Current quote for [{}] is null. Requesting latest quote...", quote);
+            return new BigDecimal(client.getStockInfo(quote.getCode(), key).get(0).getLastPrice());
+        } else if (quote.getUpdatedAt().isBefore(now().minusDays(1))) {
+            log.info("Current quote for [{}] is older than 1 day. Requesting latest quote...", quote);
+            return new BigDecimal(client.getStockInfo(quote.getCode(), key).get(0).getLastPrice());
+        } else {
+            return quote.getPrice();
+        }
+    }
 
-    @Cacheable("stockInfo#60")
-    public StockClient.StockResponse getStockInfo(String code) {
-        return client.getStockInfo("OVERVIEW", code, "GL9332G0A2RNBZBR");
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class StockResponse {
+        @JsonProperty("adjClose")
+        private String lastPrice;
+        @JsonProperty("adjOpen")
+        private String openPrice;
     }
 
 }

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

@@ -3,7 +3,8 @@ server:
 
 clients:
   stock:
-    url: https://www.alphavantage.co
+    url: https://api.tiingo.com/tiingo
+    key: 5fa305fd07c632b556f9b25904b27f6d53091ae2
 
 spring:
   data:

+ 0 - 13
src/test/java/com/danielbohry/stocks/AppTests.java

@@ -1,13 +0,0 @@
-package com.danielbohry.stocks;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class AppTests {
-
-    @Test
-    void contextLoads() {
-    }
-
-}