-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement portfolio api #93
Merged
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
5df9a10
chore: remove unnecessary import
chominho96 d6d94d7
feat: add portfolio command service
chominho96 c2653f5
feat: add portfolio query service
chominho96 7d7355e
feat: add portfolio controller
chominho96 4e70f8f
docs: add portfolio controller docs
chominho96 d40be82
fix: remove portfolio command service
chominho96 5c06346
test: add test for create portfolio api
chominho96 6d94d02
feat: add monthly/yearly dividend api
chominho96 1710e2f
feat: add monthly/yearly dividend api
chominho96 5d09011
docs: add swagger docs
chominho96 5c4eac6
feat: implement dividend repository custom
chominho96 d1e9d84
test: add portfolio query service test
chominho96 a9e5567
test: add portfolio controller test
chominho96 41393a8
feat: add sector-ratio service
songyi00 fd28440
feat: update portfolio controller
songyi00 2527f26
test: add test code
songyi00 425bc77
test: add service test code
songyi00 e522da3
feat: update swagger docs
songyi00 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
...r/src/main/java/nexters/payout/apiserver/portfolio/application/PortfolioQueryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package nexters.payout.apiserver.portfolio.application; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.MonthlyDividendResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.SingleYearlyDividendResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.YearlyDividendResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.SingleMonthlyDividendResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.request.PortfolioRequest; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.PortfolioResponse; | ||
import nexters.payout.core.time.InstantProvider; | ||
import nexters.payout.domain.dividend.domain.Dividend; | ||
import nexters.payout.domain.dividend.domain.repository.DividendRepository; | ||
import nexters.payout.domain.portfolio.domain.Portfolio; | ||
import nexters.payout.domain.portfolio.domain.PortfolioStock; | ||
import nexters.payout.domain.portfolio.domain.exception.PortfolioNotFoundException; | ||
import nexters.payout.domain.portfolio.domain.repository.PortfolioRepository; | ||
import nexters.payout.domain.stock.domain.Stock; | ||
import nexters.payout.domain.stock.domain.exception.StockIdNotFoundException; | ||
import nexters.payout.domain.stock.domain.exception.TickerNotFoundException; | ||
import nexters.payout.domain.stock.domain.repository.StockRepository; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional | ||
@Slf4j | ||
public class PortfolioQueryService { | ||
|
||
private final StockRepository stockRepository; | ||
private final PortfolioRepository portfolioRepository; | ||
private final DividendRepository dividendRepository; | ||
|
||
public PortfolioResponse createPortfolio(final PortfolioRequest request) { | ||
|
||
List<PortfolioStock> portfolioStocks = | ||
request.tickerShares() | ||
.stream().map(tickerShare -> new PortfolioStock( | ||
stockRepository.findByTicker(tickerShare.ticker()) | ||
.orElseThrow(() -> new TickerNotFoundException(tickerShare.ticker())) | ||
.getId(), | ||
tickerShare.share())) | ||
.toList(); | ||
|
||
return new PortfolioResponse(portfolioRepository.save( | ||
new Portfolio( | ||
InstantProvider.getExpireAt(), | ||
portfolioStocks | ||
)).getId() | ||
); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public List<MonthlyDividendResponse> getMonthlyDividends(final UUID id) { | ||
return InstantProvider.generateNext12Months() | ||
.stream() | ||
.map(yearMonth -> MonthlyDividendResponse.of( | ||
yearMonth.getYear(), | ||
yearMonth.getMonthValue(), | ||
getDividendsOfLastYearAndMonth( | ||
portfolioRepository.findById(id) | ||
.orElseThrow(() -> new PortfolioNotFoundException(id)) | ||
.getPortfolioStocks().getPortfolioStocks(), | ||
yearMonth.getMonthValue()) | ||
) | ||
) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public YearlyDividendResponse getYearlyDividends(final UUID id) { | ||
|
||
List<SingleYearlyDividendResponse> dividends = portfolioRepository.findById(id) | ||
.orElseThrow(() -> new PortfolioNotFoundException(id)) | ||
.getPortfolioStocks().getPortfolioStocks() | ||
.stream().map(portfolioStock -> { | ||
Stock stock = stockRepository.findById(portfolioStock.getStockId()) | ||
.orElseThrow(() -> new StockIdNotFoundException(portfolioStock.getStockId())); | ||
return SingleYearlyDividendResponse.of( | ||
stock, portfolioStock.getShares(), getYearlyDividend(stock.getId()) | ||
); | ||
}) | ||
.filter(response -> response.totalDividend() != 0) | ||
.toList(); | ||
|
||
return YearlyDividendResponse.of(dividends); | ||
} | ||
|
||
private double getYearlyDividend(final UUID stockId) { | ||
return getLastYearDividendsByStockId(stockId) | ||
.stream() | ||
.mapToDouble(Dividend::getDividend) | ||
.sum(); | ||
} | ||
|
||
private List<Dividend> getLastYearDividendsByStockId(final UUID id) { | ||
return dividendRepository.findAllByIdAndYear(id, InstantProvider.getLastYear()); | ||
} | ||
|
||
private List<SingleMonthlyDividendResponse> getDividendsOfLastYearAndMonth( | ||
final List<PortfolioStock> portfolioStocks, final int month | ||
) { | ||
return portfolioStocks | ||
.stream() | ||
.flatMap(portfolioStock -> stockRepository.findById(portfolioStock.getStockId()) | ||
.map(stock -> getMonthlyDividendResponse(month, portfolioStock, stock)) | ||
.orElseThrow(() -> new StockIdNotFoundException(portfolioStock.getStockId()))) | ||
.toList(); | ||
} | ||
|
||
private Stream<SingleMonthlyDividendResponse> getMonthlyDividendResponse( | ||
final int month, final PortfolioStock portfolioStock, final Stock stock | ||
) { | ||
return getLastYearDividendsByStockIdAndMonth(portfolioStock.getStockId(), month) | ||
.stream() | ||
.map(dividend -> SingleMonthlyDividendResponse.of(stock, portfolioStock.getShares(), dividend)); | ||
} | ||
|
||
private List<Dividend> getLastYearDividendsByStockIdAndMonth(final UUID stockId, final int month) { | ||
return dividendRepository.findAllByIdAndYearAndMonth(stockId, InstantProvider.getLastYear(), month); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
...ain/java/nexters/payout/apiserver/portfolio/application/dto/request/PortfolioRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.Valid; | ||
import jakarta.validation.constraints.Size; | ||
|
||
import java.util.List; | ||
|
||
public record PortfolioRequest( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
@Valid | ||
@Size(min = 1) | ||
List<TickerShare> tickerShares | ||
) { | ||
} |
14 changes: 14 additions & 0 deletions
14
...src/main/java/nexters/payout/apiserver/portfolio/application/dto/request/TickerShare.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.Min; | ||
import jakarta.validation.constraints.NotEmpty; | ||
|
||
public record TickerShare( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "ticker name") | ||
@NotEmpty | ||
String ticker, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "share") | ||
@Min(value = 1) | ||
Integer share | ||
) { } |
32 changes: 32 additions & 0 deletions
32
.../nexters/payout/apiserver/portfolio/application/dto/response/MonthlyDividendResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
import java.util.Comparator; | ||
import java.util.List; | ||
|
||
public record MonthlyDividendResponse( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Integer year, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Integer month, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
List<SingleMonthlyDividendResponse> dividends, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Double totalDividend | ||
) { | ||
public static MonthlyDividendResponse of( | ||
final int year, final int month, final List<SingleMonthlyDividendResponse> dividends | ||
) { | ||
return new MonthlyDividendResponse( | ||
year, | ||
month, | ||
dividends.stream() | ||
.sorted(Comparator.comparingDouble(SingleMonthlyDividendResponse::totalDividend).reversed()) | ||
.toList(), | ||
dividends.stream() | ||
.mapToDouble(SingleMonthlyDividendResponse::totalDividend) | ||
.sum() | ||
); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...n/java/nexters/payout/apiserver/portfolio/application/dto/response/PortfolioResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
import java.util.UUID; | ||
|
||
public record PortfolioResponse( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
UUID id | ||
) { | ||
} |
28 changes: 28 additions & 0 deletions
28
...rs/payout/apiserver/portfolio/application/dto/response/SingleMonthlyDividendResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import nexters.payout.domain.dividend.domain.Dividend; | ||
import nexters.payout.domain.stock.domain.Stock; | ||
|
||
public record SingleMonthlyDividendResponse( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
String ticker, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
String logoUrl, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Integer share, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Double dividend, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Double totalDividend | ||
) { | ||
public static SingleMonthlyDividendResponse of(Stock stock, int share, Dividend dividend) { | ||
return new SingleMonthlyDividendResponse( | ||
stock.getTicker(), | ||
stock.getLogoUrl(), | ||
share, | ||
dividend.getDividend(), | ||
dividend.getDividend() * share | ||
); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
...ers/payout/apiserver/portfolio/application/dto/response/SingleYearlyDividendResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import nexters.payout.domain.stock.domain.Stock; | ||
|
||
public record SingleYearlyDividendResponse( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
String ticker, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
String logoUrl, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Integer share, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Double totalDividend | ||
) { | ||
public static SingleYearlyDividendResponse of(Stock stock, int share, double dividend) { | ||
return new SingleYearlyDividendResponse( | ||
stock.getTicker(), | ||
stock.getLogoUrl(), | ||
share, | ||
dividend * share | ||
); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
...a/nexters/payout/apiserver/portfolio/application/dto/response/YearlyDividendResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package nexters.payout.apiserver.portfolio.application.dto.response; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
import java.util.Comparator; | ||
import java.util.List; | ||
|
||
public record YearlyDividendResponse( | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
List<SingleYearlyDividendResponse> dividends, | ||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) | ||
Double totalDividend | ||
) { | ||
public static YearlyDividendResponse of(List<SingleYearlyDividendResponse> dividends) { | ||
|
||
dividends = dividends | ||
.stream() | ||
.sorted(Comparator.comparingDouble(SingleYearlyDividendResponse::totalDividend).reversed()) | ||
.toList(); | ||
return new YearlyDividendResponse( | ||
dividends, | ||
dividends | ||
.stream() | ||
.mapToDouble(SingleYearlyDividendResponse::totalDividend) | ||
.sum() | ||
); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...er/src/main/java/nexters/payout/apiserver/portfolio/presentation/PortfolioController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package nexters.payout.apiserver.portfolio.presentation; | ||
|
||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import nexters.payout.apiserver.portfolio.application.PortfolioQueryService; | ||
import nexters.payout.apiserver.portfolio.application.dto.request.PortfolioRequest; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.MonthlyDividendResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.PortfolioResponse; | ||
import nexters.payout.apiserver.portfolio.application.dto.response.YearlyDividendResponse; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/portfolios") | ||
@Slf4j | ||
public class PortfolioController implements PortfolioControllerDocs { | ||
|
||
private final PortfolioQueryService portfolioQueryService; | ||
|
||
@PostMapping | ||
public ResponseEntity<PortfolioResponse> createPortfolio(@RequestBody @Valid final PortfolioRequest portfolioRequest) { | ||
return ResponseEntity.ok(portfolioQueryService.createPortfolio(portfolioRequest)); | ||
} | ||
|
||
@GetMapping("/{id}/monthly") | ||
public ResponseEntity<List<MonthlyDividendResponse>> getMonthlyDividends(@PathVariable("id") final UUID portfolioId) { | ||
return ResponseEntity.ok(portfolioQueryService.getMonthlyDividends(portfolioId)); | ||
} | ||
|
||
@GetMapping("/{id}/yearly") | ||
public ResponseEntity<YearlyDividendResponse> getYearlyDividends(@PathVariable("id") final UUID portfolioId) { | ||
return ResponseEntity.ok(portfolioQueryService.getYearlyDividends(portfolioId)); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소하지만 stream과 map 사이에 개행이 있는 것이 더 깔끔할 것 같습니다👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map 안에서 stock 조회하는 쿼리도 함수로 분리하면 변환 로직이 더 눈에 잘 들어올 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
앗 넵넵 리팩토링해놓을게요!!