사이먼's 코딩노트

[SpringBoot] 답변 수정 및 삭제 본문

Java/SpringBoot

[SpringBoot] 답변 수정 및 삭제

simonpark817 2024. 5. 15. 18:22

[답변 수정하기]

  • 답변을 작성한 다음 해당 글을 수정할 수 있어야한다.
  • 수정하는 기능을 추가하기 전, 답변이 언제 수정되었는 지 확인할 수 있도록 Answer.java 엔티티에 수정일시를 의미하는 modifyDate 속성을 하나 추가해봅시다.
  • 아래는 Answer.java 클래스에서 추가된 코드이다.
private LocalDateTime modifyDate;

 

[question_detail.html 템플릿 수정]

  • 사용자가 질문 상세 화면에서 답변 목록이 표시되는 부분에 '수정' 버튼을 클릭하면 답변을 수정할 수 있는 화면으로 진입할 수 있도록 아래와 같이 question_detail.html 템플릿에 버튼을 추가해봅시다.
<!-- 답변 반복 시작 -->
<div class="card my-3" th:each="answer : ${question.answerList}">
    <div class="card-body">
        <div class="card-text" style="white-space: pre-line;" th:text="${answer.content}"></div>
        <div class="d-flex justify-content-end">
            <div class="badge bg-light text-dark p-2 text-start">
                <div class="mb-2">
                    <span th:if="${answer.author != null}" th:text="'작성자 : ' + ${answer.author.username}"></span>
                </div>
                <div th:text="'최초 작성일 : ' + ${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
                <div style="margin-top: 7px;" th:if="${answer.modifyDate != null}" th:text="'수정일시 : ' + ${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
            </div>
        </div>
        <div class="my-3">
            <a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
               sec:authorize="isAuthenticated()"
               th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
               th:text="수정"></a>
        </div>
    </div>
</div>
<!-- 답변 반복 끝  -->
  • '수정' 버튼이 로그인한 사용자와 작성자가 동일한 경우에만 노출되도록 #authentication.getPrincipal().getUsername() == answer.author.username을 적용하였다.
  • #authentication.getPrincipal()은 타임리프에서 스프링 시큐리티와 함께 사용하는 표현식으로, 이를 통해 현재 사용자가 인증되었다면 사용자 이름을 알 수 있다.
  • 만약 로그인한 사용자와 작성자가 다르다면 '수정' 버튼은 보이지 않게된다.
  • '수정' 버튼을 클릭하게 되면 /answer/modify/{id} 형태의 URL이 GET 방식으로 요청될 것이다.

 

[Controller 수정]

  • 위에서 작성된 '수정' 버튼에 GET 방식인 @{/answer/modify/${answer.id}} 링크가 추가되었으므로 해당 링크가 동작할 수 있도록 URL 매핑을 컨트롤러에서 해줘야한다.
  • 또한, 다시 AnswerController로 돌아와 답변을 수정하는 화면에서 내용을 수정하고 '저장하기' 버튼을 누르면 호출되는 POST 요청을 처리하기 위해서도 메서드를 추가해야 한다.
@PreAuthorize("isAuthenticated()")
@PostMapping("/modify/{id}")
public String answerModify(@Valid AnswerForm answerForm, BindingResult bindingResult,
                           @PathVariable("id") Integer id, Principal principal) {
    if (bindingResult.hasErrors()) {
        return "answer_form";
    }

    Answer answer = this.answerService.getAnswer(id);

    if (!answer.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }

    this.answerService.modify(answer, answerForm.getContent());

    return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());
}

@PreAuthorize("isAuthenticated()")
@GetMapping("/modify/{id}")
public String answerModify(AnswerForm answerForm, @PathVariable("id") Integer id, Principal principal) {
    Answer answer = this.answerService.getAnswer(id);

    if (!answer.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }

    answerForm.setContent(answer.getContent());

    return "answer_form";
}
  • answerModify() 메서드를 오버로딩을 생성하고, 만약 현재 로그인한 사용자와 작성자가 동일하지 않을 경우에는 '수정권한이 없습니다.' 라는 에러가 발생하도록 하였다.
  • @GetMapping 에서는 수정할 답변의 내용을 화면에 보여주기 위해 AnswerForm 객체에 id값으로 조회한 답변의 내용의 값을 저장하여 템플릿으로 전달한다.
  • 위 과정이 없다면 답변 수정 화면에 내용의 값이 채워지지 않고 비워져 보일 것이다.
  • 여기서 주의할 점은 답변을 수정할 수 있는 템플릿인 answer_form.html은 아직 프로그램 상에 없기 때문에 새로 생성해줘야 한다.
  • @PostMapping 에서는 AnswerForm의 데이터를 검증하고 로그인한 사용자와 수정하려는 질문의 작성자가 동일한지 검증한다.
  • 검증이 통과되면 AnswerSerivce에 작성된 modify() 메서드를 호출하여 답변 데이터를 수정하고 질문 상세 페이지로 리다이렉트한다.

 

