게시물 등록까지 했으니 수정 기능을 구현해보자
/entity/Board, Reply.java 수정일자 필드 추가
@LastModifiedDate
@Column(name = "modifyDate")
private LocalDateTime modifyDate;
- Board.java와 Reply.java와 동일한 컬럼을 추가
- DBEAVER를 통해 modfiy_date 컬럼이 추가된 것을 확인할 수 있음
/templates/board/detail.html 수정, 삭제 버튼 추가
<!-- 수정/삭제 버튼 영역 -->
<div th:if="${board.writer != null and #authentication.getPrincipal().getUsername() == board.writer.username}"
sec:authorize="isAuthenticated()"
class="my-3 d-flex justify-content-end">
<a th:href="@{|/board/modify/${board.bno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
<a th:data-uri="@{|/board/delete/${board.bno}|}"
href="javascript:void(0)" class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>
- sec:authorize="isAuthenticated()" 없으면 500 에러
/templates/board/detail.html 젤 아래 <script> 추가
<script layout:fragment="sub-script" type="text/javascript">
const del_elements = document.getElementsByClassName('delete');
Array.from(del_elements).forEach((element) => {
element.addEventListener('click', function() {
if(confirm('정말로 삭제하시겠습니까?')) {
location.href = this.dataset.uri;
};
});
});
</script>
- 본인 게시글일 때 수정/삭제 버튼 나옴
- 로그인을 하지 않거나 작성자 아이디와 다른 경우 버튼이 나타나지 않음
/controller/BoardController.java modify() GET 메서드 작성
@PreAuthorize("isAuthenticated()") // 로그인 시만 작성 가능
@GetMapping("/modify/{bno}")
public String modify(BoardForm boardForm, @PathVariable("bno") Long bno, Principal principal){
Board board = this.boardService.getBoard(bno); // 기본 게시글 가져옴
if(!board.getWriter().getUsername().equals(principal.getName())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
boardForm.setTitle(board.getTitle());
boardForm.setContent(board.getContent());
return "board/create";
}
- 로그인한 회원의 .name과 게시글 작성자 name과 비교해서 일치하는 경우 수정가능하도록 함
- 수정 클릭시 기존의 create 게시글 제목, 내용 가져옴
- 수정을 눌렀을 때 기존 글의 제목, 내용이 나타나는 것을 확인할 수 있음
/templates/board/create.html form 태그에 있는 th:action 삭제
<form th:object="${boardForm}" method="post">
- create.html 생성, 수정할 때 모두 사용
- get이 /board/create로 들어가면 post도 /board/create로 실행
- get이 /board/modify/{bno}로 들어가면 post도 /board/modify/{bno}로 실행
- 따라서 form 태그에 th:action=@{/board/create}을 적어줄 필요가 없다
- 안지워주면 modify를 할 때도 create로 들어가는 문제 생김!!
/service/BoardService.java 수정 관련된 메서드 추가
// 24.06.24. modBoard 추가 작성
public void modBoard(Board board, String title, String content){
board.setTitle(title);
board.setContent(content);
board.setModifyDate(LocalDateTime.now()); // 수정된 일시 추가
this.boardRepository.save(board); // PK가 있으면 UPDATE
}
/controller/BoardController.java POST 메서드 작성
@PreAuthorize("isAuthenticated()") // 로그인 시만 작성 가능
@PostMapping("/modify/{bno}")
public String modify(@Valid BoardForm boardForm,
BindingResult bindingResult,
Principal principal,
@PathVariable("bno") Long bno) {
if(bindingResult.hasErrors()) {
return "board/create"; // 현재 html 그대로머무르기
}
Board board = this.boardService.getBoard(bno);
if(!board.getWriter().getUsername().equals(principal.getName())){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
this.boardService.modBoard(board, boardForm.getTitle(), boardForm.getContent());
return String.format("redirect:/board/detail/%s", bno);
}
- html에는 BoardForm 객체 값이 들어있음. 컨트롤러에서 받아서 Board 객체 다시 만들어서 서비스로 전달
- GET에서는 BoardForm에 저장하고, POST에서는 BOARD에 저장함
/service/BoardService.java 삭제관련 메서드 추가
public void remBoard(Board board) {
this.boardRepository.delete(board);
}
/controller/BoardController.java delete() GET 메서드 작성
@PreAuthorize("isAuthenticated()") // 로그인 시만 작성 가능
@GetMapping("/delete/{bno}")
public String delete(@PathVariable("bno") Long bno, Principal principal) {
Board board = this.boardService.getBoard(bno);
if(!board.getWriter().getUsername().equals(principal.getName())){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
}
this.boardService.remBoard(board); // 삭제
return "redirect:/";
}
댓글도 똑같이 해주면 된다
/templates/board/detail.html 댓글 수정, 삭제 버튼 추가
<!-- 수정/삭제 버튼 영역 -->
<div th:if="${reply.writer != null and #authentication.getPrincipal().getUsername() == reply.writer.username}"
sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
<a th:href="@{|/reply/modify/${reply.rno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
<a th:data-uri="@{|/reply/delete/${reply.rno}|}" href="javascript:void(0)"
class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>
/service/ReplyService.java 수정, 삭제 관련 메서드 추가
- getReply() 메서드
// 댓글 수정하기 위해 댓글 가져오기
public Reply getReply(Long rno) {
Optional<Reply> reply = this.replyRepository.findById(rno);
if(reply.isPresent()) {
return reply.get();
} else {
throw new NotFoundException("Reply NOT FOUND!");
}
}
- 댓글 수정 버튼 클릭시 rno 값을 가진 내용을 가져오기 위함
- modReply() 메서드
// 댓글 수정처리
public void modReply(Reply reply, String content) {
reply.setContent(content);
reply.setModifyDate(LocalDateTime.now());
this.replyRepository.save(reply);
}
- 위 getReply()메서드에서 가져온 댓글을 수정한 후 다시 reply에 저장하기 위함
/controller/ReplyController.java modify() GET, POST 메서드 작성
- modify() GET 메서드
@PreAuthorize("isAuthenticated()") // 로그인시만 작성가능
@GetMapping("/modify/{rno}")
public String modify(ReplyForm replyForm, @PathVariable("rno") Long rno, Principal principal) {
Reply reply = this.replyService.getReply(rno);
if(!reply.getWriter().getUsername().equals(principal.getName())){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
}
replyForm.setContent(reply.getContent());
return "reply/modify"; // templates/reply/modify.html
}
- 수정버튼 클릭시 modify.html 화면으로 이동
- modify() POST 메서드
@PreAuthorize("isAuthenticated()") // 로그인시만 작성가능
@PostMapping("/modify/{rno}")
public String modify(@Valid ReplyForm replyForm,
@PathVariable("rno") Long rno,
BindingResult bindingResult,
Principal principal) {
if(bindingResult.hasErrors()) {
return "reply/modify";
}
Reply reply = this.replyService.getReply(rno);
if(!reply.getWriter().getUsername().equals(principal.getName())){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정 권한이 없습니다.");
}
this.replyService.modReply(reply, replyForm.getContent());
return String.format("redirect:/board/detail/%s", reply.getBoard().getBno());
}
/templates/reply/modify.html 생성
<html layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container">
<h5 class="my-3 border-bottom pb-2">댓글 수정</h5>
<form th:object="${replyForm}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
<div th:replace="~{errors :: formErrorFragment}"></div>
<div class="mb-3">
<label for="content" class="form-label">내용</label>
<textarea th:field="*{content}" class="form-control" rows="10"></textarea>
</div>
<div class="d-flex justify-content-end">
<input type="submit" value="저장하기" class="btn btn-primary my-2 mx-1">
<a href="/board/list" class="btn btn-sm btn-secondary my-2">취소</a>
</div>
</form>
</div>
</html>
/service/ReplyService.java remply() 삭제 메서드 추가
// 댓글 삭제
public void remReply(Reply reply) {
this.replyRepository.delete(reply);
}
/controller/ReplyController.java delete() 메서드 추가
// 삭제
@PreAuthorize("isAuthenticated()") // 로그인시만 작성가능
@GetMapping("/delete/{rno}")
public String delete(@PathVariable("rno") Long rno, Principal principal) {
Reply reply = this.replyService.getReply(rno);
if(!reply.getWriter().getUsername().equals(principal.getName())){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
}
this.replyService.remReply(reply); // 지우기
return String.format("redirect:/board/detail/%s", reply.getBoard().getBno());
}
/templates/board/detail.html에 게시글, 댓글, 수정날짜 표시
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="container my-3">
<!-- 게시글 영역 -->
<h2 th:text="${board.title}" class="border-bottom py-2"></h2>
<div class="card text-bg-light mb-3">
<div class="card-body">
<div th:text="${board.content}" class="card-text"></div>
<div class="d-flex justify-content-end">
<!-- 수정일자 표시 뱃지 -->
<div th:if="${board.modifyDate !=null}" class="badge text-bg-primary p-2">
<div class="mb-2">
<span>최종수정일</span>
</div>
<div th:text="${#temporals.format(board.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<!-- 작성자와 작성일을 표시하는 뱃지 -->
<div class="badge text-bg-secondary p-2">
<div class="mb-2">
<span th:if="${board.writer != null}" th:text="${board.writer.username}"></span>
</div>
<div th:text="${#temporals.format(board.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
<!-- 수정삭제 버튼 영역 -->
<div th:if="${board.writer != null and #authentication.getPrincipal().getUsername() == board.writer.username}"
sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
<a th:href="@{|/board/modify/${board.bno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
<a th:data-uri="@{|/board/delete/${board.bno}|}" href="javascript:void(0)"
class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>
</div>
</div>
<!-- 댓글 리스트 영역 -->
<h5 th:text="|${#lists.size(board.replyList)}개의 댓글|" class=" border-bottom my-3 py-2"></h5>
<div th:each="reply : ${board.replyList}" class="card border-info shadow-sm my-3">
<div class="card-body">
<div th:text="${reply.content}" class="card-text"></div>
<div class="d-flex justify-content-end">
<!-- 수정일자 표시 뱃지 -->
<div th:if="${reply.modifyDate !=null}" class="badge text-bg-primary p-2">
<div class="mb-2">
<span>최종수정일</span>
</div>
<div th:text="${#temporals.format(reply.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<!-- 작성자와 작성일을 표시하는 뱃지 -->
<div class="badge text-bg-light p-2">
<div class="mb-2">
<span th:if="${reply.writer != null}" th:text="${reply.writer.username}"></span>
</div>
<div th:text="${#temporals.format(reply.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
<!-- 수정삭제 버튼 영역 -->
<div th:if="${reply.writer != null and #authentication.getPrincipal().getUsername() == reply.writer.username}"
sec:authorize="isAuthenticated()" class="my-3 d-flex justify-content-end">
<a th:href="@{|/reply/modify/${reply.rno}|}" class="btn btn-sm btn-outline-success mx-2">수정</a>
<a th:data-uri="@{|/reply/delete/${reply.rno}|}" href="javascript:void(0)"
class="delete btn btn-sm btn-outline-danger">삭제</a>
</div>
</div>
</div>
<!-- 답변기능 영역 -->
<form th:action="@{|/reply/create/${board.bno}|}" th:object="${replyForm}" method="post" class="my-3">
<div th:replace="~{errors :: formErrorFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled name="content" th:field="*{content}" rows="10"
class="form-control"></textarea>
<textarea sec:authorize="isAuthenticated()" enabled name="content" th:field="*{content}" rows="10"
class="form-control"></textarea>
<div class="d-flex justify-content-end mt-2">
<input type="submit" value="댓글등록" class="btn btn-sm btn-primary mx-1">
<a href="/board/list" class="btn btn-sm btn-secondary">목록</a>
</div>
</form>
</div>
<script layout:fragment="sub-script" type="text/javascript">
const del_elements = document.getElementsByClassName('delete');
Array.from(del_elements).forEach(function (element) {
element.addEventListener('click', function () {
if (confirm('삭제하시겠습니까?')) {
location.href = this.dataset.uri;
}
});
});
</script>
</html>
- 등록일과 수정일이 잘 나오는 것을 확인할 수 있다.
- 나중엔 수정한 날만 나오게끔 변경할 예정
게시글 목록 버튼 눌렀을 때 이전 페이지로 이동
/controller/BoardController.java detail() 메서드 수정
@GetMapping("/detail/{bno}")
public String detail(Model model,
@PathVariable("bno") Long bno, ReplyForm replyForm, HttpServletRequest request) {
String prevUrl = request.getHeader("referer");//이전 페이지 변수 담기
log.info(String.format("현재 이전 페이지: %s", prevUrl));
Board board = this.boardService.getBoard(bno);
model.addAttribute("board", board);
model.addAttribute("prevUrl", prevUrl); //이전 페이지 URL 뷰에 전달
return "board/detail";
}
/templates/detail.html 목록 버튼 수정
<a th:href="${prevUrl}" class="btn btn-sm btn-secondary">목록</a>
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] JPA 프로젝트 - Specification 인터페이스를 사용한 검색 기능 구현(1) (0) | 2024.06.24 |
---|---|
[Spring Boot] JPA 프로젝트 - 앵커 기능 구현 (0) | 2024.06.24 |
[Spring Boot] JPA 프로젝트 - 게시글 등록 시 작성자 추가 (0) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - 로그인, 로그아웃 기능 구현 (1) | 2024.06.21 |
[Spring Boot] JPA 프로젝트 - 회원가입 기능 구현 (1) | 2024.06.21 |