본문 바로가기

Spring

[Spring]기능 - 질문 수정 및 삭제

수정

글을 만들고 조회할 수 있다면 수정과 삭제를 할 수 있어야 한다.

 

그러기 위해서는 수정시간을 엔티티에 넣어주고 시작하자.

private LocalDateTime modifyDate;

 

 

1. 버튼 생성

일단 우리는 수정을 하기 위해 누를 버튼을 먼저 생성한다.

<div class="my-3">
    <a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
        sec:authorize="isAuthenticated()"
        th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
        th:text="수정"></a>
</div>

이는 로그인이 필요한 부분이고, 로그인된 사용자가 작성자와 같을 경우에 사용할 수 출력된다.

 

그러면 수정을 할 수 있도록 기능을 만들어 보자.

 

2. QuestionController 수정

버튼에서 href로 연결해준 링크를 Mapping 해줘야 한다.

 

우리는 Post로 받는게 아닌 Get으로 받았는데, 수정을 클릭할 경우 바로 이동될 때를 생각해 GetMapping부터 만들었다.

물론 로그인이 필요한 부분이고, Mapping된 id를 사용하여 question의 id를 찾고, 수정할 수 있도록 한다.

@PreAuthorize("isAuthenticated()")
@GetMapping("/modify/{id}")
public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
    Question question = this.questionService.getQuestion(id);
    if(!question.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }
    questionForm.setSubject(question.getSubject());
    questionForm.setContent(question.getContent());
    return "question_form";
}

저기에 if문을 살펴보면 question에 연결되어 있는 Author 중 Username은 로그인된 사용자를 의미하고, principal의 getName은 작성자를 의미한다. 만약 두 개가 같지 않을 경우 수정 권한이 없음을 출력한다.

 

3. question_form.html 수정

question을 수정하기 위해 <form>이 필요한데 수정과 등록을 같은 <form>으로 진행할 수 있도록 해보자.

방법은 간단한데, th:action 태그를 지워주면 된다.(안지우고, =”””” 를 지워줘도 된다.)

<form th:object="${questionForm}" method="post">
	  <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

하지만, th:action 태그를 지우게 되면 전에 배운 CSRF토큰이 자동으로 생성되지 않는다. CSRF토큰이 없으면 폼을 저장할 수 없기에 CSRF 값을 설정하기 위한 input 엘리먼트를 수동으로 추가한다.

 

th:action 태그가 없어도 동작할 수 있는 이유는, submit 할 때 Mapping된 URL의 기준으로 이동한다.

생성할 때는 question/create로, 수정할때는 question/modify/{question.id}를 실행한다.

 

4. QuestionService 수정

자, 우리는 URL Mapping까지 완료 했으니 기능을 만들어보자.

기능은 역시 Service단에서 실행되는데, 단지 question의 수정된 내용들만 set해주면 된다.

public void modify(Question question, String subject, String content) {
    question.setSubject(subject);
    question.setContent(content);
    question.setModifyDate(LocalDateTime.now());
    this.questionRepository.save(question);
}

 

5. QuestionController 수정

마지막으로 form 엘리먼트에서 받은 값을 POST형식으로 받았기 때문에 PostMapping을 사용하여 만들어둔 기능을 사용해보자.

@PreAuthorize("isAuthenticated()")
@PostMapping("/modify/{id}")
public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult, 
        Principal principal, @PathVariable("id") Integer id) {
    if (bindingResult.hasErrors()) {
        return "question_form";
    }
    Question question = this.questionService.getQuestion(id);
    if (!question.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
    }
    this.questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
    return String.format("redirect:/question/detail/%s", id);
}

폼의 값들이 잘못되었을 경우 에러를 출력하고, 다시 입력할 수 있도록한다.

Question의 id를 사용해 Question을 가져오고, 그 question의 작성자와 로그인 되어있는 사용자가 같지 않을 경우에도 수정 권한이 없음을 알려준다.

만약 이러한 오류가 없을 경우에는 Service단에서 만들어 둔 modify 메서드를 사용해 수정해준다.

  • 수정 전

  • 수정 후

 

삭제

질문을 수정할 수 있도록 만들었으니, 삭제하는 방법도 알아보자.

 

질문을 삭제하는 방법은 조금 더 쉽다.

 

1. 버튼 생성

일단 삭제할 수 있는 버튼을 만들어보자.

<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}"
    class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
    th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
    th:text="삭제"></a>

여기서 href의 속성 값을 "javascript:void(0);" 로 두었는데, “#” 과 같은 의미아무리 클릭해도 어떠한 링크로도 움직이지 않겠다는 것이다.

th:data-uri="@{|/question/delete/${question.id}|}" 은 삭제를 실행할 때 URL을 얻기 위한 속성이고, 삭제 버튼이 클릭될 경우 <Script>이벤트를 확인할 수 있도록 class에 delete를 넣어주었다.

  • Script 이벤트 코드
<script type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
    element.addEventListener('click', function() {
        if(confirm("정말로 삭제하시겠습니까?")) {
            location.href = this.dataset.uri;
        };
    });
});
</script>

이는 알람이 출력될 때 “예”를 선택한다면 th:data-uri 속성의 값이 호출된다.


Script를 통하지 않고 바로 확인을 할 수 있도록 코드를 짜 보았다.

<a onclick="if( !confirm('정말로 삭제하시겠습니까?') ) return false;" th:href="@{|/question/delete/${question.id}|}"
		class="btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
		th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
		th:text="삭제"></a>

이 코드를 사용한다면 script를 사용하지 않아도 된다.


2. layout.html 수정

이제 template에서 삭제할 때 공통으로 알림 메시지를 띄우게 하기 위해서 상속하고 있는 template인 layout.html을 수정해주어야 한다.

script를 사용하기 위해 <body> 가장 아래에 작성한다.

<th:block layout:fragment="script"></th:block>

그리고 question_detail.html 파일에서 <body>가장 아래쪽에 확인할 수 있는 script 코드를 작성한다.

템플릿 상속을 위해 block을 구현할 수 있도록 한다.

<script layout:fragment="script" type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
    element.addEventListener('click', function() {
        if(confirm("정말로 삭제하시겠습니까?")) {
            location.href = this.dataset.uri;
        };
    });
});
</script>

 

3. QuestionService 수정

삭제 기능을 위해 Service단을 수정한다.

public void delete(Question question) {
    this.questionRepository.delete(question);
}

 

4. QuestionController 수정

Script를 통해서 data-uri 속성 값으로 받은 URL을 매핑하기 위해 Controller에서 메서드를 생성한다.

@PreAuthorize("isAuthenticated()")
@GetMapping("/delete/{id}")
public String questionDelete(Principal principal, @PathVariable("id") Integer id) {
    Question question = this.questionService.getQuestion(id);
    if (!question.getAuthor().getUsername().equals(principal.getName())) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
    }
    this.questionService.delete(question);
    return "redirect:/";
}

id를 통해 Question 객체를 가져오고, 만약 작성자와 글쓴이가 같을 경우에만 삭제할 수 있도록 한다.

  • 결과 화면

'Spring' 카테고리의 다른 글

[Spring]기능 - 앵커(anchor)  (0) 2022.08.23
[Spring]기능 - 추천 기능  (0) 2022.08.23
[Spring]기능 - 작성자 출력하기  (0) 2022.08.22
[Spring]기능 - 회원가입  (0) 2022.08.18
CSRF(Cross Site Request Forgery)  (0) 2022.08.18