✏️ 수정 기능 구현
- 권한이 있는 클라이언트에 한에 수정기능이 정상 작동됨
📍 Web 계층 - 수정 버튼 추가
- 권한이 있는 클라이언트만 수정 버튼이 활성화 되게 만들어야 한다.
sec:authorize="isAuthenticated()”
th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}”
- log-in 한 사용자가 글쓴이인지 검증하는 로직
- #authentication.getPrincipal()
은 Principal 객체를 반환하는 타임리프의 유틸이다.
- 수정 날짜 표시
- 조건문으로
modifyDate
가 null 이 아닐 경우로 설정
#temporals.format
으로 날짜 정보를 원하는 형태로 변환
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
<div class="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="badge bg-light text-dark p-2 text-start">
<div class="mb-2">
<span th:if="${question.author != null}"
th:text="${question.author.username}"></span>
</div>
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<div th:if="${question.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(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></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>
</div>
</div>
<h5 class="border-bottom my-3 py-2"
th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<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>
</div>
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post" class="my-3">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled
th:field="*{content}"
class="form-control"
rows="10"></textarea>
<textarea sec:authorize="isAuthenticated()"
th:field="*{content}"
class="form-control"
rows="10"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
</div>
</html>
📍 Service 계층 - 수정 로직
public void modify(Question question, String subject, String content) {
question.setSubject(subject);
question.setContent(content);
question.setModifyDate(LocalDateTime.now());
repository.save(question);
}
📍 Controller 계층 - 수정 폼
- Web 계층에서 버튼을 막아두어도 url 을 통해 강제로 수정폼으로 접근할 수 있기때문에 principal 로 한번 더 권한을 확인해준다.
- 기존에 작성했었던 내용을 다시 Form 으로 입력해 템플릿으로 전달해준다.
- 이 작업을 하지 않으면 작성자가 다시 처음부터 작성해야 한다.
@Controller
@RequestMapping("/question")
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
private final UserService userService;
@GetMapping("/modify/{id}")
@PreAuthorize("isAuthenticated()")
public String questionModify(
@PathVariable Integer id,
QuestionForm questionForm,
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";
}
@PostMapping("/modify/{id}")
@PreAuthorize("isAuthenticated()")
public String questionModify(
@Valid QuestionForm questionForm,
@PathVariable Integer id,
BindingResult bindingResult,
Principal principal
) {
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);
}
}
📍 Web 계층 - 수정 폼
- 등록할 때 사용했던 question_form 을 수정할 때 재활용 할 수 있다.
- form 의 action 속성을 삭제한다.
- CSRF 값이 자동으로 생성되지 않게된다.
- input 태그를 새로 선언해 CSRF 값을 hidden 형태로 수동 생성한다.
- form 태그에 action 속성이 없을경우 form 은 현재의 URL 을 기준으로 전송이 된다.
- 등록시 폼이 요청되면 등록으로,
수정시 폼이 요청되면 수정으로 url 이 요청됨
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">질문등록</h5>
<form method="post"
th:object="${questionForm}" >
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<div class="mb-3">
<label for="subject" class="form-label">제목</label>
<input type="text" name="subject" th:field="*{subject}" id="subject" class="form-control">
</div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea name="content" id="content" th:field="*{content}" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>