댓글 REST API를 완성하기 위해, 컨트롤러와 서비스를 구현하기.
@RestController 생성 _ CommentApiController
@Service 생성 _ CommentService
CommentDto 생성
createCommentDto 사용시 반환할 CommentDto
//import 생략
@Service
public class CommentService {
@Autowired
private CommentRepository commentRepository;
//article데이터도 디비에서 가져와야함. 하나의 서비스에서 해주기 위해서..
@Autowired
private ArticleRepository articleRepository;
public List<CommentDto> comments(Long articleId) {
// 조회 : 댓글 목록
// List<Comment> comments = commentRepository.findByArticleId(articleId);
//
// 변환 : entity > dto
// List<CommentDto> dtos = new ArrayList<CommentDto>();
//
// for(int i = 0 ; i < comments.size() ; i++){
// Comment c = comments.get(i);
// CommentDto dto = CommentDto.createCommentDto(c);
// dtos.add(dto);
// }
//
// 반환
// return dtos;
//스트림을 사용하여 위의 주석 코드를 한방에 해결.
return commentRepository.findByArticleId(articleId)
.stream()
.map(comment -> CommentDto.createCommentDto(comment))
.collect(Collectors.toList());
}
@Transactional
public CommentDto create(Long articleId, CommentDto dto) {
//게시글 조회 및 예외 처리
Article article = articleRepository.findById(articleId)
.orElseThrow(()-> new IllegalArgumentException("댓글 생성 실패 ! 대상 게시글이 없다."));
//댓글 엔티티 생성
Comment comment = Comment.createComment(dto,article);
//엔티티를 db에 저장
Comment created = commentRepository.save(comment);
//dto로 변경하여 반환
return CommentDto.createCommentDto(created);
}
@Transactional
public CommentDto update(Long id, CommentDto dto) {
//댓글 조회 및 예외 발생
Comment target = commentRepository.findById(id)
.orElseThrow(()-> new IllegalArgumentException("F A I L ! ! ! ! !"));
//댓글 수정
target.patch(dto);
//db 갱신
Comment updated = commentRepository.save(target);
//댓글 엔티티를 dto로 변환 및 반환
return CommentDto.createCommentDto(updated);
}
@Transactional
public CommentDto delete(Long id) {
//댓글 조회 및 예외 발생
Comment target = commentRepository.findById(id)
.orElseThrow(()-> new IllegalArgumentException("F A I L ! ! "));
//db에서 삭제
commentRepository.delete(target);
//삭제 댓글 엔티티를 dto로 반환
return CommentDto.createCommentDto(target);
}
}
comment를 createCommentDto를 통해 CommentDto로 mapping 해준다.
스트림은 Object타입을 반환하기 때문에 List로 묶어주어 형변환한다.
//import 생략
@RestController
public class CommentApiController {
@Autowired
private CommentService commentService;
//댓글 목록 조회
@GetMapping("/api/articles/{articleId}/comments")
public ResponseEntity<List<CommentDto>> comments(@PathVariable Long articleId){
//서비스에게 위임
List<CommentDto> dtos = commentService.comments(articleId);
//결과 응답
return ResponseEntity.status(HttpStatus.OK).body(dtos);
}
//댓글 생성
@PostMapping("/api/articles/{articleId}/comments")
public ResponseEntity<CommentDto> create(@PathVariable Long articleId,
@RequestBody CommentDto dto){
CommentDto createDto= commentService.create(articleId,dto);
return ResponseEntity.status(HttpStatus.OK).body(createDto);
}
//댓글 수정
@PatchMapping("/api/articles/comments/{id}")
public ResponseEntity<CommentDto> update(@PathVariable Long id,
@RequestBody CommentDto dto){
CommentDto updatedDto= commentService.update(id,dto);
return ResponseEntity.status(HttpStatus.OK).body(updatedDto);
}
//댓글 삭제
@DeleteMapping("/api/articles/comments/{id}")
public ResponseEntity<CommentDto> delete(@PathVariable Long id){
CommentDto deletedDto= commentService.delete(id);
return ResponseEntity.status(HttpStatus.OK).body(deletedDto);
}
}
//import 생략
@ToString
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CommentDto {
private Long id;
@JsonProperty("article_id")
private Long articleId;
private String nickname;
private String body;
public static CommentDto createCommentDto(Comment comment) {
return new CommentDto(
comment.getId(),
comment.getArticle().getId(),
comment.getNickname(),
comment.getBody()
);
}
}
댓글 목록을 게시글 상세 뷰 페이지에 추가하기.
show.mustache 파일에 {{>comments/_comments}} 를 이용하여 _comments.mustache 파일을 추가한다.
<div>
<!--댓글 목록-->
{{>comments/_list}}
<!--새 댓글 영역-->
{{>comments/_new}}
</div>
<div id="comments-list">
{{#commentDtos}}
<div class="card m-3" id="comments-{{id}}">
<div class="card-header">
{{nickname}}
</div>
<div class="card-body">
{{body}}
</div>
</div>
{{/commentDtos}}
</div>
id로 "commentsDtos"데이터를 가져오고 "commentDtos"의 이름으로 Model에 등록
@GetMapping("/articles/{id}")
public String show(@PathVariable Long id, Model model){
log.info("id = "+ id);
//1 아이디로 데이터 가져오기
Article articleEntity = articleRepository.findById(id).orElse(null);
List<CommentDto> commentsDtos = commentService.comments(id);
//2 가져온 데이터를 모델에 등록
model.addAttribute("article",articleEntity);
model.addAttribute("commentDtos",commentsDtos);
//3 보여줄 페이지를 설정
return "/articles/show";
}
특정 게시글을 보여주는 페이지에서 그 글에 대한 댓글도 카드형식으로 함께 보여주는 페이지이다. 따라서 ArticleController에 model 등록 코드를 추가하였다.
나의 실수 기록
이 과정에서 처음에는 "commentsDtos"의 이름으로 model에 등록했는데 _list.mustache에는 "commentDtos"로 해당 모델을 사용하였기 때문에 뷰 페이지에서 댓글이 보이지 않는 실수를 했었다. (s 하나도 주의할 것!!)
댓글 등록 페이지를 만들고, REST API를 호출하여 새 댓글 생성을 해보기.
게시글 등록은 HTML의 form태그를 통한 HTTP 요청 방식을 사용했지만,
댓글 등록은 JS를 사용하여 HTTP 요청 방식을 사용했다.
최근 더 선호하는 요청 방식이다.
JS REST API 호출
객체를 JS를 통해서도 보낼 수 있다.
자바스크립을 사용하긴 하지만 깊게 공부하는 것이 아니므로 현재 프로젝트에서 사용한 문법을 이해한 것만 정리하였다.
select DOM element : 뷰페이지에서 변수화시킬 대상을 선택해준다.
//댓글 생성 버튼을 변수화
const commentCreateBtn = document.querySelector(“#comment-create-btn”);
handle specific event : 해당 변수에 지정한 이벤트가 생기는지 주시한다. 이벤트가 발생하면 function이 동작한다.
//버튼 클릭 이벤트를 감지
commentCreateBtn.addEventListener(“click”, function() {
.
//생략
.
}
fetch REST API resources
: 변수 선언
: 상수 선언(변경 불가 변수)
해당 코드가 끝나고 동작할 코드
<!--댓글 생성 뷰페이지의 코드 : _new.mustache 코드 -->
<div class="card m-2" id="comments-new">
<div class="card-body">
<form>
<!-- 닉네임 입력 -->
<div class="mb-3">
<label class="form-label">닉네임</label>
<input type="text" class="form-control form-control-sm" id="new-comment-nickname">
</div>
<!-- 댓글 본문 입력 -->
<div class="mb-3">
<label class="form-label">댓글 내용</label>
<textarea type="text" class="form-control form-control-sm" rows="3" id="new-comment-body"></textarea>
</div>
<!-- 히든 인풋 -->
<!-- 보이진 않지만 가지고 있는 값 -->
{{#article}}
<input type="hidden" id="new-comment-article-id" value="{{id}}">
{{/article}}
<!-- 전송 버튼 -->
<button type="button" class="btn btn-outline-primary btn-sm" id="comment-create-btn">댓글 작성</button>
</form>
</div>
</div>
<script>
{
//댓글 생성 버튼을 변수화
const commentCreateBtn = document.querySelector("#comment-create-btn");
//버튼 클릭 이벤트를 감지
commentCreateBtn.addEventListener("click", function() {
console.log(“Button Click !!”);
//새 댓글 객체 생성
const comment = {
nickname: document.querySelector("#new-comment-nickname").value,
body: document.querySelector("#new-comment-body").value,
article_id: document.querySelector("#new-comment-article-id").value
};
console.log(comment);
const url="/api/articles/"+ comment.article_id + "/comments";
fetch( url , {
method: "post",
body: JSON.stringify(comment),
headers:{
"Content-Type": "application/json"
}
}).then(response =>
{
//http 응답코드에 따른 메시지 출력
const msg = (response.OK) ? " 댓글 등록 성공 " : " 댓글 등록 실패 " ;
alert(msg);
//현재 페이지 새로고침
window.location.reload();
}
);
} );
}
</script>
console.log를 통해 브라우저의 console에서 출력을 확인할 수 있다.