PortfolioService.java 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package com.danielbohry.stocks.service;
  2. import com.danielbohry.stocks.context.UserContextHolder;
  3. import com.danielbohry.stocks.domain.Portfolio;
  4. import com.danielbohry.stocks.domain.Quote;
  5. import com.danielbohry.stocks.domain.Stock;
  6. import com.danielbohry.stocks.exception.BadRequestException;
  7. import com.danielbohry.stocks.exception.NotFoundException;
  8. import com.danielbohry.stocks.exception.UnauthorizedException;
  9. import com.danielbohry.stocks.repository.portfolio.PortfolioEntity;
  10. import com.danielbohry.stocks.repository.portfolio.PortfolioEntity.PortfolioStock;
  11. import com.danielbohry.stocks.repository.portfolio.PortfolioRepository;
  12. import com.danielbohry.stocks.service.ExchangeService.ExchangeRateResponse;
  13. import lombok.AllArgsConstructor;
  14. import lombok.extern.slf4j.Slf4j;
  15. import org.apache.commons.lang3.ObjectUtils;
  16. import org.springframework.stereotype.Service;
  17. import java.math.BigDecimal;
  18. import java.math.RoundingMode;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Objects;
  22. import java.util.UUID;
  23. import static com.danielbohry.stocks.domain.Portfolio.convert;
  24. import static java.time.LocalDateTime.now;
  25. import static java.util.Collections.emptyList;
  26. @Slf4j
  27. @Service
  28. @AllArgsConstructor
  29. public class PortfolioService {
  30. private final PortfolioRepository repository;
  31. private final StockService stockService;
  32. private final ExchangeService exchangeService;
  33. private final PortfolioEncryptService encryptService;
  34. public List<String> getAllIds() {
  35. return repository.findAllPortfolioIds();
  36. }
  37. public List<Portfolio> getByUser(String userId, String currency) {
  38. return repository.findAllByUser(userId).stream()
  39. .map(entity -> get(entity.getId(), currency))
  40. .toList();
  41. }
  42. public Portfolio get(String id, String currency) {
  43. PortfolioEntity entity = repository.findById(id)
  44. .orElseThrow(() -> new NotFoundException("No portfolio found with id: " + id));
  45. String encrypted = entity.getEncryptedStocks();
  46. if (encrypted != null) {
  47. entity.setStocks(encryptService.decryptStocks(encrypted));
  48. }
  49. Portfolio portfolio = convert(entity);
  50. ExchangeRateResponse exchangeRate = exchangeService.getCurrentRate(currency);
  51. Map<String, BigDecimal> rates = exchangeRate.getConversionRates();
  52. BigDecimal targetRate = rates.getOrDefault(currency, BigDecimal.ONE);
  53. log.info("Getting portfolio [{}] and converting to [{}]", id, currency);
  54. List<Stock> updatedStocks = portfolio.getStocks().stream()
  55. .peek(stock -> {
  56. Quote quote = stockService.getStockQuote(stock.getCode());
  57. stock.setName(quote.getName());
  58. BigDecimal quoteRate = rates.getOrDefault(quote.getCurrency(), BigDecimal.ONE);
  59. BigDecimal convertedPrice = quote.getPrice()
  60. .divide(quoteRate, 2, RoundingMode.HALF_UP)
  61. .multiply(targetRate);
  62. stock.setPrice(convertedPrice);
  63. stock.setTotal(convertedPrice.multiply(new BigDecimal(stock.getQuantity())));
  64. }).toList();
  65. portfolio.setStocks(updatedStocks);
  66. return portfolio;
  67. }
  68. public Portfolio create() {
  69. String id = UUID.randomUUID().toString();
  70. PortfolioEntity toSave = PortfolioEntity.builder()
  71. .id(id)
  72. .stocks(emptyList())
  73. .user(UserContextHolder.get().getId())
  74. .createdAt(now())
  75. .updatedAt(now())
  76. .build();
  77. return convert(repository.save(toSave));
  78. }
  79. public Portfolio update(String id, List<Stock> stocks) {
  80. log.info("Updating portfolio [{}]", id);
  81. PortfolioEntity toUpdate = repository.findById(id).orElseThrow(() -> new NotFoundException("Failed to update portfolio with id: " + id));
  82. if (!Objects.equals(toUpdate.getUser(), UserContextHolder.get().getId()) || UserContextHolder.isAdmin()) {
  83. throw new UnauthorizedException("You do not have permission to update portfolio");
  84. }
  85. validate(stocks);
  86. toUpdate.setUpdatedAt(now());
  87. toUpdate.setEncryptedStocks(encryptService.encryptStocks(stocks.stream()
  88. .map(stock -> new PortfolioStock(stock.getCode(), stock.getQuantity()))
  89. .toList()));
  90. toUpdate.setStocks(null);
  91. PortfolioEntity updated = repository.save(toUpdate);
  92. List<PortfolioStock> decryptStocks = encryptService.decryptStocks(updated.getEncryptedStocks());
  93. updated.setStocks(decryptStocks);
  94. return convert(updated);
  95. }
  96. public void delete(String id) {
  97. log.info("Deleting portfolio [{}]", id);
  98. repository.deleteById(id);
  99. }
  100. public void cleanup() {
  101. repository.findAll().forEach(portfolio -> {
  102. if ((ObjectUtils.isEmpty(portfolio.getEncryptedStocks()))) {
  103. log.info("Removing empty portfolio [{}]", portfolio.getId());
  104. repository.deleteById(portfolio.getId());
  105. }
  106. });
  107. }
  108. private void validate(List<Stock> stocks) {
  109. boolean anyInvalid = stocks.stream()
  110. .anyMatch(stock -> !stockService.isValid(stock.getCode()));
  111. if (anyInvalid) {
  112. throw new BadRequestException("Invalid stock found");
  113. }
  114. }
  115. }