Skip to content
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: routes for buy and sell stocks #54

Merged
merged 12 commits into from
Mar 6, 2024
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package com.mandacarubroker.controller;

import com.mandacarubroker.domain.position.RequestStockOwnershipDTO;
import com.mandacarubroker.domain.position.ResponseStockOwnershipDTO;
import com.mandacarubroker.service.PortfolioService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -21,4 +27,20 @@ public PortfolioController(final PortfolioService receivedPortfolioService) {
public List<ResponseStockOwnershipDTO> getAuthenticatedUserStockPortfolio() {
return portfolioService.getAuthenticatedUserStockPortfolio();
}

@PostMapping("/{stockId}/buy")
public ResponseEntity<ResponseStockOwnershipDTO> buyStock(
@PathVariable final String stockId,
@RequestBody @Valid final RequestStockOwnershipDTO shares) {
ResponseStockOwnershipDTO stockPosition = portfolioService.buyStock(stockId, shares);
return ResponseEntity.ok(stockPosition);
}

@PostMapping("/{stockId}/sell")
public ResponseEntity<ResponseStockOwnershipDTO> sellStock(
@PathVariable final String stockId,
@RequestBody @Valid final RequestStockOwnershipDTO shares) {
ResponseStockOwnershipDTO stockPosition = portfolioService.sellStock(stockId, shares);
return ResponseEntity.ok(stockPosition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,3 @@ public ResponseEntity<Object> deleteUser(@PathVariable final String id) {
return ResponseEntity.noContent().build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

public interface StockOwnershipRepository extends JpaRepository<StockOwnership, String> {
List<StockOwnership> findByUserId(String userId);
StockOwnership findByUserIdAndStockId(String userId, String stockId);
}
145 changes: 143 additions & 2 deletions src/main/java/com/mandacarubroker/service/PortfolioService.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package com.mandacarubroker.service;

import com.mandacarubroker.domain.position.RequestStockOwnershipDTO;
import com.mandacarubroker.domain.position.ResponseStockOwnershipDTO;
import com.mandacarubroker.domain.position.StockOwnership;
import com.mandacarubroker.domain.position.StockOwnershipRepository;
import com.mandacarubroker.domain.stock.Stock;
import com.mandacarubroker.domain.user.User;
import com.mandacarubroker.domain.user.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Optional;

@Service
public class PortfolioService {
private final StockOwnershipRepository stockPositionRepository;
private final StockService stockService;
private final UserRepository userRepository;

public PortfolioService(final StockOwnershipRepository receivedStockPositionRepository) {
public PortfolioService(
final StockOwnershipRepository receivedStockPositionRepository,
final StockService receivedStockService,
final UserRepository recievedUserRepository
) {
this.stockPositionRepository = receivedStockPositionRepository;
this.stockService = receivedStockService;
this.userRepository = recievedUserRepository;
}

public List<ResponseStockOwnershipDTO> getAuthenticatedUserStockPortfolio() {
User user = AuthService.getAuthenticatedUser();
String userId = user.getId();
Expand All @@ -29,4 +42,132 @@ public List<ResponseStockOwnershipDTO> getPortfolioByUserId(final String userId)
.map(ResponseStockOwnershipDTO::fromStockPosition)
.toList();
}

public Optional<ResponseStockOwnershipDTO> getStockPositionByUserIdAndStockId(final String stockId) {
User user = AuthService.getAuthenticatedUser();
String userId = user.getId();

StockOwnership stockPosition = stockPositionRepository.findByUserIdAndStockId(userId, stockId);

if (stockPosition == null) {
return Optional.empty();
}

return Optional.of(ResponseStockOwnershipDTO.fromStockPosition(stockPosition));
}


private StockOwnership createStockPosition(
final RequestStockOwnershipDTO requestStockOwnershipDTO,
final Stock stock,
final User user) {
StockOwnership newStockPosition = new StockOwnership(requestStockOwnershipDTO, stock, user);
return stockPositionRepository.save(newStockPosition);
}

private Optional<StockOwnership> updateStockPositionShares(
final String stockOwnershipId,
final RequestStockOwnershipDTO requestStockOwnershipDTO
) {
return stockPositionRepository.findById(stockOwnershipId)
.map(stockOwnership -> {
stockOwnership.setShares(requestStockOwnershipDTO.shares());
return stockPositionRepository.save(stockOwnership);
});
}


private ResponseStockOwnershipDTO addStockPositionInPortfolio(
final Stock stock,
final User user,
final RequestStockOwnershipDTO shares
) {

Optional<ResponseStockOwnershipDTO> existentStockPosition =
getStockPositionByUserIdAndStockId(stock.getId());

if (existentStockPosition.isPresent()) {
Optional<StockOwnership> updatedStockPosition = updateStockPositionShares(
existentStockPosition.get().id(),
new RequestStockOwnershipDTO(
existentStockPosition.get().totalShares()
+ shares.shares())
);

if (updatedStockPosition.isEmpty()) {
throw new IllegalStateException("Error on update shares in portfolio");
}
return ResponseStockOwnershipDTO.fromStockPosition(
updatedStockPosition.get()
);
}

return ResponseStockOwnershipDTO.fromStockPosition(
createStockPosition(
new RequestStockOwnershipDTO(shares.shares()),
stock,
user
)
);
}

private void removeStockPositionFromPortfolio(final String stockPositionId) {
stockPositionRepository.deleteById(stockPositionId);
}

public ResponseStockOwnershipDTO buyStock(final String stockId, final RequestStockOwnershipDTO shares) {
User user = AuthService.getAuthenticatedUser();
Optional<Stock> stock = stockService.getStockById(stockId);

if (stock.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Stock not found");
}
double userBalance = user.getBalance();
double stockBoughtPrice = stock.get().getPrice() * shares.shares();
if (userBalance < stockBoughtPrice) {
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Insufficient balance");
}
ResponseStockOwnershipDTO boughtStockPosition = addStockPositionInPortfolio(stock.get(), user, shares);
user.setBalance(userBalance - stockBoughtPrice);
userRepository.save(user);
return boughtStockPosition;
}

public ResponseStockOwnershipDTO sellStock(final String stockId, final RequestStockOwnershipDTO shares) {
User user = AuthService.getAuthenticatedUser();
Optional<Stock> stock = stockService.getStockById(stockId);
Optional<ResponseStockOwnershipDTO> userStockPosition = getStockPositionByUserIdAndStockId(stockId);

if (userStockPosition.isEmpty() || stock.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Stock not found");
}
int sharesToSell = shares.shares();
int userShares = userStockPosition.get().totalShares();
if (sharesToSell > userShares) {
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY,
"User with insufficient shares to sell");
}
double userBalance = user.getBalance();
double stocksSoldPrice = stock.get().getPrice() * sharesToSell;
int remainingShares = userShares - sharesToSell;

Optional<StockOwnership> updatedStockPosition = updateStockPositionShares(
userStockPosition.get().id(),
new RequestStockOwnershipDTO(
remainingShares
)
);

if (updatedStockPosition.isEmpty()) {
throw new IllegalStateException("Error on update shares in portfolio");
}

if (remainingShares == 0) {
removeStockPositionFromPortfolio(userStockPosition.get().id());
}
user.setBalance(userBalance + stocksSoldPrice);
userRepository.save(user);

return ResponseStockOwnershipDTO.fromStockPosition(updatedStockPosition.get());
}
}
1 change: 1 addition & 0 deletions src/main/java/com/mandacarubroker/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,4 @@ public Optional<ResponseUserDTO> findByUsername(final String username) {
return user.map(this::userToResponseUserDTO);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.mandacarubroker.domain.stock.Stock;
import com.mandacarubroker.domain.user.RequestUserDTO;
import com.mandacarubroker.domain.user.User;
import com.mandacarubroker.domain.user.UserRepository;
import com.mandacarubroker.security.SecuritySecretsMock;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -24,6 +25,8 @@
public class PortfolioServiceTest {
@MockBean
private StockOwnershipRepository stockPositionRepository;
private StockService stockService;
private UserRepository userRepository;

private PortfolioService portfolioService;

Expand Down Expand Up @@ -60,7 +63,14 @@ void setUp() {
stockPositionRepository = Mockito.mock(StockOwnershipRepository.class);
Mockito.when(stockPositionRepository.findByUserId(validUser.getId())).thenReturn(givenStockOwnerships);

portfolioService = new PortfolioService(stockPositionRepository);
stockService = Mockito.mock(StockService.class);
userRepository = Mockito.mock(UserRepository.class);

portfolioService = new PortfolioService(
stockPositionRepository,
stockService,
userRepository
);
}

@Test
Expand Down
Loading