이번에는 작성한 질문과 답변을 수정하고 삭제하는 기능을 만들어보겠습니다.
시작
질문과 답변을 수정했을때 수정날짜를 표시할 수 있도록 Question 과 Answer 엔티티에 modifyDate 속성을 추가합니다.
package com.crud.model;
...
@Entity
@Getter
@Setter
public class Question {
...
private LocalDateTime modifyDate;
}
package com.crud.model;
...
@Entity
@Getter
@Setter
public class Answer {
...
private LocalDateTime modifyDate;
}
Question 수정
question 이 수정되는 과정을 정리하면 다음과 같습니다.
수정 버튼 클릭 -> GET 방식 처리 -> question_form 데이터 수정 -> POST 방식으로 처리 후 저장
question_detail
다음과 같이 question_detail 페이지에서 질문을 수정할 수 있는 버튼을 만듭니다.
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<h1 class="border-bottom py-2" th:text = "${question.subject}"></h1>
<div clas="card my-3">
<div class="card-body">
<div class="card-text" style="white-space : pre-line;" th:text="${question.content}"></div>
<div class="d-flex justify-content-end">
<div class="mb-2">
<span th:if="${question.author != null}" th:text="${question.author.username}"></span>
</div>
<div class="badge bg-light text-dark p-2 text-start">
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH')}"></div>
</div>
</div>
</div>
</div>
<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>
...
</html>
#authentication.getPrincipal().getUsername 과 question 의 author 을 비교해서 버튼을 생성합니다.
#authentication.getPrincipal() 은 Principal 객체를 리턴하는 타임리프 유틸리티
QuestionService
질문 데이터를 수정할 수 있도록 questionService 에 modify 메서드를 생성합니다.
package com.crud.service;
...
@Service
public class QuestionService {
@Autowired
private QuestionRepository questionRepository;
...
public void modify(Question question, String subject, String content) {
question.setSubject(subject);
question.setContent(content);
question.setModifyDate(LocalDateTime.now());
questionRepository.save(question);
}
}
question 객체와 새로운 subject 그리고 content 를 입력받아서
입력받은 question 객체에 새로운 subject 와 content 를 현재 시간 LocalDateTime.now() 와 함께 저장합니다.
QuestionController
question_detail 페이지에서 GET 방식으로 localhost:8080/question/modify/${question.id} 로 요청을 보냈을때
처리하는 메서드를 아래와 같이 생성합니다.
package com.crud.controller;
...
@Controller
public class QuestionController {
@Autowired
private QuestionService questionService;
@Autowired
private UserService userService;
...
@PreAuthorize("isAuthenticated()")
@GetMapping("/question/modify/{id}")
public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
Question question = 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";
}
}
Principal 객체를 통해 현재 로그인한 사용자와 해당 id 의 question 의 작성자를 비교해서
동일하지 않을 경우 "수정권한이 없습니다" 라는 오류를 발생하도록 하였습니다.
사용자가 동일할 경우 기존에 작성된 question 객체를 question_form 전달하고 이동합니다.
다음으로 POST 방식으로 localhost:8080/question/modify/${question.id} 요청이 왔을때 처리하는 메서드를 작성합니다.
package com.crud.controller;
...
@Controller
public class QuestionController {
@Autowired
private QuestionService questionService;
@Autowired
private UserService userService;
...
@PreAuthorize("isAuthenticated()")
@PostMapping("/question/modify/{id}")
public String questionModify(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal, @PathVariable("id") Integer id ) {
if(bindingResult.hasErrors()) {
return "question_form";
}
Question question = questionService.getQuestion(id);
if(!question.getAuthor().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
return String.format("redirect:/question/detail/%s", id);
}
}
question_form 을 통해 전달받은 데이터를 검증해서 이상이 없을 경우, id 를 통해 question 를 가져옵니다.
그리고 현재 사용자와 question 의 author 을 비교하고 동일할 경우
위에서 만든 QuestionService 의 modify 메서드를 통해 저장을 합니다.
question_form
다음으로 question_form 의 form 태그 부분을 다음과 같이 수정합니다.
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form th:object=${questionForm} method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
<div th:each="err : ${#fields.allErrors()}" th:text="${err}"/>
</div>
...
</html>
form 태그의 th:action 속성을 삭제하고 CSRF 값 설정을 위한 input 엘리먼트를 hidden 으로 생성했습니다.
form 태그의 action 속성없이 전송하게 되면 폼은 현재 URL 을 기준으로 전송이 됩니다.
질문을 수정할때 URL 은 localhost:8080/question/modify/{id} 형태이기 때문에
POST 방식으로 해당 URL 에 요청을 하게 됩니다.
결과
다음과 같이 로그인을 하고 작성한 게시글을 클릭하면 수정 버튼이 보입니다.
수정버튼을 클릭하면 아래와 같이 question_form 페이지로 이동해서 글을 수정할 수 있습니다,
이때 현재 URL 인 localhost:8080/question/modify/105 로 POST 방식으로 요청을 보내서 글을 수정하게 됩니다.
Question 삭제
다음으로 삭제 기능을 만들어보겠습니다.
question_detail
detail 페이지에 삭제 버튼을 아래와 같이 생성합니다.
<html layout:decorate="~{layout}">
...
<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>
<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>
</div>
<h5 class="border-bottom my-3 py-2" th:text="|${#lists.size(question.answerList)} 개의 답변이 있습니다.|"></h5>
...
</html>
삭제 버튼을 클릭했을때 자바스크립트의 경고창 alert 를 통해 재확인하는 절차를 넣기 위해
href 의 값을 javascript:void(0) 로 설정하고 th:data-uri 속성을 추가했습니다.
타임리프의 data-uri 속성은 자바스크립트에서 this.dataset.uri 로 사용해서 그 값을 얻을 수 있습니다.
question_detail 페이지의 하단에 다음과 같이 script 를 작성합니다.
<html layout:decorate="~{layout}">
...
<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>
</html>
layout.html 을 상속하는 템플릿에서 위치에 상관없이 자바스크립트를 사용할 수 있도록 다음 구문을 생성합니다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Spring Boot Board</title>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous">
</script>
</head>
<body>
...
<th:block layout:fragment="content"></th:block>
<th:block layout:fragment="script"></th:block>
</body>
</html>
QuestionService
글을 삭제하는 QuestionService 의 delete 메서드를 아래와 같이 작성합니다.
package com.crud.service;
...
@Service
public class QuestionService {
@Autowired
private QuestionRepository questionRepository;
...
public void delete(Question question) {
questionRepository.delete(question);
}
}
QuestionController
QuestionController 에서 localhost:8080/question/delete/{id} 로 받은 요청을 처리하는 questionDelete 를 작성합니다.
package com.crud.controller;
...
@Controller
public class QuestionController {
@Autowired
private QuestionService questionService;
@Autowired
private UserService userService;
...
@PreAuthorize("isAuthenticated()")
@GetMapping("/question/delete/{id}")
public String questionDelete(Principal principal, @PathVariable("id") Integer id) {
Question question = questionService.getQuestion(id);
if(!question.getAuthor().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
}
questionService.delete(question);
return "redirect:/";
}
}
quesitonModify 메서드에서 사용한것처럼 Principal 객체를 통해 question 작성자와 비교를 합니다.
현재 사용자와 작성자가 동일할 경우 삭제를 questionService 의 delete 메서드를 통해 삭제합니다.
결과
삭제 버튼을 클릭하면 해당 글이 삭제되는것을 볼 수 있습니다.
'Spring > Springboot 실습' 카테고리의 다른 글
게시판 만들기 - 글 수정 및 삭제(2) (0) | 2023.02.02 |
---|---|
게시판 만들기 - 글쓴이 표시(2) (0) | 2023.01.26 |
게시판 만들기 - 글쓴이 표시(1) (0) | 2023.01.26 |
게시판 만들기 - 로그아웃 (0) | 2023.01.25 |
게시판 만들기 - 로그인 (0) | 2023.01.25 |