드디어 댓글 기능을 구현했습니다. 어제는 따로 조회 까지만 했고 오늘 오전에 for문 사용해서 게시글 목록에 댓글까지 전체 조회 하는 기능을 구현했습니다. 이제 이번주차 심화 과제 Lv 1을 조원들과 협력해서 할 예정입니다. Lv1의 경우에는 Spring security를 사용해서 CRUD를 구현하면 됩니다. 시큐리티 부분만 구현하면 나머지는 상대적으로 간단(?)해서 빠르게 구현할 수 있으리라 예상됩니다.
게시판 댓글 기능 구현
@CommentController
package com.example.post.controller;
import com.example.post.dto.CommentRequestDto;
import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.service.CommentService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/api/comment/")
@RequiredArgsConstructor
@RestController
public class CommentController {
private final CommentService commentService;
@PostMapping("/{postId}")
public CommentResponseDto createComment(@PathVariable Long postId, @RequestBody CommentRequestDto requestDto, HttpServletRequest request){
return commentService.createComment(postId, requestDto, request);
}
@PutMapping("/{commentId}")
public ResponseDto updateComment(@PathVariable Long commentId, @RequestBody CommentRequestDto requestDto, HttpServletRequest request) {
return commentService.updateComment(commentId, requestDto, request);
}
@DeleteMapping("/{commentId}")
public ResponseDto deleteComment(@PathVariable Long commentId, HttpServletRequest request) {
return commentService.deleteComment(commentId, request);
}
}
- 나중에 고쳐보고 싶은 점이라면 url을 /{postId}/{commentId}로 해서 더 직관적으로 만들고 싶습니다. 지금은 POSTMAN으로 테스트하고 있어서 괜찮은데 프론트가 합쳐지면 매우 헷갈릴거 같거든요.
- 하면서 깨달은 중요한점은 @RequestBody 잊지말자!!!! 입니다. 저 어노테이션을 안달아주면 데이터가 json 형식으로 받질 못해서 에러가 납니다.
@CommentService
package com.example.post.service;
import com.example.post.dto.CommentRequestDto;
import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.entity.Comment;
import com.example.post.entity.Post;
import com.example.post.entity.User;
import com.example.post.jwt.JwtUtil;
import com.example.post.repository.CommentRepository;
import com.example.post.repository.PostRepository;
import com.example.post.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CommentService {
private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
public CommentResponseDto createComment(Long postId, CommentRequestDto requestDto, HttpServletRequest request) {
//토큰 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
if (token != null) {
if (jwtUtil.validateToken(token)) {
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
//토큰의 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
//게시글 조회
Post post = postRepository.findById(postId).orElseThrow(
() -> new NullPointerException("게시글이 존재하지 않습니다.")
);
Comment comment = new Comment(user.getUsername(), requestDto.getComment(), post.getId());
commentRepository.save(comment);
return new CommentResponseDto(comment);
} else{
return null;
}
}
public ResponseDto updateComment(Long commentId, CommentRequestDto requestDto, HttpServletRequest request) {
//토큰 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
if(token != null){
if(jwtUtil.validateToken(token)) {
claims = jwtUtil.getUserInfoFromToken(token);
}else {
throw new IllegalArgumentException("Token Error");
}
//토큰의 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
Comment comment = commentRepository.findById(commentId).orElseThrow(
() -> new NullPointerException("댓글이 존재하지 않습니다.")
);
comment.update(requestDto);
return new ResponseDto(HttpStatus.OK.value(),"댓글 수정 성공");
}
else{
return null;
}
}
public ResponseDto deleteComment(Long commentId, HttpServletRequest request) {
//토큰 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
if(token != null){
if(jwtUtil.validateToken(token)) {
claims = jwtUtil.getUserInfoFromToken(token);
}else {
throw new IllegalArgumentException("Token Error");
}
//토큰의 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
Comment comment = commentRepository.findById(commentId).orElseThrow(
() -> new NullPointerException("댓글이 존재하지 않습니다.")
);
commentRepository.deleteById(commentId);
return new ResponseDto(HttpStatus.OK.value(),"댓글 삭제 성공");
}else{
return null;
}
}
}
- 댓글 작성, 수정, 삭제 자체는 어렵지는 않았습니다.
- Dto를 결정할 때, 어떤걸 요청하고 어떤걸 보낼지에 대한 고민은 있었습니다.
@PostService
package com.example.post.service;
import com.example.post.dto.CommentResponseDto;
import com.example.post.dto.PostRequestDto;
import com.example.post.dto.PostResponseDto;
import com.example.post.dto.ResponseDto;
import com.example.post.entity.Comment;
import com.example.post.entity.Post;
import com.example.post.entity.User;
import com.example.post.entity.UserRoleEnum;
import com.example.post.jwt.JwtUtil;
import com.example.post.repository.CommentRepository;
import com.example.post.repository.PostRepository;
import com.example.post.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class PostService {
private final JwtUtil jwtUtil;
private final PostRepository postRepository;
private final UserRepository userRepository;
private final CommentRepository commentRepository;
//게시글 작성
@Transactional
public PostResponseDto createPost(PostRequestDto requestDto, HttpServletRequest request) {
//토큰 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
//토큰 확인 후 게시글 작성 가능
if(token != null){
if(jwtUtil.validateToken(token)) {
claims = jwtUtil.getUserInfoFromToken(token);
}else {
throw new IllegalArgumentException("Token Error");
}
//토큰의 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
//요청 받은 Dto로 DB에 저장할 객체 만들기
Post post = postRepository.saveAndFlush(new Post (requestDto, user.getId(), user.getUsername()));
return new PostResponseDto(post, null);
} else {
return null;
}
}
//게시글 전체 조회 USER/ADMIN 상관 없음
public List<PostResponseDto> getPostList() {
List<Post> postList = postRepository.findAllByOrderByModifiedAtDesc();
if(postList.isEmpty()){
throw new NullPointerException("게시글이 존재하지 않습니다.");
}
List<PostResponseDto> postResponseDtoList = new ArrayList<>();
//댓글을 각 게시물마다 조회해서 넣어주는건 에바
for (Post post : postList) {
List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();
Long postId = post.getId();
List<Comment> comments = commentRepository.findAllByPostId(postId);
for (Comment comment : comments) {
CommentResponseDto commentResponseDto = new CommentResponseDto(comment);
commentResponseDtoList.add(commentResponseDto);
}
postResponseDtoList.add(new PostResponseDto(post,commentResponseDtoList ));
}
return postResponseDtoList;
}
@Transactional(readOnly = true)
//게시글 상세 조회 USER/ADMIN 상관 없음
public PostResponseDto getPost(Long id) {
Post post = postRepository.findById(id).orElseThrow(
() -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
);
List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();
for (int i =0; i<post.getComments().size(); i++){
Comment comment = post.getComments().get(i);
commentResponseDtoList.add(new CommentResponseDto(comment));
}
return new PostResponseDto(post, commentResponseDtoList);
}
@Transactional
//게시글 수정 USER/ADMIN 권한 설정
public PostResponseDto updatePost(Long id, PostRequestDto requestDto, HttpServletRequest request) {
//request에서 token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
//토큰이 있는 경우에만 게시글 수정 가능
if(token != null){
if(jwtUtil.validateToken(token)) {
//토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
//토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
//게시글 있는지 조회
Post post = postRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
() -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
);
//사용자 권한 가져와서 Admin 이면 다 수정, USER 이면 본인이 작성한 글만 수정
UserRoleEnum userRoleEnum = user.getRole();
if(userRoleEnum == UserRoleEnum.USER) {
String Username = user.getUsername();
if(!post.getUsername().equals(Username)){
throw new IllegalArgumentException("본인이 작성한 게시글만 수정 가능합니다.");
}
}
post.update(requestDto);
List<Comment> commentList = commentRepository.findAllByPostId(id);
List<CommentResponseDto> commentResponseDtoList = new ArrayList<>();
for (Comment comment : commentList) {
commentResponseDtoList.add(new CommentResponseDto(comment));
}
return new PostResponseDto(post, commentResponseDtoList);
} else {
return null;
}
}
@Transactional
public ResponseDto deletePost(Long id, HttpServletRequest request) {
//request에서 token 가져오기
String token = jwtUtil.resolveToken(request);
Claims claims;
//토큰이 있는 경우에만 게시글 수정 가능
if(token != null) {
if (jwtUtil.validateToken(token)) {
//토큰에서 사용자 정보 가져오기
claims = jwtUtil.getUserInfoFromToken(token);
} else {
throw new IllegalArgumentException("Token Error");
}
//토큰에서 가져온 사용자 정보를 사용하여 DB 조회
User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
() -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
);
//게시글 있는지 조회
Post post = postRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
() -> new NullPointerException("해당 게시글은 존재하지 않습니다.")
);
//사용자 권한 가져와서 Admin 이면 다 수정, USER 이면 본인이 작성한 글만 수정
UserRoleEnum userRoleEnum = user.getRole();
if (userRoleEnum == UserRoleEnum.USER) {
String Username = user.getUsername();
if (!post.getUsername().equals(Username)) {
throw new IllegalArgumentException("본인이 작성한 게시글만 삭제 가능합니다.");
}
}
postRepository.deleteById(id);
return new ResponseDto(HttpStatus.OK.value(), "게시글 삭제 성공");
} else {
return null;
}
}
}
- 기존에 구현한 기능에서 조회 기능에 댓글만 추가했습니다.
JPA 연관관계
이건 좀 고민 더 해보고 정리해보려고 합니다.
'TIL' 카테고리의 다른 글
TIL 첫 포트폴리오 프로젝트 시작 221216 (0) | 2022.12.17 |
---|---|
TIL JPA 마스터의 길 게시글 댓글 연관 관계 만들기 221215 (0) | 2022.12.15 |
TIL CRUD 마스터의 길 2 221213 (0) | 2022.12.14 |
TIL CRUD 마스터의 길 221212 (0) | 2022.12.13 |
TIL/WIL ORM SQL MVC 부트캠프 1+4주차 회고 221211 (1) | 2022.12.11 |