[Service 수정]

  • 수정된 답변이 서비스를 처리될 수 있도록 AnswerService.java 클래스에서 아래와 같이 modify() 메서드를 추가해봅시다.
public void modify(Answer answer, String content) {
    answer.setContent(content);
    answer.setModifyDate(LocalDateTime.now());

    this.answerRepository.save(answer);
}

 

[answer_form.html 생성]

  • 답변을 수정하기 위한 템플릿인 answer_form.html를 생성하여 코드를 추가해봅시다.
<html layout:decorate="~{layout}">
    <div layout:fragment="content" class="container">
        <h5 class="my-3 border-bottom pb-2">답변 수정</h5>
        <form th:action th:object="${answerForm}" method="post">
            <div th:replace="~{form_errors :: formErrorsFragment}"></div>
            <div class="mb-3">
                <label for="content" class="form-label">내용</label>
                <textarea th:field="*{content}" class="form-control" rows="7"></textarea>
            </div>
            <input type="submit" value="저장하기" class="btn btn-primary my-2">
        </form>
    </div>
</html>
  • 답변 작성 시 사용하는 <form> 태그에도 역시 th:action을 사용하지 않았기 때문에 상황에 맞게 현재 호출된 URL로 폼이 전송된다.
  • 위의 답변 수정과 관련된 코드 수정을 마쳤다면, 로컬 서버를 다시 실행했을 때 답변 수정 버튼 생성과 함께 '수정' 버튼 클릭 시 answer_form.html 템플릿을 통한 내용 작성이 가능한 것을 확인할 수 있다.

답변 수정하기 추가

   

답변 수정 페이지

 

[답변 삭제하기]

  • 답변을 삭제하는 기능을 추가하기 위해 답변 수정과 마찬가지로 질문 상세 페이지인 question_detail.html에 버튼을 추가하여 삭제할 수 있도록 하려한다.
  • 버튼을 추가하기 전, 답변 삭제 기능을 수행하는 delete() 메서드를 AnswerService.java 클래스에 아래와 같이 추가해봅시다.
public void delete(Answer answer) {
    this.answerRepository.delete(answer);
}

 

[question_detail.html 템플릿 수정]

  • 아래 코드는 question_detail.html 템플릿에서 답변 수정하기를 통해 생성한 '수정' 버튼바로 아래에 추가된 '삭제' 버튼이다.
<div class="my-3">
    <a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
       sec:authorize="isAuthenticated()"
       th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
       th:text="수정"></a>

    <a onclick="return confirm('정말로 삭제하시겠습니까?')" th:href="@{|/answer/delete/${answer.id}|}"
       class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
       th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
       th:text="삭제"></a>
</div>
  • '삭제' 버튼이 로그인한 사용자와 작성자가 동일한 경우에만 노출되도록 #authentication.getPrincipal().getUsername() == answer.author.username을 적용하였다.
  • 만약 로그인한 사용자와 작성자가 다르다면 '삭제' 버튼은 보이지 않게된다.
  • '삭제' 버튼을 클릭하게 되면 GET 방식의 /answer/delete/{id} URL이 요청된다.
  • 그리고 onclick="return confirm('정말로 삭제하시겠습니까?') 라는 속성을 통해 삭제를 하기 전 alert 알림으로 한 번 더 확인하는 알림창이 나타나게 된다.
  • 자바 스크립트를 통해서도 구현이 가능하지만, 최대한 복잡해지지 않도록 onclick 속성만을 통해 해당 기능을 추가하였다.

 

[Controller 수정]

  • 마지막으로 '삭제' 버튼을 클릭했을 때 /answer/delete/{id} URL을 처리할 수 있도록 AnswerController.java 클래스에 아래와 같이 코드를 추가해봅시다.
@PreAuthorize("isAuthenticated()")
@GetMapping("/delete/{id}")
public String answerDelete(Principal principal, @PathVariable("id") Integer id) {
    Answer answer = this.answerService.getAnswer(id);

    if (!answer.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
    }

    this.answerService.delete(answer);

    return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());
}
  • 사용자가 '삭제' 버튼을 클릭했다면 URL로 전달받은 id값을 사용하여 Answer 데이터를 조회한 후, 로그인한 사용자와 답변 작성자가 동일할 경우 앞서 작성한 서비스를 이용하여 답변을 삭제하도록 하였다.
  • 답변을 삭제한 후에는 질문 상세 페이지인 /question/detail/{id}로 리다이렉트하였다.
  • 삭제와 관련된 모든 코드를 추가 작성하고 로컬 서버를 다시 실행하면 답변 삭제 버튼 생성과 함께 답변 삭제가 성공적으로 이루어지는 것을 확인할 수 있다.

답변 삭제하기 추가

  

반응형