Project/project-board

[Project] 댓글 기능 컨트롤러와 서비스코드 작성

수수한개발자 2022. 8. 3.
728x90

저번 글에서 해시태그 검색 기능까지 구현하였다.

일반적인 게시판이면 게시글들을 쓰면 거기에 댓글을 달 수가 있다. 구현해보도록 하겠습니다.

 

ArticleCommentController

RequiredArgsConstructor
@RequestMapping("/comments")
@Controller
public class ArticleCommentController {

    private final ArticleCommentService articleCommentService;

    @PostMapping("/new")
    public String postNewArticleComment(ArticleCommentRequest articleCommentRequest) {

        articleCommentService.saveArticleComment(articleCommentRequest.toDto(UserAccountDto.of(
                "jisu", "1234", "jisu@email.com", null, null
        )));
        return "redirect:/articles/" + articleCommentRequest.getArticleId();
    }

    @PostMapping("/{commentId}/delete")
    public String deleteArticleComment(@PathVariable long commentId, long articleId) {
        articleCommentService.deleteArticleComment(commentId);
        return "redirect:/articles/" + articleId;
    }
}

@PostMappint("/new") 는 새로운 댓글을 작성하는 메소드입니다.

현재 인증기능이 구현되지 않았으므로 하드코딩으로 UsersAccount 엔티티의 값을 넣어줍니다.

그 후의 articleCommentRequest에서 게시글 id를 가져와 다시 리턴해줍니다.

 

@PostMapping("/{commentId}/delete")

댓글을 삭제하는 메소드 url 입니다. 

 

ArticleCommentDto

public static ArticleCommentDto of(Long articleId, UserAccountDto userAccountDto, String content) {
    return new ArticleCommentDto(null, articleId, userAccountDto, content, null, null, null, null);
}
    
public ArticleComment toEntity(Article article, UserAccount userAccount) {
    return ArticleComment.of(
            article,
            userAccount,
            content
    );
}

 

두개의 메소드를 추가해줍니다. 

of는 정적팩토리 메소드로 ArticleCommentDto객체를 리턴해줍니다.

 

toEntity 메소드는 댓글엔티티 == ArticleComment 를 repository로 넘길때 엔티티를 리턴해주는 역할을 합니다.

 

ArticleCommentRequest

@Data
public class ArticleCommentRequest {
    private final Long articleId;
    private final String content;

    public static ArticleCommentRequest of(Long articleId, String content) {
        return new ArticleCommentRequest(articleId, content);
    }

    public ArticleCommentDto toDto(UserAccountDto userAccountDto) {
        return ArticleCommentDto.of(
                articleId,
                userAccountDto,
                content
        );
    }
}

 

Request객체입니다. 클라이언트에서 넘겨올때 받을 객체입니다. 정적메서드 of는 게시글의 id인 articleId와 댓글 내용 content만 받아서 리턴하고 있습니다. 

toDto는 뷰에서 값을 받아서 dto로 변환해주는 로직입니다.

 

ArticleCommentService

 public void saveArticleComment(ArticleCommentDto dto) {
    try {
        Article article = articleRepository.getReferenceById(dto.getArticleId());
        UserAccount userAccount = userAccountRepository.getReferenceById(dto.getUserAccountDto().getUserId());
        articleCommentRepository.save(dto.toEntity(article, userAccount));
    } catch (EntityNotFoundException e) {
        log.warn("댓글 저장 실패. 댓글 작성에 필요한 정보를 찾을 수 없습니다. - {}", e.getLocalizedMessage());
    }
}

 

컨트롤러에서 호출할 서비스메소드 입니다.

articleCommentRequest DTO에서 toDto를 넘겨 받아 repository에서 getReferenceByid로 article을 가져옵니다. 그후 UserAccount도 같은 방식으로 가져온후 댓글을 세이브 합니다.

여기서 getReferenceById에 대해서는 여기에 정리해둔 글이 있습니다.

 

ArticleCommentControllerTest

@DisplayName("View 컨트롤러 - 댓글")
@Import({SecurityConfig.class, FormDataEncoder.class})
@WebMvcTest(ArticleCommentController.class)
class ArticleCommentControllerTest {

    private final MockMvc mvc;
    private final FormDataEncoder formDataEncoder;

    @MockBean private ArticleCommentService articleCommentService;

    public ArticleCommentControllerTest(@Autowired MockMvc mvc, @Autowired FormDataEncoder formDataEncoder) {
        this.mvc = mvc;
        this.formDataEncoder = formDataEncoder;
    }


    @DisplayName("[view][POST] 댓글 등록 - 정상 호출")
    @Test
    void givenArticleCommentInfo_whenRequesting_thenSavesNewArticleComment() throws Exception {
        // Given
        long articleId = 1L;
        ArticleCommentRequest request = ArticleCommentRequest.of(articleId, "test comment");
        willDoNothing().given(articleCommentService).saveArticleComment(any(ArticleCommentDto.class));

        // When & Then
        mvc.perform(
                        post("/comments/new")
                                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                                .content(formDataEncoder.encode(request))
                                .with(csrf())
                )
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/articles/" + articleId))
                .andExpect(redirectedUrl("/articles/" + articleId));
        then(articleCommentService).should().saveArticleComment(any(ArticleCommentDto.class));
    }

    @DisplayName("[view][GET] 댓글 삭제 - 정상 호출")
    @Test
    void givenArticleCommentIdToDelete_whenRequesting_thenDeletesArticleComment() throws Exception {
        // Given
        long articleId = 1L;
        long articleCommentId = 1L;
        willDoNothing().given(articleCommentService).deleteArticleComment(articleCommentId);

        // When & Then
        mvc.perform(
                        post("/comments/" + articleCommentId + "/delete")
                                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                                .content(formDataEncoder.encode(Map.of("articleId", articleId)))
                                .with(csrf())
                )
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/articles/" + articleId))
                .andExpect(redirectedUrl("/articles/" + articleId));
        then(articleCommentService).should().deleteArticleComment(articleCommentId);
    }
}

 

 

 

728x90

댓글