diff --git a/src/main/java/com/gamzabat/algohub/exception/CustomExceptionHandler.java b/src/main/java/com/gamzabat/algohub/exception/CustomExceptionHandler.java index 130f702a..dfe82b27 100644 --- a/src/main/java/com/gamzabat/algohub/exception/CustomExceptionHandler.java +++ b/src/main/java/com/gamzabat/algohub/exception/CustomExceptionHandler.java @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import com.gamzabat.algohub.feature.board.exception.BoardValidationExceoption; +import com.gamzabat.algohub.feature.board.exception.BoardValidationException; import com.gamzabat.algohub.feature.comment.exception.CommentValidationException; import com.gamzabat.algohub.feature.group.ranking.exception.CannotFoundRankingException; import com.gamzabat.algohub.feature.group.studygroup.exception.CannotFoundGroupException; @@ -101,8 +101,8 @@ protected ResponseEntity handler(SolvedAcApiErrorException e) { return ResponseEntity.status(e.getCode()).body(new ErrorResponse(e.getCode(), e.getError(), null)); } - @ExceptionHandler(BoardValidationExceoption.class) - protected ResponseEntity handler(BoardValidationExceoption e) { + @ExceptionHandler(BoardValidationException.class) + protected ResponseEntity handler(BoardValidationException e) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(new ErrorResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), e.getError(), null)); } diff --git a/src/main/java/com/gamzabat/algohub/feature/board/controller/BoardController.java b/src/main/java/com/gamzabat/algohub/feature/board/controller/BoardController.java index 3d4aa3bd..99c90d38 100644 --- a/src/main/java/com/gamzabat/algohub/feature/board/controller/BoardController.java +++ b/src/main/java/com/gamzabat/algohub/feature/board/controller/BoardController.java @@ -4,6 +4,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -66,4 +67,11 @@ public ResponseEntity updateBoard(@AuthedUser User user, @Valid @RequestBo boardService.updateBoard(user, request); return ResponseEntity.ok().build(); } + + @DeleteMapping + @Operation(summary = "공지 삭제 API") + public ResponseEntity deleteBoard(@AuthedUser User user, @RequestParam Long boardId) { + boardService.deleteBoard(user, boardId); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationExceoption.java b/src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationException.java similarity index 55% rename from src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationExceoption.java rename to src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationException.java index c718ea6d..60ca8884 100644 --- a/src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationExceoption.java +++ b/src/main/java/com/gamzabat/algohub/feature/board/exception/BoardValidationException.java @@ -3,10 +3,10 @@ import lombok.Getter; @Getter -public class BoardValidationExceoption extends RuntimeException { +public class BoardValidationException extends RuntimeException { private final String error; - public BoardValidationExceoption(String error) { + public BoardValidationException(String error) { this.error = error; } } diff --git a/src/main/java/com/gamzabat/algohub/feature/board/repository/BoardCommentRepository.java b/src/main/java/com/gamzabat/algohub/feature/board/repository/BoardCommentRepository.java index ea2c5cf6..d51c948b 100644 --- a/src/main/java/com/gamzabat/algohub/feature/board/repository/BoardCommentRepository.java +++ b/src/main/java/com/gamzabat/algohub/feature/board/repository/BoardCommentRepository.java @@ -3,6 +3,8 @@ 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; import com.gamzabat.algohub.feature.board.domain.Board; import com.gamzabat.algohub.feature.board.domain.BoardComment; @@ -10,4 +12,8 @@ public interface BoardCommentRepository extends JpaRepository { List findAllByBoard(Board board); + + @Modifying + @Query("delete from BoardComment c where c.board = :board") + void deleteAllCommentByBoard(Board board); } diff --git a/src/main/java/com/gamzabat/algohub/feature/board/service/BoardCommentService.java b/src/main/java/com/gamzabat/algohub/feature/board/service/BoardCommentService.java index c658a109..df9226e5 100644 --- a/src/main/java/com/gamzabat/algohub/feature/board/service/BoardCommentService.java +++ b/src/main/java/com/gamzabat/algohub/feature/board/service/BoardCommentService.java @@ -11,7 +11,7 @@ import com.gamzabat.algohub.feature.board.domain.Board; import com.gamzabat.algohub.feature.board.domain.BoardComment; import com.gamzabat.algohub.feature.board.dto.CreateBoardCommentRequest; -import com.gamzabat.algohub.feature.board.exception.BoardValidationExceoption; +import com.gamzabat.algohub.feature.board.exception.BoardValidationException; import com.gamzabat.algohub.feature.board.repository.BoardCommentRepository; import com.gamzabat.algohub.feature.board.repository.BoardRepository; import com.gamzabat.algohub.feature.comment.dto.GetCommentResponse; @@ -106,7 +106,7 @@ public void deleteComment(User user, Long commentId) { private Board validateBoard(User user, Long boardId) { Board board = boardRepository.findById(boardId) - .orElseThrow(() -> new BoardValidationExceoption("공지사항이 존재하지 않습니다.")); + .orElseThrow(() -> new BoardValidationException("공지사항이 존재하지 않습니다.")); StudyGroup group = studyGroupRepository.findById(board.getStudyGroup().getId()) .orElseThrow(() -> new StudyGroupValidationException( diff --git a/src/main/java/com/gamzabat/algohub/feature/board/service/BoardService.java b/src/main/java/com/gamzabat/algohub/feature/board/service/BoardService.java index cda84ad4..a6e8da3a 100644 --- a/src/main/java/com/gamzabat/algohub/feature/board/service/BoardService.java +++ b/src/main/java/com/gamzabat/algohub/feature/board/service/BoardService.java @@ -15,7 +15,8 @@ import com.gamzabat.algohub.feature.board.dto.CreateBoardRequest; import com.gamzabat.algohub.feature.board.dto.GetBoardResponse; import com.gamzabat.algohub.feature.board.dto.UpdateBoardRequest; -import com.gamzabat.algohub.feature.board.exception.BoardValidationExceoption; +import com.gamzabat.algohub.feature.board.exception.BoardValidationException; +import com.gamzabat.algohub.feature.board.repository.BoardCommentRepository; import com.gamzabat.algohub.feature.board.repository.BoardRepository; import com.gamzabat.algohub.feature.group.studygroup.domain.GroupMember; import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup; @@ -24,7 +25,6 @@ import com.gamzabat.algohub.feature.group.studygroup.repository.GroupMemberRepository; import com.gamzabat.algohub.feature.group.studygroup.repository.StudyGroupRepository; import com.gamzabat.algohub.feature.user.domain.User; -import com.gamzabat.algohub.feature.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -35,7 +35,7 @@ public class BoardService { private final BoardRepository boardRepository; - private final UserRepository userRepository; + private final BoardCommentRepository boardCommentRepository; private final StudyGroupRepository studyGroupRepository; private final GroupMemberRepository groupMemberRepository; @@ -63,7 +63,7 @@ public void createBoard(@AuthedUser User user, CreateBoardRequest request) { @Transactional(readOnly = true) public GetBoardResponse getBoard(@AuthedUser User user, Long boardId) { Board board = boardRepository.findById(boardId) - .orElseThrow(() -> new BoardValidationExceoption("존재하지 않는 공지입니다")); + .orElseThrow(() -> new BoardValidationException("존재하지 않는 게시글입니다")); if (!groupMemberRepository.existsByUserAndStudyGroup(user, board.getStudyGroup())) throw new StudyGroupValidationException(HttpStatus.FORBIDDEN.value(), "참여하지 않은 스터디 그룹 입니다."); @@ -93,13 +93,32 @@ public List getBoardList(@AuthedUser User user, Long studyGrou @Transactional public void updateBoard(User user, UpdateBoardRequest request) { Board board = boardRepository.findById(request.boardId()) - .orElseThrow(() -> new BoardValidationExceoption("존재하지 않는 게시글입니다")); - StudyGroup studyGroup = studyGroupRepository.findById(board.getStudyGroup().getId()) - .orElseThrow(() -> new StudyGroupValidationException(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 스터디 그룹입니다")); + .orElseThrow(() -> new BoardValidationException("존재하지 않는 게시글입니다")); + validateStudyGroupExists(board); if (!user.getId().equals(board.getAuthor().getId())) throw new UserValidationException("공지를 수정할 수 있는 권한이 없습니다"); board.updateBoard(request.title(), request.content()); } + @Transactional + public void deleteBoard(User user, Long boardId) { + Board board = boardRepository.findById(boardId) + .orElseThrow(() -> new BoardValidationException("존재하지 않는 게시글입니다")); + validateStudyGroupExists(board); + + if (!user.getId().equals(board.getAuthor().getId())) + throw new UserValidationException("공지를 삭제할 수 있는 권한이 없습니다"); + + boardCommentRepository.deleteAllCommentByBoard(board); + boardRepository.delete(board); + + log.info("success to delete board. userId: {}, boardId: {}", user.getId(), boardId); + } + + private void validateStudyGroupExists(Board board) { + studyGroupRepository.findById(board.getStudyGroup().getId()) + .orElseThrow(() -> new StudyGroupValidationException(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 스터디 그룹입니다")); + } + } diff --git a/src/test/java/com/gamzabat/algohub/feature/board/service/BoardCommentServiceTest.java b/src/test/java/com/gamzabat/algohub/feature/board/service/BoardCommentServiceTest.java index 8f0487aa..c1a641f0 100644 --- a/src/test/java/com/gamzabat/algohub/feature/board/service/BoardCommentServiceTest.java +++ b/src/test/java/com/gamzabat/algohub/feature/board/service/BoardCommentServiceTest.java @@ -29,7 +29,7 @@ import com.gamzabat.algohub.feature.board.domain.Board; import com.gamzabat.algohub.feature.board.domain.BoardComment; import com.gamzabat.algohub.feature.board.dto.CreateBoardCommentRequest; -import com.gamzabat.algohub.feature.board.exception.BoardValidationExceoption; +import com.gamzabat.algohub.feature.board.exception.BoardValidationException; import com.gamzabat.algohub.feature.board.repository.BoardCommentRepository; import com.gamzabat.algohub.feature.board.repository.BoardRepository; import com.gamzabat.algohub.feature.comment.domain.Comment; @@ -109,7 +109,7 @@ void createComment_1() { when(studyGroupRepository.findById(30L)).thenReturn(Optional.ofNullable(studyGroup)); when(groupMemberRepository.existsByUserAndStudyGroup(user2, studyGroup)).thenReturn(true); when(commentRepository.save(any(BoardComment.class))).thenReturn(comment); - + // when commentService.createComment(user2, request); // then @@ -132,7 +132,7 @@ void createCommentFailed_1() { when(boardRepository.findById(10L)).thenReturn(Optional.empty()); // when, then assertThatThrownBy(() -> commentService.createComment(user, request)) - .isInstanceOf(BoardValidationExceoption.class) + .isInstanceOf(BoardValidationException.class) .hasFieldOrPropertyWithValue("error", "공지사항이 존재하지 않습니다."); verify(notificationService, never()).send(any(), any(), any(), any()); } @@ -225,7 +225,7 @@ void getCommentListFailed_1() { when(boardRepository.findById(10L)).thenReturn(Optional.empty()); // when, then assertThatThrownBy(() -> commentService.getCommentList(user, 10L)) - .isInstanceOf(BoardValidationExceoption.class) + .isInstanceOf(BoardValidationException.class) .hasFieldOrPropertyWithValue("error", "공지사항이 존재하지 않습니다."); } @@ -300,7 +300,7 @@ void deleteCommentFailed_3() { when(commentRepository.findById(40L)).thenReturn(Optional.ofNullable(comment)); // when, then assertThatThrownBy(() -> commentService.deleteComment(user, 40L)) - .isInstanceOf(BoardValidationExceoption.class) + .isInstanceOf(BoardValidationException.class) .hasFieldOrPropertyWithValue("error", "공지사항이 존재하지 않습니다."); } diff --git a/src/test/java/com/gamzabat/algohub/service/BoardServiceTest.java b/src/test/java/com/gamzabat/algohub/service/BoardServiceTest.java index f8ea2af3..53ee8ba0 100644 --- a/src/test/java/com/gamzabat/algohub/service/BoardServiceTest.java +++ b/src/test/java/com/gamzabat/algohub/service/BoardServiceTest.java @@ -29,7 +29,8 @@ import com.gamzabat.algohub.feature.board.dto.CreateBoardRequest; import com.gamzabat.algohub.feature.board.dto.GetBoardResponse; import com.gamzabat.algohub.feature.board.dto.UpdateBoardRequest; -import com.gamzabat.algohub.feature.board.exception.BoardValidationExceoption; +import com.gamzabat.algohub.feature.board.exception.BoardValidationException; +import com.gamzabat.algohub.feature.board.repository.BoardCommentRepository; import com.gamzabat.algohub.feature.board.repository.BoardRepository; import com.gamzabat.algohub.feature.board.service.BoardService; import com.gamzabat.algohub.feature.group.studygroup.domain.GroupMember; @@ -49,6 +50,8 @@ public class BoardServiceTest { private BoardRepository boardRepository; @Mock GroupMemberRepository groupMemberRepository; + @Mock + private BoardCommentRepository boardCommentRepository; @Captor private ArgumentCaptor boardCaptor; @@ -187,8 +190,8 @@ void getBoardFailed_1() { //when, then assertThatThrownBy(() -> boardService.getBoard(user, 1001L)) - .isInstanceOf(BoardValidationExceoption.class) - .hasFieldOrPropertyWithValue("error", "존재하지 않는 공지입니다"); + .isInstanceOf(BoardValidationException.class) + .hasFieldOrPropertyWithValue("error", "존재하지 않는 게시글입니다"); } @@ -288,7 +291,7 @@ void updateBoardFailed_1() { when(boardRepository.findById(1001L)).thenReturn(Optional.empty()); //when,then assertThatThrownBy(() -> boardService.updateBoard(user, updateBoardRequest)) - .isInstanceOf(BoardValidationExceoption.class) + .isInstanceOf(BoardValidationException.class) .hasFieldOrPropertyWithValue("error", "존재하지 않는 게시글입니다"); } @@ -320,4 +323,53 @@ void updateBoardFailed_3() { .hasFieldOrPropertyWithValue("errors", "공지를 수정할 수 있는 권한이 없습니다"); } + @Test + @DisplayName("공지 삭제 성공") + void deleteBoardSuccess() { + //given + when(boardRepository.findById(1000L)).thenReturn(Optional.ofNullable(board)); + when(studyGroupRepository.findById(30L)).thenReturn(Optional.ofNullable(studyGroup)); + doNothing().when(boardCommentRepository).deleteAllCommentByBoard(board); + //when + boardService.deleteBoard(user, 1000L); + //then + verify(boardRepository, times(1)).delete(board); + } + + @Test + @DisplayName("공지 삭제 실패(존재하지 않는 게시글)") + void deleteBoardFailed_1() { + //given + when(boardRepository.findById(1001L)).thenReturn(Optional.empty()); + //when,then + assertThatThrownBy(() -> boardService.deleteBoard(user, 1001L)) + .isInstanceOf(BoardValidationException.class) + .hasFieldOrPropertyWithValue("error", "존재하지 않는 게시글입니다"); + } + + @Test + @DisplayName("공지 삭제 실패(존재하지 않는 스터디 그룹)") + void deleteBoardFailed_2() { + //given + when(boardRepository.findById(1000L)).thenReturn(Optional.ofNullable(board)); + when(studyGroupRepository.findById(30L)).thenReturn(Optional.empty()); + //when,then + assertThatThrownBy(() -> boardService.deleteBoard(user, 1000L)) + .isInstanceOf(StudyGroupValidationException.class) + .hasFieldOrPropertyWithValue("code", HttpStatus.BAD_REQUEST.value()) + .hasFieldOrPropertyWithValue("error", "존재하지 않는 스터디 그룹입니다"); + } + + @Test + @DisplayName("공지 삭제 실패(게시글 작성자가 아님)") + void deleteBoardFailed_3() { + //given + when(boardRepository.findById(1000L)).thenReturn(Optional.ofNullable(board)); + when(studyGroupRepository.findById(30L)).thenReturn(Optional.ofNullable(studyGroup)); + //when, then + assertThatThrownBy(() -> boardService.deleteBoard(user4, 1000L)) + .isInstanceOf(UserValidationException.class) + .hasFieldOrPropertyWithValue("errors", "공지를 삭제할 수 있는 권한이 없습니다"); + } + }