사이먼's 코딩노트
[SpringBoot] 답변 수정 및 삭제 본문
[답변 수정하기]
- 답변을 작성한 다음 해당 글을 수정할 수 있어야한다.
- 수정하는 기능을 추가하기 전, 답변이 언제 수정되었는 지 확인할 수 있도록 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}로 리다이렉트하였다.
- 삭제와 관련된 모든 코드를 추가 작성하고 로컬 서버를 다시 실행하면 답변 삭제 버튼 생성과 함께 답변 삭제가 성공적으로 이루어지는 것을 확인할 수 있다.
반응형
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] 앵커 이동 / 마크다운 적용 (0) | 2024.05.16 |
---|---|
[SpringBoot] 질문/답변 추천 (0) | 2024.05.16 |
[SpringBoot] 질문 수정 및 삭제 (0) | 2024.05.15 |
[SpringBoot] 로그아웃 / 작성자 추가 (0) | 2024.05.14 |
[SpringBoot] 로그인 구현 (0) | 2024.05.13 |