diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/controller/CommentController.java b/server/src/main/java/com/main36/pikcha/domain/comment/controller/CommentController.java index d8e7de60..9b1c4b72 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/controller/CommentController.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/controller/CommentController.java @@ -1,11 +1,13 @@ package com.main36.pikcha.domain.comment.controller; +import com.main36.pikcha.domain.comment.dto.CommentDetailResponseDto; import com.main36.pikcha.domain.comment.dto.CommentDto; import com.main36.pikcha.domain.comment.dto.CommentResponseDto; import com.main36.pikcha.domain.comment.entity.Comment; import com.main36.pikcha.domain.comment.mapper.CommentMapper; import com.main36.pikcha.domain.comment.service.CommentService; import com.main36.pikcha.domain.member.entity.Member; +import com.main36.pikcha.domain.post.entity.Post; import com.main36.pikcha.domain.post.service.PostService; import com.main36.pikcha.global.aop.LoginUser; import com.main36.pikcha.global.response.DataResponseDto; @@ -20,7 +22,10 @@ import javax.validation.Valid; import javax.validation.constraints.Positive; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RestController @Slf4j @@ -44,9 +49,9 @@ public ResponseEntity<DataResponseDto<?>> postComment(Member loginUser, commentBuilder .commentContent(commentPostDto.getCommentContent()) .member(loginUser) - .post(postService.findPost(postId)) + .post(postService.findPostNoneSetView(postId)) .build() - ); + ,commentPostDto.getParentId()); CommentResponseDto commentResponseDto = mapper.commentToCommentResponseDto(comment); @@ -73,15 +78,40 @@ public ResponseEntity<DataResponseDto<?>> getComment(@PathVariable("comment-id") return ResponseEntity.ok(new DataResponseDto<>(response)); } - @GetMapping() - public ResponseEntity<MultiResponseDto<?>> getComment(@Positive @RequestParam(required = false, defaultValue = "1") int page, + @GetMapping("/listof/{post-id}") + public ResponseEntity<MultiResponseDto<?>> getComment(@PathVariable("post-id") @Positive long postId, + @Positive @RequestParam(required = false, defaultValue = "1") int page, @Positive @RequestParam(required = false, defaultValue = "10") int size) { - Page<Comment> commentPage = commentService.findComments(page - 1, size); - List<Comment> comments = commentPage.getContent(); - - List<CommentResponseDto> commentResponseDtos = mapper.commentsToCommentResponseDtos(comments); - - return ResponseEntity.ok(new MultiResponseDto<>(commentResponseDtos, commentPage)); + Post findPost = postService.findPostNoneSetView(postId); + Page<Comment> commentPage = commentService.findComments(page - 1, size, findPost); + List<Comment> commentList = commentPage.getContent(); + for(Comment c : commentList) { + log.info(c.getCommentContent()); + } + List<CommentDetailResponseDto> result = new ArrayList<>(); + Map<Long, CommentDetailResponseDto> map = new HashMap<>(); + + commentList.stream().forEach(c-> { + CommentDetailResponseDto rDto = CommentDetailResponseDto.convertCommentToDto(c); + // map <댓글Id, responseDto> + map.put(c.getCommentId(), rDto); + // 댓글이 부모가 있다면 + if(c.getParent() != null) { + // 부모 댓글의 id의 responseDto를 조회한다음 + map.get(c.getParent().getCommentId()) + // 부모 댓글 responseDto의 자식으로 + .getChildren() + // rDto를 추가한다. + .add(rDto); + } + // 댓글이 최상위 댓글이라면 + else{ + // 그냥 result에 추가한다. + result.add(rDto); + } + }); + + return ResponseEntity.ok(new MultiResponseDto<>(result, commentPage)); } @LoginUser diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDetailResponseDto.java b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDetailResponseDto.java new file mode 100644 index 00000000..77fb73f8 --- /dev/null +++ b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDetailResponseDto.java @@ -0,0 +1,39 @@ +package com.main36.pikcha.domain.comment.dto; + +import com.main36.pikcha.domain.comment.entity.Comment; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@Builder +public class CommentDetailResponseDto { + private Long commentId; + private Long parentId; + private Long memberId; + private String username; + private String memberPicture; + private String commentContent; + private LocalDateTime createdAt; + private LocalDateTime modifiedAt; + private List<CommentDetailResponseDto> children; + + public static CommentDetailResponseDto convertCommentToDto(Comment comment){ + return CommentDetailResponseDto.builder() + .commentId(comment.getCommentId()) + .parentId(comment.getParent() == null ? null : comment.getParent().getCommentId()) + .memberId(comment.getMember().getMemberId()) + .username(comment.getMember().getUsername()) + .memberPicture(comment.getMember().getPicture()) + .commentContent(comment.getCommentContent()) + .createdAt(comment.getCreatedAt()) + .modifiedAt(comment.getModifiedAt()) + .children(new ArrayList<>()) + .build(); + } +} diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDto.java b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDto.java index baf41b04..329810ce 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDto.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentDto.java @@ -11,6 +11,7 @@ public class CommentDto { @Getter public static class Post { + private Long parentId; @NotBlank(message = "댓글 내용을 입력해주세요.") private String commentContent; } diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentResponseDto.java b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentResponseDto.java index e8bc031f..3f3b3e5a 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentResponseDto.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/dto/CommentResponseDto.java @@ -8,6 +8,7 @@ @Data public class CommentResponseDto { private Long commentId; + private Long parentId; private Long memberId; private String username; private String memberPicture; diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/entity/Comment.java b/server/src/main/java/com/main36/pikcha/domain/comment/entity/Comment.java index 4aea7e61..acd5600f 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/entity/Comment.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/entity/Comment.java @@ -34,9 +34,17 @@ public class Comment extends Auditable { private Post post; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "comment_id") + @JoinColumn(name = "parent") private Comment parent; - @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent") + public void updateParent(Comment parent){ + this.parent = parent; + } + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true) private List<Comment> children = new ArrayList<>(); + + public List<Comment> getChildren(){ + return this.children; + } } diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/mapper/CommentMapper.java b/server/src/main/java/com/main36/pikcha/domain/comment/mapper/CommentMapper.java index 3a7b3e58..cc80b550 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/mapper/CommentMapper.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/mapper/CommentMapper.java @@ -1,5 +1,6 @@ package com.main36.pikcha.domain.comment.mapper; +import com.main36.pikcha.domain.comment.dto.CommentDetailResponseDto; import com.main36.pikcha.domain.comment.dto.CommentDto; import com.main36.pikcha.domain.comment.dto.CommentResponseDto; import com.main36.pikcha.domain.comment.entity.Comment; @@ -16,7 +17,15 @@ public interface CommentMapper { @Mapping(target = "username", source = "member.username") @Mapping(target = "memberId", source = "member.memberId") @Mapping(target = "memberPicture", source = "member.picture") + @Mapping(target = "parentId", source = "parent.commentId") CommentResponseDto commentToCommentResponseDto(Comment comment); + @Mapping(target = "username", source = "member.username") + @Mapping(target = "memberId", source = "member.memberId") + @Mapping(target = "memberPicture", source = "member.picture") + @Mapping(target = "parentId", source = "parent.commentId") + CommentDetailResponseDto commentToCommentDetailResponseDto(Comment comment); + List<CommentResponseDto> commentsToCommentResponseDtos(List<Comment> comments); + List<CommentDetailResponseDto> commentsToCommentDetailResponseDtos(List<Comment> comments); } diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepository.java b/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepository.java index 9993d75c..36b40af8 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepository.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepository.java @@ -2,26 +2,9 @@ import com.main36.pikcha.domain.comment.entity.Comment; import com.main36.pikcha.domain.post.entity.Post; -import com.querydsl.jpa.impl.JPAQueryFactory; -import org.springframework.stereotype.Repository; -import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; -import static com.main36.pikcha.domain.comment.entity.QComment.comment; - -@Repository -public class CommentCustomRepository { - private JPAQueryFactory jpaQueryFactory; - - public CommentCustomRepository(JPAQueryFactory jpaQueryFactory) { - this.jpaQueryFactory = jpaQueryFactory; - } - - public List<Comment> findCommentByPost(Post post){ - return jpaQueryFactory.selectFrom(comment) - .leftJoin(comment.parent) - .fetchJoin() - .where(comment.post.postId.eq(post.getPostId())) - .orderBy(comment.parent.commentId.asc().nullsFirst(), comment.createdAt.asc()) - .fetch(); - } +public interface CommentCustomRepository { + Page<Comment> findCommentByPost(Post post, Pageable pageable); } diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepositoryImpl.java b/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepositoryImpl.java new file mode 100644 index 00000000..cfccae7d --- /dev/null +++ b/server/src/main/java/com/main36/pikcha/domain/comment/repository/CommentCustomRepositoryImpl.java @@ -0,0 +1,53 @@ +package com.main36.pikcha.domain.comment.repository; + +import com.main36.pikcha.domain.comment.entity.Comment; +import com.main36.pikcha.domain.post.entity.Post; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static com.main36.pikcha.domain.comment.entity.QComment.comment; + +@Repository +public class CommentCustomRepositoryImpl implements CommentCustomRepository{ + private JPAQueryFactory jpaQueryFactory; + + public CommentCustomRepositoryImpl(JPAQueryFactory jpaQueryFactory) { + this.jpaQueryFactory = jpaQueryFactory; + } + + @Override + public Page<Comment> findCommentByPost(Post post, Pageable pageable) { + List<Comment> commentList = findCommnetList(post, pageable); + + JPAQuery<Long> countQuery = getCount(post); + + return PageableExecutionUtils.getPage(commentList, pageable, ()-> countQuery.fetchOne()); + } + + private JPAQuery<Long> getCount(Post post) { + JPAQuery<Long> countQuery = jpaQueryFactory + .select(comment.count()) + .from(comment) + .where(comment.post.postId.eq(post.getPostId())); + + return countQuery; + } + + private List<Comment> findCommnetList(Post post, Pageable pageable){ + return jpaQueryFactory.selectFrom(comment) + .leftJoin(comment.parent) + .fetchJoin() + .where(comment.post.postId.eq(post.getPostId())) + .orderBy(comment.parent.commentId.asc().nullsFirst(), comment.createdAt.asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } +} diff --git a/server/src/main/java/com/main36/pikcha/domain/comment/service/CommentService.java b/server/src/main/java/com/main36/pikcha/domain/comment/service/CommentService.java index 73d98e67..a4c00626 100644 --- a/server/src/main/java/com/main36/pikcha/domain/comment/service/CommentService.java +++ b/server/src/main/java/com/main36/pikcha/domain/comment/service/CommentService.java @@ -1,7 +1,10 @@ package com.main36.pikcha.domain.comment.service; +import com.main36.pikcha.domain.comment.dto.CommentDto; import com.main36.pikcha.domain.comment.entity.Comment; +import com.main36.pikcha.domain.comment.repository.CommentCustomRepositoryImpl; import com.main36.pikcha.domain.comment.repository.CommentRepository; +import com.main36.pikcha.domain.post.entity.Post; import com.main36.pikcha.global.exception.BusinessLogicException; import com.main36.pikcha.global.exception.ExceptionCode; import lombok.RequiredArgsConstructor; @@ -11,20 +14,28 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + @Service @Transactional @RequiredArgsConstructor public class CommentService { private final CommentRepository commentRepository; + private final CommentCustomRepositoryImpl customRepository; - public Comment createComment(Comment comment) { + public Comment createComment(Comment comment, Long parentId) { + // 부모가 있는 댓글이라면 + if(parentId != null) { + // 부모댓글의 유효성 검증 + Comment parent = findVerifiedComment(parentId); + // 부모 아래에 추가 + comment.updateParent(parent); + } return commentRepository.save(comment); } - public Comment updateComment(Comment comment) { - return commentRepository.save(comment); - } + public Comment updateComment(Comment comment){ return commentRepository.save(comment);} @Transactional(readOnly = true) public Comment findComment(long commentId) { @@ -38,6 +49,10 @@ public Page<Comment> findComments(int page, int size) { )); } + @Transactional(readOnly = true) + public Page<Comment> findComments(int page, int size, Post post){ + return customRepository.findCommentByPost(post, PageRequest.of(page, size)); + } public void deleteComment(Comment comment) { commentRepository.delete(comment); } @@ -51,7 +66,7 @@ public Comment findVerifiedComment(long commentId) { public Comment verifyClientId(long clientId, long commentId) { Comment comment = findComment(commentId); - if (clientId == 1) return comment; + if(clientId == 1) return comment; if (!(comment.getMember().getMemberId().equals(clientId))) { throw new BusinessLogicException(ExceptionCode.USER_IS_NOT_EQUAL); } @@ -59,4 +74,5 @@ public Comment verifyClientId(long clientId, long commentId) { return comment; } + } diff --git a/server/src/main/java/com/main36/pikcha/domain/post/mapper/PostMapper.java b/server/src/main/java/com/main36/pikcha/domain/post/mapper/PostMapper.java index c6305030..2396d7ca 100644 --- a/server/src/main/java/com/main36/pikcha/domain/post/mapper/PostMapper.java +++ b/server/src/main/java/com/main36/pikcha/domain/post/mapper/PostMapper.java @@ -43,6 +43,7 @@ default PostResponseDto.Detail postToPostDetailResponseDto(Post post) { .map(comment -> { return CommentResponseDto.builder() .commentId(comment.getCommentId()) + .parentId(comment.getParent().getCommentId()) .memberId(comment.getMember().getMemberId()) .username(comment.getMember().getUsername()) .memberPicture(comment.getMember().getPicture()) diff --git a/server/src/main/resources/application-local.yml b/server/src/main/resources/application-local.yml new file mode 100644 index 00000000..c7630990 --- /dev/null +++ b/server/src/main/resources/application-local.yml @@ -0,0 +1,100 @@ +spring: + main: + allow-circular-references: true + servlet: + multipart: + max-file-size: 10MB # 2MB 제한 + max-request-size: 10MB # 2MB 제한 + batch: + job: + enabled: false # spring 실행시 배치 자동실행 끄기 + + + h2: + console: + enabled: true + path: /h2 + datasource: + url: jdbc:h2:mem:test + + sql: + init: + mode: always + data-locations: classpath*:db/h2/data.sql + + jpa: + hibernate: + ddl-auto: create + show-sql: true + properties: + hibernate: + format_sql: true + defer-datasource-initialization: true + + security: + oauth2: + client: + registration: + google: + clientId: ${G_CLIENT_ID} + clientSecret: ${G_CLIENT_SECRET} + scope: + - email + - profile + + kakao: + client-id: ${KAKAO_CLIENT_ID} + client-secret: ${KAKAO_CLIENT_SECRET} + redirect-uri: http://localhost:8080/login/oauth2/code/kakao +# redirect-uri: http://localhost:8080 + authorization-grant-type: authorization_code + client-authentication-method: POST + client-name: Kakao + scope: + - profile_nickname + - profile_image + - account_email + + provider: + kakao: + authorization-uri: https://kauth.kakao.com/oauth/authorize + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + user-name-attribute: id +logging: + level: + org: + springframework: + orm: + jpa: DEBUG + +server: + servlet: + encoding: + force-response: true +# ssl: +# key-store: classpath:keystore.p12 +# key-store-type: PKCS12 +# key-store-password: ${SSL_PASSWORD} +# port: 8080 + +mail: + address: + admin: admin@gmail.com + +jwt: + secret-key: ${JWT_SECRET_KEY} + access-token-expiration-minutes: 30 + refresh-token-expiration-minutes: 420 + +cloud: + aws: + s3: + bucket: ${AWS_BUCKET_NAME} + credentials: + accesskey: ${S3_ACCESS_KEY} + secretkey: ${S3_SECRET_KEY} + region: + static: ap-northeast-2 + stack: + auto: false \ No newline at end of file diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml new file mode 100644 index 00000000..d74c444c --- /dev/null +++ b/server/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: local