Skip to content

Commit

Permalink
feat: list stocks owned (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
izaiasmachado authored Mar 3, 2024
1 parent b88d14e commit 3ab53c7
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 15 deletions.
19 changes: 4 additions & 15 deletions src/main/java/com/mandacarubroker/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@
import com.mandacarubroker.domain.auth.RequestAuthUserDTO;
import com.mandacarubroker.domain.auth.ResponseAuthUserDTO;
import com.mandacarubroker.domain.user.ResponseUserDTO;
import com.mandacarubroker.domain.user.User;
import com.mandacarubroker.service.AuthService;
import com.mandacarubroker.service.UserService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;

@RestController
Expand All @@ -43,16 +40,8 @@ public ResponseEntity<ResponseAuthUserDTO> login(@Valid @RequestBody final Reque

@GetMapping("/me")
public ResponseEntity<ResponseUserDTO> getCurrentUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();

if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
Optional<ResponseUserDTO> user = userService.findByUsername(userDetails.getUsername());
return ResponseEntity.ok(user.get());
}

return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
User user = AuthService.getAuthenticatedUser();
ResponseUserDTO responseUserDTO = ResponseUserDTO.fromUser(user);
return ResponseEntity.ok(responseUserDTO);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.mandacarubroker.controller;

import com.mandacarubroker.domain.position.ResponseStockOwnershipDTO;
import com.mandacarubroker.service.PortfolioService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/portfolio")
public class PortfolioController {
private final PortfolioService portfolioService;

public PortfolioController(final PortfolioService receivedPortfolioService) {
this.portfolioService = receivedPortfolioService;
}

@GetMapping
public List<ResponseStockOwnershipDTO> getAuthenticatedUserStockPortfolio() {
return portfolioService.getAuthenticatedUserStockPortfolio();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mandacarubroker.domain.position;

import jakarta.validation.constraints.Positive;

public record RequestStockOwnershipDTO(
@Positive(message = "Share amount must be greater than zero")
int shares
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mandacarubroker.domain.position;

import com.mandacarubroker.domain.stock.Stock;

public record ResponseStockOwnershipDTO(
String id,
Stock stock,
int totalShares,
double positionValue
) {
public static ResponseStockOwnershipDTO fromStockPosition(final StockOwnership stockPosition) {
final Stock stock = stockPosition.getStock();

return new ResponseStockOwnershipDTO(
stockPosition.getId(),
stock,
stockPosition.getShares(),
stockPosition.getTotalValue()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.mandacarubroker.domain.position;

import com.mandacarubroker.domain.stock.Stock;
import com.mandacarubroker.domain.user.User;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Table(name = "stock_ownership")
@Entity(name = "stock_ownership")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class StockOwnership {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
private int shares;
@OneToOne
private Stock stock;
@OneToOne
private User user;

public StockOwnership(final RequestStockOwnershipDTO requestStockPositionDTO, final Stock receivedStock, final User receivedUser) {
this.shares = requestStockPositionDTO.shares();
this.stock = receivedStock;
this.user = receivedUser;
}

public double getTotalValue() {
return shares * stock.getPrice();
}

public Stock getStock() {
return stock;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mandacarubroker.domain.position;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface StockOwnershipRepository extends JpaRepository<StockOwnership, String> {
List<StockOwnership> findByUserId(String userId);
}
11 changes: 11 additions & 0 deletions src/main/java/com/mandacarubroker/domain/user/ResponseUserDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,16 @@ public record ResponseUserDTO(
LocalDate birthDate,
double balance
) {
public static ResponseUserDTO fromUser(final User user) {
return new ResponseUserDTO(
user.getId(),
user.getEmail(),
user.getUsername(),
user.getFirstName(),
user.getLastName(),
user.getBirthDate(),
user.getBalance()
);
}
}

15 changes: 15 additions & 0 deletions src/main/java/com/mandacarubroker/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.mandacarubroker.domain.auth.ResponseAuthUserDTO;
import com.mandacarubroker.domain.user.User;
import com.mandacarubroker.domain.user.UserRepository;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.Optional;
Expand Down Expand Up @@ -54,4 +57,16 @@ public Optional<User> getUserGivenCredentials(final RequestAuthUserDTO requestAu

return Optional.of(user);
}

public static User getAuthenticatedUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();

if (authentication == null || !authentication.isAuthenticated()) {
throw new IllegalStateException("User not authenticated");
}

Object principal = authentication.getPrincipal();
return (User) principal;
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/mandacarubroker/service/PortfolioService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mandacarubroker.service;

import com.mandacarubroker.domain.position.ResponseStockOwnershipDTO;
import com.mandacarubroker.domain.position.StockOwnership;
import com.mandacarubroker.domain.position.StockOwnershipRepository;
import com.mandacarubroker.domain.user.User;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PortfolioService {
private final StockOwnershipRepository stockPositionRepository;

public PortfolioService(final StockOwnershipRepository receivedStockPositionRepository) {
this.stockPositionRepository = receivedStockPositionRepository;
}

public List<ResponseStockOwnershipDTO> getAuthenticatedUserStockPortfolio() {
User user = AuthService.getAuthenticatedUser();
String userId = user.getId();
return getPortfolioByUserId(userId);
}

public List<ResponseStockOwnershipDTO> getPortfolioByUserId(final String userId) {
List<StockOwnership> stockPositions = stockPositionRepository.findByUserId(userId);

return stockPositions.stream()
.map(ResponseStockOwnershipDTO::fromStockPosition)
.toList();
}
}
7 changes: 7 additions & 0 deletions src/main/resources/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ INSERT INTO users (id, email, username, password, first_name, last_name, birth_d
VALUES
('b2d13c5a-3df0-4673-b3e6-49244f395ac7', '[email protected]', 'admin', '$2a$10$oUmo9dGdjnbdWeYlq7tsNuZo/r.pwI6T8JbEu2bp26Y5Zg7uzrKMy', 'Ademir', 'Ademilson', '2002-01-01', 777.7, 'ADMIN')
ON CONFLICT (id) DO NOTHING;

INSERT INTO stock_ownership (id, stock_id, user_id, shares)
VALUES
('b3d13c5a-3df0-4673-b3e6-49244f395ac9', 'b2d13c5a-3df0-4673-b3e6-49244f395ac9', 'b2d13c5a-3df0-4673-b3e6-49244f395ac7', 10),
('b3d13c5a-3df0-4673-b3e6-49244f395ad9', 'b2d13c5a-3df0-4673-b3e6-49244f395ad9', 'b2d13c5a-3df0-4673-b3e6-49244f395ac7', 20)
ON CONFLICT (id) DO NOTHING;

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE stock_ownership (
id VARCHAR PRIMARY KEY,
shares INT NOT NULL,
stock_id VARCHAR NOT NULL,
user_id VARCHAR NOT NULL,
FOREIGN KEY (stock_id) REFERENCES stock(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.mandacarubroker.controller;

import com.mandacarubroker.domain.position.ResponseStockOwnershipDTO;
import com.mandacarubroker.domain.stock.RequestStockDTO;
import com.mandacarubroker.domain.stock.Stock;
import com.mandacarubroker.security.SecuritySecretsMock;
import com.mandacarubroker.service.PortfolioService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.List;

import static com.mandacarubroker.domain.stock.StockUtils.assertStocksAreEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class PortfolioControllerTest {
@MockBean
private PortfolioService portfolioService;
private final RequestStockDTO requestAppleStock = new RequestStockDTO("AAPL", "Apple Inc", 100.00);
private final RequestStockDTO requestGoogleStock = new RequestStockDTO("GOOGL", "Alphabet Inc", 2000.00);
private final Stock appleStock = new Stock(requestAppleStock);
private final Stock googleStock = new Stock(requestGoogleStock);
private final List<ResponseStockOwnershipDTO> storedStockPortfolio = List.of(
new ResponseStockOwnershipDTO("apple-stock-id", appleStock, 100, 10000.00),
new ResponseStockOwnershipDTO("google-stock-id", googleStock, 200, 400000.00)
);

@BeforeEach
void setUp() {
SecuritySecretsMock.mockStatic();

portfolioService = Mockito.mock(PortfolioService.class);

Mockito.when(portfolioService.getAuthenticatedUserStockPortfolio()).thenReturn(storedStockPortfolio);
}

@Test
void itShouldReturnTheAuthenticatedUserStockPortfolio() {
List<ResponseStockOwnershipDTO> expectedStockPortfolio = storedStockPortfolio;
List<ResponseStockOwnershipDTO> actualStockPortfolio = portfolioService.getAuthenticatedUserStockPortfolio();

assertEquals(expectedStockPortfolio.size(), actualStockPortfolio.size());

for (int i = 0; i < expectedStockPortfolio.size(); i++) {
ResponseStockOwnershipDTO expectedStockOwnership = expectedStockPortfolio.get(i);
ResponseStockOwnershipDTO actualStockOwnership = actualStockPortfolio.get(i);
Stock expectedStock = expectedStockOwnership.stock();
Stock actualStock = actualStockOwnership.stock();

assertEquals(expectedStockOwnership.id(), actualStockOwnership.id());
assertEquals(expectedStockOwnership.totalShares(), actualStockOwnership.totalShares());
assertEquals(expectedStockOwnership.positionValue(), actualStockOwnership.positionValue());
assertStocksAreEqual(expectedStock, actualStock);
}
}
}
15 changes: 15 additions & 0 deletions src/test/java/com/mandacarubroker/domain/stock/StockUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.mandacarubroker.domain.stock;

import static org.junit.jupiter.api.Assertions.assertEquals;

public final class StockUtils {
private StockUtils() {
}

public static void assertStocksAreEqual(final Stock expectedStock, final Stock actualStock) {
assertEquals(expectedStock.getId(), actualStock.getId());
assertEquals(expectedStock.getSymbol(), actualStock.getSymbol());
assertEquals(expectedStock.getCompanyName(), actualStock.getCompanyName());
assertEquals(expectedStock.getPrice(), actualStock.getPrice());
}
}
Loading

0 comments on commit 3ab53c7

Please sign in to comment.