하나씩 차근차근
article thumbnail

앞에서 question 을 수정하고 삭제하는 기능을 만들었습니다.

이번에는 answer 을 수정하고 삭제할 수 있도록 해보겠습니다.

 

Answer 수정

answer 이 수정되는 과정은 question 이 수정되는 과정과 동일합니다.

수정 버튼 클릭 -> GET 방식 처리 -> answer_form 데이터 수정 -> POST 방식으로 처리 후 저장

question_detail

다음과 같이 답변이 출력되는 부분에 수정 버튼을 만듭니다.

<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="mb-2">
						<span th:if="${answer.author != null}" th:text="${answer.author.username}"></span>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<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>

위와 같이 수정 버튼을 누르게 되면 localhost:8080/answer/modify/{answer.id} 로 GET 방식으로 요청을 하게 됩니다.

 

AnswerService

다음으로 AnswerController 에서 사용할 메서드를 생성합니다.

package com.crud.service;
...
@Service
public class AnswerService {

	@Autowired
	private AnswerRepository answerRepository;

	...
	public Answer getAnswer(Integer id) {
		Optional<Answer> answer = answerRepository.findById(id);
		
		// answer이 없을 경우 예외처리 필요
		return answer.get();
	}
	
	public void modify(Answer answer, String content) {
		answer.setContent(content);
		answer.setModifyDate(LocalDateTime.now());
		answerRepository.save(answer);
	}
}

위와 같이 id 값을 인자로 전달해 getAnswer 메서드를 통해 해당 id 의 answer 을 가져오며,

modify 메서드를 통해 answer 객체를 수정합니다.

getAnswer 메서드에서 answer 객체가 없을 경우 예외처리는 나중에 진행하겠습니다.

 

AnswerController

다음으로 수정 버튼을 클릭했을때 GET 방식으로 받은 요청을 처리하는 메서드를 작성하겠습니다.

package com.crud.controller;
...
@Controller
public class AnswerController {
	
	@Autowired
	private QuestionService questionService;
	
	@Autowired
	private AnswerService answerService;
	
	@Autowired
	private UserService userService;

	...
	
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/answer/modify/{id}")
	public String answerModify(AnswerForm answerForm, @PathVariable("id") Integer id, Principal principal) {
		
		Answer answer = answerService.getAnswer(id);
		
		if(!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
		}
		
		answerForm.setContent(answer.getContent());
		return "answer_form";
	}
}

principal 객체를 통해 현재 로그인한 사용자와 answer 의 author 이 다를 경우 에러를 발생시킵니다.

다음으로 POST 방식으로 요청이 왔을 경우 answerService 의 modify 메서드를 통해 

answer 를 수정하고 저장합니다.

package com.crud.controller;
...
@Controller
public class AnswerController {
	
	@Autowired
	private QuestionService questionService;
	
	@Autowired
	private AnswerService answerService;
	
	@Autowired
	private UserService userService;
	...	
	@PreAuthorize("isAuthenticated()")
	@PostMapping("/answer/modify/{id}")
	public String answerModify(@Valid AnswerForm answerForm, BindingResult bindingResult, @PathVariable("id") Integer id, Principal principal) {
		
		if(bindingResult.hasErrors()) {
			return "answer_form";
		}
		
		Answer answer = answerService.getAnswer(id);
		
		if(!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
		}
		
		answerService.modify(answer, answerForm.getContent());
		return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());
	}
}

 

answer_form

다음으로 답변 수정 버튼을 클릭했을때 수정을 할 수 있는 페이지인 answer_form 을 새로 작성합니다.

<html layout:decorate="~{layout}">
	<div layout:fragment="content" class="container">
		<h5 class="my-3 border-bottom pb-2">답변 수정</h5>
		<form th:object="${answerForm}" 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>
			<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>

앞에서 작성한 question_form 과 동일하게 form 태그 안에 action 속성이 없으므로

현재 호출된 URL 로 POST 방식으로 요청을 하게 됩니다.

 

결과

다음과 같이 답변을 작성하고 수정해보겠습니다.

수정 버튼을 클릭하면 다음과 같이 답변을 수정할 수 있는 페이지로 이동하게 됩니다.

답변을 수정하고 저장하기를 누르면 답변이 수정됩니다.

 

Answer 삭제

Answer 을 삭제하는 기능은 Question 을 삭제하는 기능과 동일하게 만듭니다.

question_detail

question_detail 페이지에서 답변을 삭제할 수 있도록 삭제 버튼을 추가합니다.

<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="mb-2">
						<span th:if="${answer.author != null}" th:text="${answer.author.username}"></span>
					</div>
					<div class="badge bg-light text-dark p-2 text-start">
						<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>	
	...
</html>

삭제 버튼도 수정 버튼과 동일하게 현재 로그인된 사용자와 answer 의 author 을 비교해서 

같은 경우 표시되도록 합니다.

 

AnswerService

AnswerService 에 답변을 삭제하는 메서드를 생성합니다.

package com.crud.service;
...
@Service
public class AnswerService {

	@Autowired
	private AnswerRepository answerRepository;

	...

	public void delete(Answer answer) {
		answerRepository.delete(answer);
	}
}

 

AnswerController

다음으로 삭제 버튼을 클릭했을때 요청을 처리하는 메서드를 AnswerController 안에 만듭니다.

AnswerService 의 delete 메서드를 통해 요청이 들어오면 해당 Answer 을 삭제하도록 합니다.

package com.crud.controller;
...
@Controller
public class AnswerController {
	
	@Autowired
	private QuestionService questionService;
	
	@Autowired
	private AnswerService answerService;
	
	@Autowired
	private UserService userService;
    
	...	
    
	@PreAuthorize("isAuthenticated()")
	@GetMapping("/answer/delete/{id}")
	public String answerDelete(Principal principal, @PathVariable("id") Integer id) {
		Answer answer = answerService.getAnswer(id);
		
		if(!answer.getAuthor().getUsername().equals(principal.getName())) {
			throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
		}
		
		answerService.delete(answer);
		return String.format("redirect:/question/detail/%s", answer.getQuestion().getId());
	}
}

 

결과

다음과 같이 삭제 버튼을 클릭하면 게시글이 삭제됩니다.

profile

하나씩 차근차근

@jeehwan_lee

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!