diff --git a/backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java b/backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java index 9c1e7c16..fbb77cc1 100644 --- a/backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java +++ b/backend/src/main/java/org/sejongisc/backend/board/controller/BoardController.java @@ -98,8 +98,10 @@ public void deletePost( public ResponseEntity> getPosts( @RequestParam UUID boardId, @RequestParam(defaultValue = "0") int pageNumber, - @RequestParam(defaultValue = "20") int pageSize) { - return ResponseEntity.ok(postService.getPosts(boardId, pageNumber, pageSize)); + @RequestParam(defaultValue = "20") int pageSize, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + UUID userId = customUserDetails.getUserId(); + return ResponseEntity.ok(postService.getPosts(boardId, userId, pageNumber, pageSize)); } // 게시글 검색 @@ -114,8 +116,10 @@ public ResponseEntity> searchPosts( @RequestParam UUID boardId, @RequestParam String keyword, @RequestParam(defaultValue = "0") int pageNumber, - @RequestParam(defaultValue = "20") int pageSize) { - return ResponseEntity.ok(postService.searchPosts(boardId, keyword, pageNumber, pageSize)); + @RequestParam(defaultValue = "20") int pageSize, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + UUID userId = customUserDetails.getUserId(); + return ResponseEntity.ok(postService.searchPosts(boardId, userId, keyword, pageNumber, pageSize)); } // 게시물 상세 조회 @@ -129,8 +133,10 @@ public ResponseEntity> searchPosts( public ResponseEntity getPostDetail( @PathVariable UUID postId, @RequestParam(defaultValue = "0") int commentPageNumber, - @RequestParam(defaultValue = "20") int commentPageSize) { - PostResponse response = postService.getPostDetail(postId, commentPageNumber, commentPageSize); + @RequestParam(defaultValue = "20") int commentPageSize, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + UUID userId = customUserDetails.getUserId(); + PostResponse response = postService.getPostDetail(postId, userId, commentPageNumber, commentPageSize); return ResponseEntity.ok(response); } @@ -149,7 +155,7 @@ public ResponseEntity createBoard( return ResponseEntity.ok().build(); } - // 게시판 생성 + // 최상위 게시판 목록 조회 @Operation( summary = "부모 게시판 목록 조회", description = "최상위 부모 게시판들의 목록을 조회합니다." @@ -161,7 +167,7 @@ public ResponseEntity> getParentBoards( } - // 게시판 생성 + // 하위 게시판 목록 조회 @Operation( summary = "하위 게시판 목록 조회", description = "하위 게시판 목록을 조회합니다." @@ -172,7 +178,7 @@ public ResponseEntity> getChildBoards( return ResponseEntity.ok(postService.getChildBoards()); } - // 게시글 삭제 + // 게시판 삭제 @Operation( summary = "게시판 삭제", description = "게시판 ID를 통해 게시판을 삭제합니다." diff --git a/backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java b/backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java index 780ce15c..3c60b94f 100644 --- a/backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java @@ -28,6 +28,8 @@ public class PostResponse { private Integer bookmarkCount; private Integer likeCount; private Integer commentCount; + private Boolean isLiked; + private Boolean isBookmarked; private LocalDateTime createdDate; private LocalDateTime updatedDate; private Page comments; diff --git a/backend/src/main/java/org/sejongisc/backend/board/service/PostService.java b/backend/src/main/java/org/sejongisc/backend/board/service/PostService.java index cabdbf9a..de0625f0 100644 --- a/backend/src/main/java/org/sejongisc/backend/board/service/PostService.java +++ b/backend/src/main/java/org/sejongisc/backend/board/service/PostService.java @@ -20,20 +20,23 @@ public interface PostService { void deletePost(UUID postId, UUID userId); // 게시물 조회 - Page getPosts(UUID boardId, int pageNumber, int pageSize); + Page getPosts(UUID boardId, UUID userId, int pageNumber, int pageSize); // 게시물 검색 - Page searchPosts(UUID boardId, String keyword, int pageNumber, int pageSize); + Page searchPosts(UUID boardId, UUID userId, String keyword, int pageNumber, int pageSize); // 게시물 상세 조회 - PostResponse getPostDetail(UUID postId, int pageNumber, int pageSize); + PostResponse getPostDetail(UUID postId, UUID userId, int pageNumber, int pageSize); // 게시판 생성 void createBoard(BoardRequest request, UUID userId); // 부모 게시판 목록 조회 List getParentBoards(); + // 하위 게시판 목록 조회 List getChildBoards(); + + // 게시판 삭제 void deleteBoard(UUID boardId, UUID boardUserId); } diff --git a/backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java b/backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java index 85ce754f..958372f4 100644 --- a/backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java +++ b/backend/src/main/java/org/sejongisc/backend/board/service/PostServiceImpl.java @@ -1,11 +1,8 @@ package org.sejongisc.backend.board.service; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; import java.util.stream.Stream; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.sejongisc.backend.board.dto.BoardRequest; @@ -24,7 +21,6 @@ import org.sejongisc.backend.board.repository.PostBookmarkRepository; import org.sejongisc.backend.board.repository.PostLikeRepository; import org.sejongisc.backend.board.repository.PostRepository; -import org.sejongisc.backend.board.repository.projection.PostIdUserIdProjection; import org.sejongisc.backend.common.exception.CustomException; import org.sejongisc.backend.common.exception.ErrorCode; import org.sejongisc.backend.user.dao.UserRepository; @@ -175,6 +171,8 @@ public void deletePost(UUID postId, UUID userId) { postRepository.delete(post); } + // 게시판 삭제 + @Override @Transactional public void deleteBoard(UUID boardId, UUID boardUserId) { User user = userRepository.findById(boardUserId).orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); @@ -200,7 +198,7 @@ public void deleteBoard(UUID boardId, UUID boardUserId) { // 게시물 조회 (해당 게시판의 게시물) @Override @Transactional(readOnly = true) - public Page getPosts(UUID boardId, int pageNumber, int pageSize) { + public Page getPosts(UUID boardId, UUID userId, int pageNumber, int pageSize) { Pageable pageable = PageRequest.of( pageNumber, pageSize, @@ -211,16 +209,20 @@ public Page getPosts(UUID boardId, int pageNumber, int pageSize) { Board board = boardRepository.findById(boardId) .orElseThrow(() -> new CustomException(ErrorCode.BOARD_NOT_FOUND)); + // 유저 조회 (좋아요/북마크 여부 확인용) + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + // 해당 게시판의 게시물 조회 Page posts = postRepository.findAllByBoard(board, pageable); - return posts.map(this::mapToPostResponse); + return posts.map(post -> mapToPostResponse(post, user)); } // 게시물 검색 (제목/내용) @Override @Transactional(readOnly = true) - public Page searchPosts(UUID boardId, String keyword, int pageNumber, int pageSize) { + public Page searchPosts(UUID boardId, UUID userId, String keyword, int pageNumber, int pageSize) { Pageable pageable = PageRequest.of( pageNumber, pageSize, @@ -235,13 +237,17 @@ public Page searchPosts(UUID boardId, String keyword, int pageNumb Page posts = postRepository.searchByBoardAndKeyword( board, keyword, pageable); - return posts.map(this::mapToPostResponse); + // 유저 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + return posts.map(post -> mapToPostResponse(post, user)); } // 게시물 상세 조회 @Override @Transactional(readOnly = true) - public PostResponse getPostDetail(UUID postId, int pageNumber, int pageSize) { + public PostResponse getPostDetail(UUID postId, UUID userId, int pageNumber, int pageSize) { // 게시물 조회 Post post = postRepository.findById(postId) .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); @@ -277,18 +283,11 @@ public PostResponse getPostDetail(UUID postId, int pageNumber, int pageSize) { .map(PostAttachmentResponse::of) .toList(); - // PostResponse DTO를 직접 빌드하여 반환 - return PostResponse.builder() - .postId(post.getPostId()) - .board(BoardResponse.from(post.getBoard())) - .user(UserInfoResponse.from(post.getUser())) - .title(post.getTitle()) - .content(post.getContent()) - .bookmarkCount(post.getBookmarkCount()) - .likeCount(post.getLikeCount()) - .commentCount(post.getCommentCount()) - .createdDate(post.getCreatedDate()) - .updatedDate(post.getUpdatedDate()) + // 유저 조회 + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + return getCommonPostBuilder(post, user) .comments(commentResponses) .attachments(attachmentResponses) .build(); @@ -341,11 +340,11 @@ public List getChildBoards() { List childBoards = boardRepository.findAllByParentBoardIsNotNull(); return childBoards.stream() - .map(BoardResponse::from) - .toList(); + .map(BoardResponse::from) + .toList(); } - private PostResponse mapToPostResponse(Post post) { + private PostResponse.PostResponseBuilder getCommonPostBuilder(Post post, User user) { return PostResponse.builder() .postId(post.getPostId()) .user(UserInfoResponse.from(post.getUser())) @@ -357,6 +356,12 @@ private PostResponse mapToPostResponse(Post post) { .commentCount(post.getCommentCount()) .createdDate(post.getCreatedDate()) .updatedDate(post.getUpdatedDate()) + .isLiked(postLikeRepository.existsByUserUserIdAndPostPostId(user.getUserId(), post.getPostId())) + .isBookmarked(postBookmarkRepository.existsByUserUserIdAndPostPostId(user.getUserId(), post.getPostId())); + } + + private PostResponse mapToPostResponse(Post post, User user) { + return getCommonPostBuilder(post, user) .build(); } -} +} \ No newline at end of file diff --git a/backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java b/backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java index d7cedc68..8dd734c9 100644 --- a/backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java +++ b/backend/src/test/java/org/sejongisc/backend/board/service/PostServiceImplTest.java @@ -38,7 +38,7 @@ import org.sejongisc.backend.common.exception.CustomException; import org.sejongisc.backend.common.exception.ErrorCode; import org.sejongisc.backend.user.dao.UserRepository; -import org.sejongisc.backend.user.entity.Role; // Role Enum import 필요 +import org.sejongisc.backend.user.entity.Role; import org.sejongisc.backend.user.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -92,7 +92,7 @@ void setUp() { .userId(userId) .email("test@example.com") .name("Tester") - .role(Role.TEAM_MEMBER) // 또는 Role.USER (프로젝트 Enum 정의에 맞게) + .role(Role.TEAM_MEMBER) // 실제 프로젝트 Enum에 맞게 설정 .build(); mockParentBoard = Board.builder() @@ -106,7 +106,7 @@ void setUp() { .boardId(boardId) .boardName("Child Board") .parentBoard(mockParentBoard) - .createdBy(mockUser) // BoardResponse.from 호출 시 필요 + .createdBy(mockUser) .build(); mockPost = Post.builder() @@ -245,12 +245,15 @@ void getPosts_Success() { // Mocking when(boardRepository.findById(boardId)).thenReturn(Optional.of(mockBoard)); + when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); // userId 조회 추가 when(postRepository.findAllByBoard(mockBoard, pageable)).thenReturn(postPage); + // 좋아요/북마크 Mocking (mapToPostResponse에서 호출됨) + when(postLikeRepository.existsByUserUserIdAndPostPostId(userId, postId)).thenReturn(true); + when(postBookmarkRepository.existsByUserUserIdAndPostPostId(userId, postId)).thenReturn(false); + // when - // 여기서 mapToPostResponse 내부적으로 UserInfoResponse.from(), BoardResponse.from()이 호출됨 - // mockUser에 Role 정보가 없으면 NPE 발생 가능 (setUp에서 처리함) - Page result = postService.getPosts(boardId, page, size); + Page result = postService.getPosts(boardId, userId, page, size); // then assertThat(result.getTotalElements()).isEqualTo(1); @@ -258,6 +261,9 @@ void getPosts_Success() { // DTO 변환 확인 assertThat(result.getContent().get(0).getUser().getName()).isEqualTo(mockUser.getName()); assertThat(result.getContent().get(0).getBoard().getBoardName()).isEqualTo(mockBoard.getBoardName()); + // 좋아요/북마크 상태 확인 + assertThat(result.getContent().get(0).getIsLiked()).isTrue(); + assertThat(result.getContent().get(0).getIsBookmarked()).isFalse(); } @Test @@ -272,7 +278,7 @@ void getPostDetail_Success_WithReplies() { Comment parentComment = Comment.builder() .commentId(UUID.randomUUID()) .post(mockPost) - .user(mockUser) // 댓글 작성자 필요 (DTO 변환 위해) + .user(mockUser) .content("부모댓글1") .parentComment(null) .build(); @@ -280,7 +286,7 @@ void getPostDetail_Success_WithReplies() { Comment childComment = Comment.builder() .commentId(UUID.randomUUID()) .post(mockPost) - .user(mockUser) // 댓글 작성자 필요 + .user(mockUser) .content("대댓글1") .parentComment(parentComment) .build(); @@ -289,20 +295,29 @@ void getPostDetail_Success_WithReplies() { // Mocking when(postRepository.findById(postId)).thenReturn(Optional.of(mockPost)); - // 1. 부모 댓글(parentComment == null) 조회 Mocking + when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); // userId 조회 추가 + + // 댓글 조회 Mocking when(commentRepository.findAllByPostPostIdAndParentCommentIsNull(postId, commentPageable)) .thenReturn(parentCommentPage); // 2. 자식 댓글 조회 Mocking when(commentRepository.findByParentComment(parentComment)).thenReturn(List.of(childComment)); - // 3. 첨부파일 조회 Mocking + + // 첨부파일 조회 Mocking when(postAttachmentRepository.findAllByPostPostId(postId)).thenReturn(Collections.emptyList()); + // 좋아요/북마크 Mocking (공통 빌더에서 호출됨) + when(postLikeRepository.existsByUserUserIdAndPostPostId(userId, postId)).thenReturn(true); + when(postBookmarkRepository.existsByUserUserIdAndPostPostId(userId, postId)).thenReturn(true); + // when - PostResponse result = postService.getPostDetail(postId, page, size); + PostResponse result = postService.getPostDetail(postId, userId, page, size); // then assertThat(result).isNotNull(); assertThat(result.getPostId()).isEqualTo(postId); + assertThat(result.getIsLiked()).isTrue(); + assertThat(result.getIsBookmarked()).isTrue(); // 댓글 검증 Page commentPage = result.getComments();