Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.autoinvestor.client.marketfeeling;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

@Component
public class MarketFeelingClient {

private final WebClient webClient;

public MarketFeelingClient(
WebClient.Builder webClientBuilder,
@Value("${autoinvestor.client.market-feeling.url}") String baseUrl
) {
this.webClient = webClientBuilder.baseUrl(baseUrl).build();
}

public Mono<List<NewsResponse>> getNews(String userId, String assetId) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/portfolio/holdings")
.queryParam("assetId", assetId)
.build())
.header("X-User-Id", userId)
.exchangeToMono(clientResponse -> Mono.defer(() -> {
if (clientResponse.statusCode().value() == HttpStatus.OK.value()) {
return clientResponse.bodyToMono(NewsResponse[].class).map(Arrays::asList);
}
return clientResponse.createError();
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.autoinvestor.client.marketfeeling;

import java.time.LocalDateTime;

public record NewsResponse(
String title,
LocalDateTime date,
String url,
String assetId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.autoinvestor.client.portfolio;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

@Component
public class PortfolioClient {

private final WebClient webClient;

public PortfolioClient(
WebClient.Builder webClientBuilder,
@Value("${autoinvestor.client.portfolio.url}") String baseUrl
) {
this.webClient = webClientBuilder.baseUrl(baseUrl).build();
}

public Mono<List<PortfolioHoldingResponse>> getHoldings(String userId) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/portfolio/holdings")
.build())
.header("X-User-Id", userId)
.exchangeToMono(clientResponse -> Mono.defer(() -> {
if (clientResponse.statusCode().value() == HttpStatus.OK.value()) {
return clientResponse.bodyToMono(PortfolioHoldingResponse[].class).map(Arrays::asList);
}
return clientResponse.createError();
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.autoinvestor.client.portfolio;

public record PortfolioHoldingResponse(
String assetId,
Integer amount,
Integer price
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec
.pathMatchers("/api/oauth2/**", "/api/login/**").permitAll()
.anyExchange().permitAll()
.anyExchange().authenticated()
)
.oauth2Login(Customizer.withDefaults())
.build();
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/io/autoinvestor/controller/NewsController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.autoinvestor.controller;

import io.autoinvestor.client.marketfeeling.MarketFeelingClient;
import io.autoinvestor.client.marketfeeling.NewsResponse;
import io.autoinvestor.client.portfolio.PortfolioClient;
import io.autoinvestor.client.portfolio.PortfolioHoldingResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Comparator;
import java.util.List;

@RestController
@RequestMapping("/internal/news")
@RequiredArgsConstructor
public class NewsController {

private final PortfolioClient portfolioClient;
private final MarketFeelingClient marketFeelingClient;

@GetMapping
public Mono<ResponseEntity<?>> getNews(@RequestHeader("X-User-Id") String userId) {
return portfolioClient.getHoldings(userId)
.map(holdings -> holdings.stream().map(PortfolioHoldingResponse::assetId).toList())
.map(assetIds -> assetIds.stream().map(assetId -> marketFeelingClient.getNews(userId, assetId)).toList())
.flatMap(NewsController::flatten)
.map(list -> list.stream().sorted(Comparator.comparing(NewsResponse::date).reversed()).toList())
.map(ResponseEntity::ok);
}

private static <T> Mono<List<T>> flatten(List<Mono<List<T>>> monoList) {
return Flux.fromIterable(monoList)
.flatMap(mono -> mono)
.flatMapIterable(list -> list)
.collectList();
}
}
10 changes: 7 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ autoinvestor:
client:
users:
url: "${USERS_BASE_URL}"
portfolio:
url: "${PORTFOLIO_BASE_URL}"
market-feeling:
url: "${MARKET_FEELING_BASE_URL}"

spring:
security:
Expand Down Expand Up @@ -94,16 +98,16 @@ spring:
- RewritePath=/api/(?<segment>.*),/${segment}

- id: get-news-endpoint
uri: "${MARKET_FEELING_BASE_URL}"
uri: http://localhost:8080
predicates:
- Path=/api/news
- Method=GET
filters:
- ClaimToHeader=userId,X-User-Id
- RewritePath=/api/(?<segment>.*),/${segment}
- RewritePath=/api/news, /internal/news

- id: get-decisions-endpoint
uri: "${MARKET_FEELING_BASE_URL}"
uri: "${DECISION_MAKING_BASE_URL}"
predicates:
- Path=/api/decisions
- Method=GET
Expand Down