(16-2) 기능구현 - 수정과 삭제 (답변)

답변을 수정하고 삭제하는 기능을 구현하자

이전포스팅의 기능과 거의 비슷한 방법


시작하기 전에

개요 : 질문과 답변을 할 수 있는 게시판 서비스를 스프링부트를 통해 만들어 본다.

학습사이트 : https://wikidocs.net/book/7601

예제 코드 : https://github.com/pahkey/sbb


답변 수정


답변 수정 버튼

답변 수정 버튼을 추가한다


  • question_detail.html 수정
<!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->

<html layout:decorate="~{layout}">

(... 생략 ...)
<!-- 답변 반복 시작 -->
<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>
        </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>
<!-- 답변 반복 끝  -->
(... 생략 ...)

</html>

수정 버튼을 누르면 /answer/modify/답변ID 형태의 URL이 GET 방식으로 요청된다.


  • SBB테스트

6

수정 버튼이 생겼다!


답변 수정 기능 구현

버튼을 만들었으니 기능을 구현하자.


  • AnswerService.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java
package com.mysite.sbb.answer;

(... 생략 ...)
import java.util.Optional;
import com.mysite.sbb.DataNotFoundException;
(... 생략 ...)

@RequiredArgsConstructor
@Service
public class AnswerService {

    (...)
    
    // 답변 조회 메서드
    // 답변을 수정, 삭제하기 위해 본인의 ID와 같은지 검사가 필요
    public Answer getAnswer(Integer id) {
        Optional<Answer> answer = this.answerRepository.findById(id);
        if (answer.isPresent()) {
            return answer.get();
        } else {
            throw new DataNotFoundException("answer not found");
        }
    }

    // 답변 수정 메서드
    public void modify(Answer answer, String content) {
        answer.setContent(content);
        answer.setModifyDate(LocalDateTime.now());
        this.answerRepository.save(answer);
    }
}

답변을 수정, 삭제하기 위한 getAnswer메서드와, 답변을 수정하는 modify메서드 추가.


  • AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;

(... 생략 ...)
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.server.ResponseStatusException;
(... 생략 ...)

@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {

    (... 생략 ...)
    
    // 답변 수정 URL 컨트롤
    @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";
    }
}

답변 수정시 기존의 내용이 필요하므로 AnswerForm 객체에 조회한 값을 저장하고,

저장한 값을 answer_form.html템플릿에서 수정할수 있게 한다.

아직 템플릿을 만들지 않았으므로 다음으로는 템플릿을 만들어보자.


  • answer_form.html 생성
<!-- 경로 : /sbb/src/main/resources/templates/answer_form.html -->

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container">
    <h5 class="my-3 border-bottom pb-2">답변 수정</h5>
    
    <!-- 답변 전달 (POST) -->
    <form th:object="${answerForm}" method="post">
    
        <!-- CSRF 수동추가 -->
        <input type="hidden" 
        th:name="${_csrf.parameterName}" 
        th:value="${_csrf.token}" />
        
        <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="10"></textarea>
        </div>
        <input type="submit" value="저장하기" class="btn btn-primary my-2">
    </form>
</div>
</html>

수정할 값을 POST방식으로 전달한다.

따라서 Controlloer 수정도 필요하다.


  • AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;

(... 생략 ...)

@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class 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());
    }
}

answer_form의 POST요청을 처리하는 answerModify 메서드 추가


수정 일시 표시

  • question_detail.html 수정
<!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">

   (... 생략 ...)
        
    <!-- 답변 반복 시작 -->
    <div th:each="answer : ${question.answerList}">
            <a th:id="|answer_${answer.id}|"></a>
            <div class="card my-3">
                <div class="card-body">
                    <div class="d-flex justify-content-end">
            
            	<!-- 수정 일시 표시 -->
	            <div th:if="${answer.modifyDate != null}"
	             class="badge bg-light text-dark p-2 text-start mx-3">
	                <div class="mb-2">modified at</div>
	                <div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
	            </div>

                <!-- 글쓴이, 작성시간 표시 -->
                <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>
            </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>
    </div>
    <!-- 답변 반복 끝  -->
    
  (... 생략 ...)

</html>

질문 수정일시를 수정해준 것과 똑같이 해준다.


SBB테스트


  • 수정 템플릿

7


  • 수정 확인

8

잘 수정된다!



답변 삭제


답변 삭제 버튼


  • question_detail.html 수정
<!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->

<html layout:decorate="~{layout}">

(... 생략 ...)
        
    <!-- 답변 반복 시작 -->
    <div th:each="answer : ${question.answerList}">
            <a th:id="|answer_${answer.id}|"></a>
            <div class="card my-3">
                <div class="card-body">
                    <div class="d-flex justify-content-end">
            
            	<!-- 수정 일시 표시 -->
	            <div th:if="${answer.modifyDate != null}"
	             class="badge bg-light text-dark p-2 text-start mx-3">
	                <div class="mb-2">modified at</div>
	                <div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
	            </div>

                <!-- 글쓴이, 작성시간 표시 -->
                <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>
            </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>
	            
	            <!-- 삭제 -->
                <a href="javascript:void(0);" th:data-uri="@{|/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>
            </div>
        </div>
    </div>
    <!-- 답변 반복 끝  -->
    
  (... 생략 ...)

</html>

수정 오른쪽에 삭제 버튼 추가.

질문 삭제와 마찬가지로 버튼 class = delete이므로 자바스크립트 알림창이 띄워진다.


답변 삭제 기능


  • AnswerService.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java
package com.mysite.sbb.answer;

(... 생략 ...)

@RequiredArgsConstructor
@Service
public class AnswerService {

    (... 생략 ...)
    
    // 답변 삭제 메서드
    public void delete(Answer answer) {
        this.answerRepository.delete(answer);
    }
}

입력으로 받은 Answer 객체를 사용하여 답변을 삭제하는 delete 메서드 추가


  • AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;

(... 생략 ...)

@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {

    (... 생략 ...)
    
    // 답변 삭제 (GET)
    @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());
    }

}

답변 삭제시 요청되는 GET방식의 URL을 처리하는 answerDelete 메서드 추가.


SBB테스트


  • 삭제알림

9


  • 삭제 확인

10

잘 삭제된다






Leave a comment