diff --git a/src/main/java/com/strcat/config/oauth/OAuthSuccessHandler.java b/src/main/java/com/strcat/config/oauth/OAuthSuccessHandler.java index b62ab53..7cbf1a0 100644 --- a/src/main/java/com/strcat/config/oauth/OAuthSuccessHandler.java +++ b/src/main/java/com/strcat/config/oauth/OAuthSuccessHandler.java @@ -4,6 +4,7 @@ import com.strcat.service.OAuthUserService; import com.strcat.util.JwtUtils; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -35,8 +36,12 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String provider = request.getRequestURI().split("/")[4]; User user = oAuthUserService.signIn(authentication.getName(), provider); String token = jwtUtils.createJwtToken(user.getId().toString()); + Cookie cookie = new Cookie("token", token); log.info("token: " + token); + cookie.setMaxAge(60 * 60); + cookie.setDomain(".strcat.me"); + response.sendRedirect(String.format("%s?token=%s", REDIRECT_URI, token)); } diff --git a/src/main/java/com/strcat/controller/BoardController.java b/src/main/java/com/strcat/controller/BoardController.java index 796ec8a..e8c990d 100644 --- a/src/main/java/com/strcat/controller/BoardController.java +++ b/src/main/java/com/strcat/controller/BoardController.java @@ -1,6 +1,5 @@ package com.strcat.controller; -import com.strcat.domain.Board; import com.strcat.domain.User; import com.strcat.dto.CreateBoardReqDto; import com.strcat.dto.CreateContentReqDto; @@ -10,7 +9,6 @@ import com.strcat.service.BoardService; import com.strcat.service.ContentService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -25,7 +23,6 @@ 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.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -57,9 +54,10 @@ public class BoardController { @ApiResponse(responseCode = "401", description = "인증 실패", content = { @Content(examples = {@ExampleObject("인증 실패")}) }) - public String createBoard(@Parameter(hidden = true) @RequestHeader("Authorization") String token, + public String createBoard(Authentication authentication, @RequestBody CreateBoardReqDto dto) { - return boardService.createBoard(dto, token); + Long userId = (Long) authentication.getPrincipal(); + return boardService.createBoard(dto, userId); } @PostMapping("/{boardId}/contents") @@ -84,9 +82,10 @@ public String createPicture(@PathVariable(name = "boardId") String encryptedBoar @GetMapping("/{boardId}") @SecurityRequirement(name = "Bearer Authentication") @Operation(summary = "보드 조회", description = "보드에 대한 모든 정보와 보드 소유자 여부를 반환합니다.") - public ReadBoardResDto readBoard(@Parameter(hidden = true) @RequestHeader("Authorization") String token, + public ReadBoardResDto readBoard(Authentication authentication, @PathVariable(name = "boardId") String encryptedBoardId) { - return boardService.readBoard(encryptedBoardId, token); + Long userId = (Long) authentication.getPrincipal(); + return boardService.readBoard(encryptedBoardId, userId); } @GetMapping("/{boardId}/summaries") diff --git a/src/main/java/com/strcat/controller/UserController.java b/src/main/java/com/strcat/controller/UserController.java index 2191718..4a0dc21 100644 --- a/src/main/java/com/strcat/controller/UserController.java +++ b/src/main/java/com/strcat/controller/UserController.java @@ -1,10 +1,11 @@ package com.strcat.controller; +import com.strcat.dto.HistoryDto; import com.strcat.dto.ReadMyInfoResDto; import com.strcat.service.BoardService; +import com.strcat.service.UserService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -12,8 +13,10 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestHeader; +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; @@ -34,12 +37,33 @@ @RequiredArgsConstructor public class UserController { private final BoardService boardService; + private final UserService userService; @GetMapping("/boards") @SecurityRequirement(name = "Bearer Authentication") @Operation(summary = "내 보드 조회", description = "내가 생성한 보드 정보 리스트를 반환합니다.") public List readMyBoardInfo( - @Parameter(hidden = true) @RequestHeader("Authorization") String token) { - return boardService.readMyBoardInfo(token); + Authentication authentication) { + Long userId = (Long) authentication.getPrincipal(); + + return userService.readMyBoardInfo(userId); + } + @GetMapping("/history") + @SecurityRequirement(name = "Bearer Authentication") + @Operation(summary = "최근 방문한 보드 조회", description = "내가 최근에 방문한 보드 리스트를 반환합니다.") + public HistoryDto readMyBoardHistory( + Authentication authentication) { + Long userId = (Long) authentication.getPrincipal(); + return userService.readMyBoardHistory(userId); + } + + @PostMapping("/history") + @SecurityRequirement(name = "Bearer Authentication") + @Operation(summary = "최근 방문한 보드 저장", description = "내가 최근에 방문한 보드 리스트를 저장합니다.") + public HistoryDto postMyBoardHistory( + Authentication authentication, @RequestBody HistoryDto dto) { + Long userId = (Long) authentication.getPrincipal(); + + return userService.postMyBoardHistory(userId, dto); } } diff --git a/src/main/java/com/strcat/domain/Board.java b/src/main/java/com/strcat/domain/Board.java index e627408..ace58d7 100644 --- a/src/main/java/com/strcat/domain/Board.java +++ b/src/main/java/com/strcat/domain/Board.java @@ -26,7 +26,6 @@ @Entity @Data -@ToString @NoArgsConstructor @EntityListeners(AuditingEntityListener.class) public class Board { @@ -55,6 +54,10 @@ public class Board { @OneToMany(mappedBy = "board", cascade = CascadeType.ALL) private List contents = new ArrayList<>(); + @JsonIgnore + @OneToMany(mappedBy = "board", cascade = CascadeType.ALL) + private List history; + public Board(String title, String theme, User user) { this.title = title; this.theme = theme; diff --git a/src/main/java/com/strcat/domain/History.java b/src/main/java/com/strcat/domain/History.java new file mode 100644 index 0000000..a7a67de --- /dev/null +++ b/src/main/java/com/strcat/domain/History.java @@ -0,0 +1,60 @@ +package com.strcat.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.strcat.dto.HistoryDto; +import com.strcat.dto.HistoryItem; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.apache.commons.lang3.builder.HashCodeExclude; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@Data +@ToString(exclude = {"user", "board"}) +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class History { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JsonIgnore + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @JsonIgnore + @ManyToOne + @JoinColumn(name = "board_id", nullable = false) + private Board board; + + @Column(name = "visited_at", nullable = false) + private LocalDateTime visitedAt; + + + public History(User user, Board board, LocalDateTime visitedAt) { + this.user = user; + this.board = board; + this.visitedAt = visitedAt; + } + + public HistoryItem toDto() { + return new HistoryItem(board.getEncryptedId(), board.getTitle(), visitedAt); + } +} diff --git a/src/main/java/com/strcat/domain/User.java b/src/main/java/com/strcat/domain/User.java index 610c1fc..98699b8 100644 --- a/src/main/java/com/strcat/domain/User.java +++ b/src/main/java/com/strcat/domain/User.java @@ -1,5 +1,6 @@ package com.strcat.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -9,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import lombok.Data; import lombok.NoArgsConstructor; @@ -30,4 +32,8 @@ public class User { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List boards; + + @JsonIgnore + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) + private List histories = new ArrayList<>(); } diff --git a/src/main/java/com/strcat/dto/HistoryDto.java b/src/main/java/com/strcat/dto/HistoryDto.java new file mode 100644 index 0000000..2593c8c --- /dev/null +++ b/src/main/java/com/strcat/dto/HistoryDto.java @@ -0,0 +1,6 @@ +package com.strcat.dto; + +import java.util.List; + +public record HistoryDto(List history) { +} diff --git a/src/main/java/com/strcat/dto/HistoryItem.java b/src/main/java/com/strcat/dto/HistoryItem.java new file mode 100644 index 0000000..3ce6eb5 --- /dev/null +++ b/src/main/java/com/strcat/dto/HistoryItem.java @@ -0,0 +1,17 @@ +package com.strcat.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; + +@Schema(example = """ + { + "encryptedBoardId": "string", + "title": "string", + "visitTime": "2024-02-07 07:54:54" + } + """) +public record HistoryItem(String encryptedBoardId, String title, + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") + LocalDateTime visitTime) { +} diff --git a/src/main/java/com/strcat/repository/BoardRepository.java b/src/main/java/com/strcat/repository/BoardRepository.java index c0f6ffb..da5da87 100644 --- a/src/main/java/com/strcat/repository/BoardRepository.java +++ b/src/main/java/com/strcat/repository/BoardRepository.java @@ -8,6 +8,5 @@ @Repository public interface BoardRepository extends JpaRepository { - List findByUserId(Long userId); - Optional findFirstByOrderByCreatedAtDesc(); + Optional findByEncryptedId(String encryptedId); } diff --git a/src/main/java/com/strcat/repository/HistoryRepository.java b/src/main/java/com/strcat/repository/HistoryRepository.java new file mode 100644 index 0000000..c225709 --- /dev/null +++ b/src/main/java/com/strcat/repository/HistoryRepository.java @@ -0,0 +1,21 @@ +package com.strcat.repository; + +import com.strcat.domain.History; +import com.strcat.domain.User; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; + +public interface HistoryRepository extends JpaRepository { + public List findByUserIdOrderByVisitedAtAsc(Long userId); + + @Modifying + @Query("DELETE FROM History h WHERE h.user = :user") + public void deleteHistoriesByUser(User user); + + @Modifying + @Query("DELETE FROM History h WHERE h.id = :id") + public void deleteById(Long id); + +} diff --git a/src/main/java/com/strcat/service/BoardService.java b/src/main/java/com/strcat/service/BoardService.java index b4207b5..00d0b68 100644 --- a/src/main/java/com/strcat/service/BoardService.java +++ b/src/main/java/com/strcat/service/BoardService.java @@ -3,16 +3,16 @@ import com.strcat.domain.Board; import com.strcat.domain.User; import com.strcat.dto.CreateBoardReqDto; +import com.strcat.dto.HistoryItem; import com.strcat.dto.ReadBoardResDto; import com.strcat.dto.ReadBoardSummaryResDto; -import com.strcat.dto.ReadMyInfoResDto; import com.strcat.exception.NotAcceptableException; import com.strcat.repository.BoardRepository; -import com.strcat.util.JwtUtils; +import com.strcat.repository.UserRepository; +import com.strcat.usecase.RecordHistoryUseCase; import com.strcat.util.SecureDataUtils; +import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -21,24 +21,12 @@ public class BoardService { private final BoardRepository boardRepository; private final SecureDataUtils secureDataUtils; - private final UserService userService; - private final JwtUtils jwtUtils; + private final UserRepository userRepository; + private final RecordHistoryUseCase recordHistoryUseCase; - public List findByUserId(Long userId) { - return boardRepository.findByUserId(userId); - } - - public List readMyBoardInfo(String token) { - User user = userService.getUser(token); - List boards = findByUserId(user.getId()); - return boards.stream() - .map(Board::toReadMyInfoResDto) - .collect(Collectors.toList()); - } - - public String createBoard(CreateBoardReqDto dto, String token) { + public String createBoard(CreateBoardReqDto dto, Long userId) { Board board; - User user = userService.getUser(token); + User user = userRepository.findById(userId).orElseThrow(() -> new NotAcceptableException("유저가 존재하지 않습니다.")); board = new Board(dto.getTitle(), dto.getTheme(), user); board = boardRepository.save(board); @@ -48,10 +36,13 @@ public String createBoard(CreateBoardReqDto dto, String token) { return encryptedBoardId; } - public ReadBoardResDto readBoard(String encryptedBoardId, String token) { - Board board = getBoard(encryptedBoardId); + public ReadBoardResDto readBoard(String encryptedBoardId, Long userId) { + Board board = boardRepository.findByEncryptedId(encryptedBoardId) + .orElseThrow(() -> new NotAcceptableException("존재하지 않는 보드입니다.")); + try { - Long userId = jwtUtils.parseUserId(jwtUtils.removeBearerString(token)); + recordHistoryUseCase.write(userId, + List.of(new HistoryItem(encryptedBoardId, board.getTitle(), LocalDateTime.now()))); Boolean isOwner = userId.equals(board.getUser().getId()); return board.toReadBoardResDto(isOwner); } catch (NotAcceptableException e) { @@ -60,16 +51,10 @@ public ReadBoardResDto readBoard(String encryptedBoardId, String token) { } public ReadBoardSummaryResDto readSummary(String encryptedBoardId) { - return getBoard(encryptedBoardId).toReadBoardSummaryDto(); + return boardRepository.findByEncryptedId(encryptedBoardId) + .orElseThrow(() -> new NotAcceptableException("존재하지 않는 보드입니다.")) + .toReadBoardSummaryDto(); } - public Board getBoard(String encryptedBoardId) { - Long boardId = secureDataUtils.decrypt(encryptedBoardId); - Optional optionalBoard = boardRepository.findById(boardId); - if (optionalBoard.isEmpty()) { - throw new NotAcceptableException("존재하지 않는 보드입니다."); - } - return optionalBoard.get(); - } } diff --git a/src/main/java/com/strcat/service/UserService.java b/src/main/java/com/strcat/service/UserService.java index d912b59..858e340 100644 --- a/src/main/java/com/strcat/service/UserService.java +++ b/src/main/java/com/strcat/service/UserService.java @@ -1,27 +1,40 @@ package com.strcat.service; +import com.strcat.domain.Board; +import com.strcat.domain.History; import com.strcat.domain.User; +import com.strcat.dto.HistoryDto; +import com.strcat.dto.HistoryItem; +import com.strcat.dto.ReadMyInfoResDto; import com.strcat.exception.NotAcceptableException; +import com.strcat.repository.BoardRepository; +import com.strcat.repository.HistoryRepository; import com.strcat.repository.UserRepository; +import com.strcat.usecase.RecordHistoryUseCase; import com.strcat.util.JwtUtils; +import jakarta.transaction.Transactional; +import java.util.List; import java.util.Optional; -import lombok.RequiredArgsConstructor; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service -@RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final HistoryRepository historyRepository; + private final RecordHistoryUseCase recordHistoryUseCase; private final JwtUtils jwtUtils; - public User getUser(String token) { - Long userId = jwtUtils.parseUserId(jwtUtils.removeBearerString(token)); - Optional user = userRepository.findById(userId); + @Autowired + public UserService(UserRepository userRepository, BoardRepository boardRepository, + HistoryRepository historyRepository, + JwtUtils jwtUtils) { - if (user.isEmpty()) { - throw new NotAcceptableException("유저가 존재하지 않습니다."); - } - return user.get(); + this.userRepository = userRepository; + this.historyRepository = historyRepository; + this.recordHistoryUseCase = new RecordHistoryUseCase(userRepository, boardRepository, historyRepository); + this.jwtUtils = jwtUtils; } public boolean isLogin(String token) { @@ -46,4 +59,29 @@ public Optional validate(String rawToken) { return Optional.empty(); } + + public List readMyBoardInfo(Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new NotAcceptableException("유저가 존재하지 않습니다.")); + List boards = user.getBoards(); + return boards.stream() + .map(Board::toReadMyInfoResDto) + .collect(Collectors.toList()); + } + + + public HistoryDto readMyBoardHistory(Long userId) { + List histories = historyRepository.findByUserIdOrderByVisitedAtAsc(userId); + List historyItems = histories.stream().map(History::toDto).toList(); + + return new HistoryDto(historyItems); + } + + @Transactional + public HistoryDto postMyBoardHistory(Long userId, HistoryDto dto) { + List result = recordHistoryUseCase.write(userId, dto.history()); + + return new HistoryDto(result.stream() + .map((history -> new HistoryItem(history.getBoard().getEncryptedId(), history.getBoard().getTitle(), + history.getVisitedAt()))).toList()); + } } \ No newline at end of file diff --git a/src/main/java/com/strcat/usecase/RecordHistoryUseCase.java b/src/main/java/com/strcat/usecase/RecordHistoryUseCase.java new file mode 100644 index 0000000..2a62e60 --- /dev/null +++ b/src/main/java/com/strcat/usecase/RecordHistoryUseCase.java @@ -0,0 +1,80 @@ +package com.strcat.usecase; + +import com.strcat.domain.Board; +import com.strcat.domain.History; +import com.strcat.domain.User; +import com.strcat.dto.HistoryItem; +import com.strcat.exception.NotAcceptableException; +import com.strcat.repository.BoardRepository; +import com.strcat.repository.HistoryRepository; +import com.strcat.repository.UserRepository; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Queue; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RecordHistoryUseCase { + private final UserRepository userRepository; + private final BoardRepository boardRepository; + private final HistoryRepository historyRepository; + + public List write(Long userId, List historyItems) { + User user = userRepository.findById(userId).orElseThrow(() -> new NotAcceptableException("유저를 찾을 수 없습니다")); + + Queue histories = new LinkedList<>(historyItems.stream().map((item) -> { + Board board = boardRepository.findByEncryptedId(item.encryptedBoardId()) + .orElseThrow(() -> new NotAcceptableException("보드를 찾을 수 없습니다")); + + return new History(user, board, item.visitTime()); + }).sorted(Comparator.comparing(History::getVisitedAt)).toList()); + + List origins = user.getHistories(); + + updateHistories(histories, origins); + filterRecentHistory(origins); + + return historyRepository.findByUserIdOrderByVisitedAtAsc(userId); + } + + // 모든 기록이 담긴 PriorityQueue에서 최신 10개의 데이터만 남기고 삭제하고 DB에 적용 + private void filterRecentHistory(List origins) { + Queue priorityQ = new PriorityQueue<>(Comparator.comparing(History::getVisitedAt).reversed()); + + priorityQ.addAll(origins); + + while (priorityQ.size() > 10) { + History history = priorityQ.poll(); + historyRepository.deleteById(history.getId()); + } + + historyRepository.saveAll(priorityQ); + } + + // 이미 해당 보드의 기록이 존재하면 보다 최신 기록으로 갱신하고 아니라면 새 기록을 추가함 + private void updateHistories(Queue histories, List origins) { + + while (!histories.isEmpty()) { + History history = histories.peek(); + boolean isIn = false; + + for (History origin: origins) { + if (origin.getBoard().getEncryptedId().equals(history.getBoard().getEncryptedId())) { + if (origin.getVisitedAt().compareTo(history.getVisitedAt()) > 0) { + origin.setVisitedAt(history.getVisitedAt()); + } + isIn = true; + break; + } + } + if (!isIn) { + origins.add(history); + } + histories.poll(); + } + } +} diff --git a/src/test/java/com/strcat/board/BoardServiceTest.java b/src/test/java/com/strcat/board/BoardServiceTest.java index 11c44c4..7669562 100644 --- a/src/test/java/com/strcat/board/BoardServiceTest.java +++ b/src/test/java/com/strcat/board/BoardServiceTest.java @@ -10,17 +10,16 @@ import com.strcat.exception.NotAcceptableException; import com.strcat.repository.BoardRepository; import com.strcat.repository.ContentRepository; +import com.strcat.repository.HistoryRepository; import com.strcat.repository.UserRepository; import com.strcat.service.BoardService; -import com.strcat.service.UserService; +import com.strcat.usecase.RecordHistoryUseCase; import com.strcat.util.JwtUtils; import com.strcat.util.SecureDataUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; @@ -35,28 +34,24 @@ public class BoardServiceTest { private final BoardRepository boardRepository; private final UserRepository userRepository; private final ContentRepository contentRepository; - private final JwtUtils jwtUtils; private final SecureDataUtils secureDataUtils; - private String token; + private User user; @Autowired public BoardServiceTest(BoardRepository boardRepository, UserRepository userRepository, - ContentRepository contentRepository) { + ContentRepository contentRepository, HistoryRepository historyRepository) { this.boardRepository = boardRepository; this.userRepository = userRepository; this.contentRepository = contentRepository; - this.jwtUtils = new JwtUtils("testtesttesttesttesttesttesttesttesttest"); + JwtUtils jwtUtils = new JwtUtils("testtesttesttesttesttesttesttesttesttest"); this.secureDataUtils = new SecureDataUtils("MyTestCode-32CharacterTestAPIKey"); - UserService userService = new UserService(userRepository, jwtUtils); - this.boardService = new BoardService(boardRepository, secureDataUtils, userService, - jwtUtils); + this.boardService = new BoardService(boardRepository, secureDataUtils, userRepository, new RecordHistoryUseCase(userRepository, boardRepository, historyRepository)); } @BeforeEach public void beforeEach() { final User user = new User(); - userRepository.save(user); - token = "Bearer " + jwtUtils.createJwtToken(user.getId().toString()); + this.user = userRepository.save(user); } @Nested @@ -67,7 +62,7 @@ class 성공 { CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); //when - String encryptedUrl = boardService.createBoard(dto, token); + String encryptedUrl = boardService.createBoard(dto, user.getId()); Board board = boardRepository.findAll().get(0); //then @@ -78,13 +73,13 @@ class 성공 { public void 보드주인일때조회() { //given CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - String encryptedUrl = boardService.createBoard(dto, token); + String encryptedUrl = boardService.createBoard(dto, user.getId()); Board board = boardRepository.findAll().get(0); ReadBoardResDto expect = new ReadBoardResDto(true, new BoardResponse(board.getEncryptedId(), board.getTitle(), board.getTheme(), board.getContents())); //when - ReadBoardResDto result = boardService.readBoard(encryptedUrl, token); + ReadBoardResDto result = boardService.readBoard(encryptedUrl, user.getId()); //then Assertions.assertEquals(expect, result); @@ -94,17 +89,16 @@ class 성공 { public void 보드주인아닌조회() { //given CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - String encryptedUrl = boardService.createBoard(dto, token); + String encryptedUrl = boardService.createBoard(dto, user.getId()); Board board = boardRepository.findAll().get(0); User user2 = new User(); userRepository.save(user2); - String token2 = "Bearer " + jwtUtils.createJwtToken(user2.getId().toString()); ReadBoardResDto expect = new ReadBoardResDto(false, new BoardResponse(board.getEncryptedId(), board.getTitle(), board.getTheme(), board.getContents())); //when - ReadBoardResDto result = boardService.readBoard(encryptedUrl, token2); + ReadBoardResDto result = boardService.readBoard(encryptedUrl, user2.getId()); //then Assertions.assertEquals(expect, result); @@ -114,7 +108,7 @@ class 성공 { public void 컨텐츠없는요약() { //given CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - String encryptedUrl = boardService.createBoard(dto, token); + String encryptedUrl = boardService.createBoard(dto, user.getId()); ReadBoardSummaryResDto expect = new ReadBoardSummaryResDto("가나다", "Green", 0, 0L); //when @@ -128,9 +122,9 @@ class 성공 { public void 컨텐츠존재요약() { //given CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - String encryptedUrl = boardService.createBoard(dto, token); - ReadBoardResDto boardRes = boardService.readBoard(encryptedUrl, token); - Board board = boardService.getBoard(encryptedUrl); + String encryptedUrl = boardService.createBoard(dto, user.getId()); + ReadBoardResDto boardRes = boardService.readBoard(encryptedUrl, user.getId()); + Board board = boardRepository.findByEncryptedId(encryptedUrl).orElseThrow(); Content content = new Content("test", "test", "test.jpg", board); contentRepository.save(content); boardRes.getBoard().getContents().add(content); // contents에 자동으로 content 추가가 안됨... @@ -154,51 +148,22 @@ class 실패 { //when Throwable thrown = Assertions.assertThrows(DataIntegrityViolationException.class, () -> //then - boardService.createBoard(dto, token) + boardService.createBoard(dto, user.getId()) ); Assertions.assertTrue(thrown.getMessage().contains("Data too long")); } - @ParameterizedTest - @ValueSource(strings = {"", "1", "12", "123", "1234", "12345", "123456", "1234567", "1234567890"}) - public void 잘못된토큰보드생성(String invalidToken) { - //given - CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - - //when - Throwable thrown = Assertions.assertThrows(NotAcceptableException.class, () -> - //then - boardService.createBoard(dto, invalidToken) - ); - Assertions.assertEquals("잘못된 토큰 형식입니다.", thrown.getMessage()); - } - - @Test - public void 잘못된URL보드조회() { - //given - CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - String encryptedUrl = boardService.createBoard(dto, token); - String invalidUrl = encryptedUrl.substring(4); - - //when - Throwable thrown = Assertions.assertThrows(NotAcceptableException.class, () -> - //then - boardService.readBoard(invalidUrl, token) - ); - Assertions.assertEquals("복호화에 실패했습니다.", thrown.getMessage()); - } - @Test public void 존재하지않는보드조회() { //given CreateBoardReqDto dto = new CreateBoardReqDto("가나다", "Green"); - boardService.createBoard(dto, token); + boardService.createBoard(dto, user.getId()); String validNotExistUrl = secureDataUtils.encrypt(Long.MAX_VALUE); //when Throwable thrown = Assertions.assertThrows(NotAcceptableException.class, () -> //then - boardService.readBoard(validNotExistUrl, token) + boardService.readBoard(validNotExistUrl, user.getId()) ); Assertions.assertEquals("존재하지 않는 보드입니다.", thrown.getMessage()); } diff --git a/src/test/java/com/strcat/user/UserServiceTest.java b/src/test/java/com/strcat/user/UserServiceTest.java new file mode 100644 index 0000000..28f4314 --- /dev/null +++ b/src/test/java/com/strcat/user/UserServiceTest.java @@ -0,0 +1,101 @@ +package com.strcat.user; + +import com.strcat.domain.Board; +import com.strcat.domain.History; +import com.strcat.domain.User; +import com.strcat.dto.HistoryDto; +import com.strcat.dto.HistoryItem; +import com.strcat.repository.BoardRepository; +import com.strcat.repository.HistoryRepository; +import com.strcat.repository.UserRepository; +import com.strcat.service.UserService; +import com.strcat.util.JwtUtils; +import com.strcat.util.SecureDataUtils; +import jakarta.transaction.Transactional; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +@SuppressWarnings("NonAsciiCharacters") +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DataJpaTest +@Transactional +public class UserServiceTest { + private final UserService userService; + private final UserRepository userRepository; + private final BoardRepository boardRepository; + private final HistoryRepository historyRepository; + private final SecureDataUtils secureDataUtils; + private User me; + private List boards; + + @Autowired + public UserServiceTest(UserRepository userRepository, BoardRepository boardRepository, + HistoryRepository historyRepository) { + this.boardRepository = boardRepository; + this.userRepository = userRepository; + this.historyRepository = historyRepository; + this.secureDataUtils = new SecureDataUtils("testtesttesttesttesttesttesttest"); + JwtUtils jwtUtils = new JwtUtils("testtesttesttesttesttesttesttesttesttest"); + this.userService = new UserService(userRepository, boardRepository, historyRepository, jwtUtils); + } + + @BeforeEach + public void beforeEach() { + User me = new User(); + User other = new User(); + + this.me = userRepository.save(me); + userRepository.save(other); + + for (int i = 1; i <= 10; ++i) { + boardRepository.save(new Board("test_board" + i, "green", other)); + } + + this.boards = boardRepository.findAll(); + + for (int i = 0; i < 10; ++i) { + Board board = boards.get(i); + board.setEncryptedId(secureDataUtils.encrypt(board.getId())); + } + } + + @Test + public void 최근기록저장_성공() { + //given + List history = boards.stream() + .map((board) -> new HistoryItem(board.getEncryptedId(), board.getTitle(), LocalDateTime.now())) + .toList(); + HistoryDto historyDto = new HistoryDto(history); + + //when + HistoryDto result = userService.postMyBoardHistory(me.getId(), historyDto); + + //then + Assertions.assertEquals(historyDto, result); + } + + @Test + public void 최근기록조회_성공() { + //given + User user = userRepository.findById(me.getId()).orElseThrow(); + LocalDateTime now = LocalDateTime.now(); + List history = boards.stream() + .map((board) -> new HistoryItem(board.getEncryptedId(), board.getTitle(), now)).toList(); + HistoryDto historyDto = new HistoryDto(history); + user.setHistories(boards.stream().map((board) -> new History(user, board, now)).toList()); + userService.postMyBoardHistory(user.getId(), historyDto); + + //when + HistoryDto result = userService.readMyBoardHistory(user.getId()); + + //then + Assertions.assertTrue(result.history().containsAll(historyDto.history())); + } +}