포토그램
[Chapter 10] 댓글 구현하기
yukuda
2024. 2. 25. 20:53
728x90
74. 댓글-Comment 모델 만들기
Comment 모델 만들기
Comment 레파지토리 만들기
Comment 서비스 만들기
Comment API 컨트롤러 만들기
댓글 쓰기
댓글 삭제
com.cos.photogramstart.domain.comment.Comment.java
package com.cos.photogramstart.domain.comment;
import java.time.LocalDateTime;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import com.cos.photogramstart.domain.image.Image;
import com.cos.photogramstart.domain.likes.Likes;
import com.cos.photogramstart.domain.user.User;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@Entity // DB에 테이블을 생성
@Data
@NoArgsConstructor // 빈 생성자
@AllArgsConstructor // 전체 생성자
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라간다.
private int id;
@Column(length=100,nullable=false)
private String content;
@JoinColumn(name="userId")
@ManyToOne(fetch=FetchType.EAGER)
private User user;
@JoinColumn(name="imageId")
@ManyToOne(fetch=FetchType.EAGER)
private Image image;
private LocalDateTime createDate;
@PrePersist // DB에 Insert가 되기 직전에 실행
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
com.cos.photogramstart.domain.comment.CommentRepository.java
package com.cos.photogramstart.domain.comment;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CommentRepository extends JpaRepository<Comment, Integer>{
}
75. 댓글-컨트롤러, 서비스 만들기
com.cos.photogramstart.service.CommentService.java
package com.cos.photogramstart.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.cos.photogramstart.domain.comment.Comment;
import com.cos.photogramstart.domain.comment.CommentRepository;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class CommentService {
private final CommentRepository commentRepository;
@Transactional
public Comment 댓글쓰기() {
return null;
}
@Transactional
public void 댓글삭제() {
}
}
com.cos.photogramstart.web.api.CommentApiController.java
package com.cos.photogramstart.web.api;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.service.CommentService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class CommentApiController {
private final CommentService commentService;
@PostMapping("/api/comment")
public ResponseEntity<?> commentSave() {
return null;
}
@DeleteMapping("/api/comment/{id}")
public ResponseEntity<?> commentDelete(@PathVariable int id) {
return null;
}
}
76. 댓글-댓글쓰기 Ajax 함수 만들기
story.js
// (4) 댓글쓰기
function addComment(imageId) {
let commentInput = $(`#storyCommentInput-${imageId}`);
let commentList = $(`#storyCommentList-${imageId}`);
let data = {
imagaId:imageId,
content: commentInput.val()
}
if (data.content === "") {
alert("댓글을 작성해주세요!");
return;
}
$.ajax({
type:"post",
url:"/api/comment",
data:JSON.stringify(data),
contentType:"application/json; charset=utf-8",
dataType:"json"
}).done(res=>{
console.log("성공",res);
}).fail(error=>{
console.log("오류",error);
})
let content = `
<div class="sl__item__contents__comment" id="storyCommentItem-2"">
<p>
<b>GilDong :</b>
댓글 샘플입니다.
</p>
<button><i class="fas fa-times"></i></button>
</div>
`;
commentList.prepend(content); // 최신 댓글
commentInput.val("");
}
77. 댓글-댓글쓰기 완료
CommitDto.java
package com.cos.photogramstart.web.dto.comment;
import lombok.Data;
@Data
public class CommentDto {
private String content;
private int imageId;
// toEntity가 필요없음
}
CommentApiController.java
package com.cos.photogramstart.web.api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.config.auth.PrincipalDatails;
import com.cos.photogramstart.domain.comment.Comment;
import com.cos.photogramstart.service.CommentService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.comment.CommentDto;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class CommentApiController {
private final CommentService commentService;
@PostMapping("/api/comment")
public ResponseEntity<?> commentSave(@RequestBody CommentDto commentDto,
@AuthenticationPrincipal PrincipalDatails principalDatails ) { // x-www-~ 뭐시기 안받고 JSON 받으려면 @RequestBody 붙여야함
Comment comment = commentService.댓글쓰기(commentDto.getContent(),commentDto.getImageId(),principalDatails.getUser().getId()); // content, imageId, userId
return new ResponseEntity<>(new CMRespDto<>(1,"댓글쓰기성공",comment),HttpStatus.CREATED);
}
@DeleteMapping("/api/comment/{id}")
public ResponseEntity<?> commentDelete(@PathVariable int id) {
return null;
}
}
CommentService.java
package com.cos.photogramstart.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.cos.photogramstart.domain.comment.Comment;
import com.cos.photogramstart.domain.comment.CommentRepository;
import com.cos.photogramstart.domain.image.Image;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.domain.user.UserRepository;
import com.cos.photogramstart.handler.ex.CustomApiException;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class CommentService {
private final CommentRepository commentRepository;
private final UserRepository userRepository;
@Transactional
public Comment 댓글쓰기(String content, int imageId, int userId) {
// Tip (객체를 만들 때 id값만 담아서 insert 할 수 있다.)
// 대신 return 시에 image객체와 user객체는 id값만 가지고 있는 빈 객체를 리턴받는다.
Image image = new Image();
image.setId(imageId);
User userEntity = userRepository.findById(userId).orElseThrow(()->{
throw new CustomApiException("유저 아이디를 찾을 수 없습니다.");
});
Comment comment = new Comment();
comment.setContent(content);
comment.setImage(image);
comment.setUser(userEntity);
return commentRepository.save(comment);
}
@Transactional
public void 댓글삭제() {
}
}
78. 댓글-뷰 렌더링
Comment.java
@JoinColumn(name="imageId")
@ManyToOne(fetch=FetchType.EAGER)
private Image image;
story.js
<div id="storyCommentList-${image.id}">`;
image.comments.forEach((comment)=>{
item += `<div class="sl__item__contents__comment" id="storyCommentItem-${comment.id}">
<p>
<b>${comment.user.username} :</b> ${comment.content}
</p>
<button>
<i class="fas fa-times"></i>
</button>
</div>`;
});
itme += `
$.ajax({
type:"post",
url:"/api/comment",
data:JSON.stringify(data),
contentType:"application/json; charset=utf-8",
dataType:"json"
}).done(res=>{
console.log("성공",res);
let comment = res.data;
let content = `
<div class="sl__item__contents__comment" id="storyCommentItem-${comment.id}">
<p>
<b>${comment.user.username}:</b>
${comment.content}
</p>
<button><i class="fas fa-times"></i></button>
</div>
`;
commentList.prepend(content); // 최신 댓글
}).fail(error=>{
console.log("오류",error);
})
commentInput.val(""); // 인풋 필드를 깨끗하게 비워준다.
ImageApiController.java
@GetMapping("/api/image")
public ResponseEntity<?> imageStory(@AuthenticationPrincipal PrincipalDatails principalDatails,
@PageableDefault(size=3) Pageable pageable){
Page<Image> images = imageService.이미지스토리(principalDatails.getUser().getId(), pageable);
return new ResponseEntity<>(new CMRespDto<>(1, "성공",images),HttpStatus.OK); // JS에서 호출해야함
}
Image.java
// 댓글
@OrderBy("id DESC")
@JsonIgnoreProperties({"image"})
@OneToMany(mappedBy = "image")
private List<Comment> comments;
79. 댓글-댓글 삭제하기
story.js
// (0) 현재 로그인한 사용자 아이디
let principalId = $("#principalId").val();
// (5) 댓글 삭제
function deleteComment(commentId) {
$.ajax({
type:"delete",
url:`/api/comment/${commentId}`,
dataType:"json"
}).done(res=>{
console.log("성공",res);
$(`#storyCommentItem-${commentId`).remove();
}).fail(error=>{
console.log("성공",error);
});
}
CommentApiController.java
package com.cos.photogramstart.web.api;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.config.auth.PrincipalDatails;
import com.cos.photogramstart.domain.comment.Comment;
import com.cos.photogramstart.service.CommentService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.comment.CommentDto;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class CommentApiController {
private final CommentService commentService;
@PostMapping("/api/comment")
public ResponseEntity<?> commentSave(@RequestBody CommentDto commentDto,
@AuthenticationPrincipal PrincipalDatails principalDatails ) { // x-www-~ 뭐시기 안받고 JSON 받으려면 @RequestBody 붙여야함
Comment comment = commentService.댓글쓰기(commentDto.getContent(),commentDto.getImageId(),principalDatails.getUser().getId()); // content, imageId, userId
return new ResponseEntity<>(new CMRespDto<>(1,"댓글쓰기성공",comment),HttpStatus.CREATED);
}
@DeleteMapping("/api/comment/{id}")
public ResponseEntity<?> commentDelete(@PathVariable int id) {
commentService.댓글삭제(id);
return new ResponseEntity<>(new CMRespDto<>(1,"댓글삭제성공",null),HttpStatus.OK);
}
}
CommentService.java
@Transactional
public void 댓글삭제(int id) {
try {
commentRepository.deleteById(id);
} catch (Exception e) {
throw new CustomApiException(e.getMessage());
}
}
80. 댓글-유효성 검사하기
CommentDto.java
package com.cos.photogramstart.web.dto.comment;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.Data;
// NotNull = Null값 체크
// NotEmpty = 빈 값이거나 null 체크
// NotBlank = 빈 값이거나 null 체크 그리고 빈 공백(스페이스)까지
@Data
public class CommentDto {
@NotBlank // 빈 값이거나 null 체크 그리고 빈 공백까지
private String content;
@NotNull // null 갑 체크
private Integer imageId;
// toEntity가 필요없음
}
CommentApiController.java
package com.cos.photogramstart.web.api;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.cos.photogramstart.config.auth.PrincipalDatails;
import com.cos.photogramstart.domain.comment.Comment;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.service.CommentService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.comment.CommentDto;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class CommentApiController {
private final CommentService commentService;
@PostMapping("/api/comment")
public ResponseEntity<?> commentSave(@Valid @RequestBody CommentDto commentDto, BindingResult bindingResult,
@AuthenticationPrincipal PrincipalDatails principalDatails ) { // x-www-~ 뭐시기 안받고 JSON 받으려면 @RequestBody 붙여야함
if(bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(),error.getDefaultMessage());
// System.out.println(error.getDefaultMessage());
}
throw new CustomValidationApiException("유효성 검사 실패함", errorMap);
}
Comment comment = commentService.댓글쓰기(commentDto.getContent(),commentDto.getImageId(),principalDatails.getUser().getId()); // content, imageId, userId
return new ResponseEntity<>(new CMRespDto<>(1,"댓글쓰기성공",comment),HttpStatus.CREATED);
}
@DeleteMapping("/api/comment/{id}")
public ResponseEntity<?> commentDelete(@PathVariable int id) {
commentService.댓글삭제(id);
return new ResponseEntity<>(new CMRespDto<>(1,"댓글삭제성공",null),HttpStatus.OK);
}
}
UserApiController.java
package com.cos.photogramstart.web.api;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.cos.photogramstart.config.auth.PrincipalDatails;
import com.cos.photogramstart.domain.user.User;
import com.cos.photogramstart.handler.ex.CustomValidationApiException;
import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.service.SubscribeService;
import com.cos.photogramstart.service.UserService;
import com.cos.photogramstart.web.dto.CMRespDto;
import com.cos.photogramstart.web.dto.subscribe.SubscribeDto;
import com.cos.photogramstart.web.dto.user.UserUpdateDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@RestController
public class UserApiController {
private final UserService userService;
private final SubscribeService subscribeService;
@PutMapping("/api/user/{principalId}/profileImageUrl")
public ResponseEntity<?> profileImageUrlUpdate(@PathVariable int principalId, MultipartFile profileImageFile, @AuthenticationPrincipal PrincipalDatails principalDatails) {
User userEntity = userService.회원프로필사진변경(principalId, profileImageFile);
principalDatails.setUser(userEntity); // 세션 변경
return new ResponseEntity<>(new CMRespDto<>(1, "프로필사진변경 성공",null), HttpStatus.OK);
}
@GetMapping("/api/user/{pageUserId}/subscribe")
public ResponseEntity<?> subscribeList(@PathVariable int pageUserId, @AuthenticationPrincipal PrincipalDatails principalDatails){
List<SubscribeDto> subscribeDto = subscribeService.구독리스트(principalDatails.getUser().getId(),pageUserId);
return new ResponseEntity<>(new CMRespDto<>(1,"구독자 정보 리스트 가져오기 성공",subscribeDto),HttpStatus.OK);
}
@PutMapping("/api/user/{id}")
public CMRespDto<?> update(
@PathVariable int id,
@Valid UserUpdateDto userUpdateDto,
BindingResult bindingResult, // 꼭 @Valid가 적혀 있는 다음 파라미터에 적어야함
@AuthenticationPrincipal PrincipalDatails principalDatails) {
if(bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
for(FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(),error.getDefaultMessage());
// System.out.println(error.getDefaultMessage());
}
throw new CustomValidationApiException("유효성 검사 실패함", errorMap);
}else {
// System.out.println(userUpdateDto);
User userEntity = userService.회원수정(id, userUpdateDto.toEntity());
principalDatails.setUser(userEntity); // 세션 정보 변경(바뀌면 '남'이 뜨게 (gender))
return new CMRespDto<>(1,"회원수정완료",userEntity); // 응답시에 userEntity의 모든 getter 함수가 호출되고 JSON으로
// 파싱하여 응답한다.
}
}
}
story.js
$.ajax({
type:"post",
url:"/api/comment",
data:JSON.stringify(data),
contentType:"application/json; charset=utf-8",
dataType:"json"
}).done(res=>{
console.log("성공",res);
let comment = res.data;
let content = `
<div class="sl__item__contents__comment" id="storyCommentItem-${comment.id}">
<p>
<b>${comment.user.username}:</b>
${comment.content}
</p>
<button onclick="deleteComment(${conment.id})"><i class="fas fa-times"></i></button>
</div>
`;
commentList.prepend(content); // 최신 댓글
}).fail(error=>{
console.log("오류",error.responseJson.data.content);
alert(error.responseJson.data.content);
})
commentInput.val(""); // 인풋 필드를 깨끗하게 비워준다.