Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/community' into dev-check
Browse files Browse the repository at this point in the history
  • Loading branch information
versatile0010 committed Nov 13, 2023
2 parents b307c0a + 993630c commit 29c61b9
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest
Article article = articleRepository.findById(articleId)
.orElseThrow(NotFoundArticleException::new);
CommentEntity savedComment = commentRepository.save(
new CommentEntity(request.getContent(), article)
new CommentEntity(request.getContent(), article, member)
);

articleParticipantRepository.save(new ArticleParticipant(article, member));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.numberone.backend.domain.comment.controller;

import com.numberone.backend.domain.comment.dto.request.CreateChildCommentRequest;
import com.numberone.backend.domain.comment.dto.response.CreateChildCommentResponse;
import com.numberone.backend.domain.comment.dto.response.GetCommentDto;
import com.numberone.backend.domain.comment.service.CommentService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
import java.util.List;

@Slf4j
@RequestMapping("api/comments")
@RequiredArgsConstructor
@RestController
public class CommentController {

private final CommentService commentService;

@Operation(summary = "대댓글 작성 API", description = """
comment-id 는 부모 댓글의 id 입니다.
article-id 와 comment-id 는 모두 path variable 으로 보내주세요!
""")
@PostMapping("{article-id}/{comment-id}")
public ResponseEntity<CreateChildCommentResponse> createChildComment(
@PathVariable("article-id") Long articleId,
@PathVariable("comment-id") Long commentId,
@RequestBody @Valid CreateChildCommentRequest request) {
CreateChildCommentResponse response = commentService.createChildComment(articleId, commentId, request);
return ResponseEntity.created(
URI.create(String.format("/comments/%s/%s", articleId, commentId)))
.body(response);
}

@Operation(summary = "해당 게시물에 달린 댓긂을 모두 조회하는 API 입니다.", description = """
해당 게시물에 달린 댓글을 계층 형태로 조회합니다.
- Long commentId : 댓글 아이디
- Long parentCommentId : 부모 댓글의 야이디 (nullable)
- List<GetCommentDto> childComments = new ArrayList<>() : 대댓글 리스트
- Integer likeCount : 해당 댓글의 좋아요 개수
- LocalDateTime createdAt : 해당 댓글의 생성 시각
- LocalDateTime modifiedAt : 해당 댓글의 마지막 수정 시각
- String content : 해당 댓글의 내용
- Long authorId : 해당 댓글의 작성자 아이디
- String authorNickName : 해당 댓글의 작성자 닉네임
- String authorProfileImageUrl : 해당 댓글 작성자의 프로필 사진 url
댓글 작성자가 추후에 탈퇴하는 경우를 고려했는데,
authorNickName 이 "알수없는 사용자" 로 변경되어 내려갑니다..!
""")
@GetMapping("{article-id}")
public ResponseEntity<List<GetCommentDto>> getCommentsByArticle(@PathVariable("article-id") Long articleId){
List<GetCommentDto> response = commentService.getCommentsByArticle(articleId);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.numberone.backend.domain.comment.dto.request;

import jakarta.validation.constraints.NotNull;
import lombok.*;

@ToString
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class CreateChildCommentRequest {
@NotNull
private String content;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.numberone.backend.domain.comment.dto.response;

import com.numberone.backend.domain.comment.entity.CommentEntity;
import lombok.*;

import java.time.LocalDateTime;

@ToString
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class CreateChildCommentResponse {

private LocalDateTime createdAt;
private Long commentId;

public static CreateChildCommentResponse of (CommentEntity comment){
return CreateChildCommentResponse.builder()
.createdAt(comment.getCreatedAt())
.commentId(comment.getId())
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ public static CreateCommentResponse of (CommentEntity comment){
.commentId(comment.getId())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.numberone.backend.domain.comment.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.numberone.backend.domain.article.entity.Article;
import com.numberone.backend.domain.comment.entity.CommentEntity;
import com.numberone.backend.domain.member.entity.Member;
import com.querydsl.core.annotations.QueryProjection;
import lombok.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@ToString
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class GetCommentDto {

private Long commentId;
private Long parentCommentId;
private List<GetCommentDto> childComments = new ArrayList<>();
private Integer likeCount;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss a", timezone = "Asia/Seoul")
private LocalDateTime createdAt;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss a", timezone = "Asia/Seoul")
private LocalDateTime modifiedAt;
private String content;
private Long authorId;
private String authorNickName;
private String authorProfileImageUrl;


@QueryProjection
public GetCommentDto(CommentEntity comment){
if(!Objects.isNull(comment.getParent())){
this.parentCommentId = comment.getParent().getId();
}
this.commentId = comment.getId();
this.createdAt = comment.getCreatedAt();
this.modifiedAt = comment.getModifiedAt();
this.content = comment.getContent();
this.authorId = comment.getAuthorId();
this.likeCount = comment.getLikeCount();
}

public void updateCommentInfo(Optional<Member> author){
author.ifPresentOrElse(
a -> {
this.authorNickName = a.getNickName();
this.authorProfileImageUrl = a.getProfileImageUrl();
},
() -> {
this.authorNickName = "알 수 없는 사용자";
}
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import com.numberone.backend.config.basetime.BaseTimeEntity;
import com.numberone.backend.domain.article.entity.Article;
import com.numberone.backend.domain.member.entity.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

import java.util.ArrayList;
import java.util.List;

@Comment("동네생활 댓글 정보")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
Expand All @@ -32,10 +37,25 @@ public class CommentEntity extends BaseTimeEntity {
@Comment("댓글 내용")
private String content;

public CommentEntity(String content, Article article){
@Comment("작성자 아이디")
private Long authorId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private CommentEntity parent;

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<CommentEntity> childs = new ArrayList<>();

public CommentEntity(String content, Article article, Member author){
this.depth = 0;
this.content = content;
this.article = article;
this.authorId = author.getId();
}

public void updateParent(CommentEntity parent){
this.parent = parent;
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.numberone.backend.domain.comment.repository;

import com.numberone.backend.domain.comment.entity.CommentEntity;
import com.numberone.backend.domain.comment.repository.custom.CommentRepositoryCustom;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<CommentEntity, Long> {
public interface CommentRepository extends JpaRepository<CommentEntity, Long>, CommentRepositoryCustom {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.numberone.backend.domain.comment.repository.custom;

import com.numberone.backend.domain.comment.dto.response.GetCommentDto;

import java.util.List;

public interface CommentRepositoryCustom {
public List<GetCommentDto> findAllByArticle(Long articleId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.numberone.backend.domain.comment.repository.custom;

import com.numberone.backend.domain.article.entity.QArticle;
import com.numberone.backend.domain.comment.dto.response.GetCommentDto;
import com.numberone.backend.domain.comment.dto.response.QGetCommentDto;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;

import java.util.List;

import static com.numberone.backend.domain.article.entity.QArticle.article;
import static com.numberone.backend.domain.comment.entity.QCommentEntity.commentEntity;

public class CommentRepositoryCustomImpl implements CommentRepositoryCustom {
private final JPAQueryFactory queryFactory;

public CommentRepositoryCustomImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}

@Override
public List<GetCommentDto> findAllByArticle(Long articleId) {
return queryFactory.select(new QGetCommentDto(commentEntity))
.from(commentEntity)
.leftJoin(commentEntity.parent)
.fetchJoin()
.where(commentEntity.article.id.eq(articleId))
.orderBy(
commentEntity.parent.id.asc().nullsFirst(),
commentEntity.createdAt.asc()
)
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.numberone.backend.domain.comment.service;

import com.numberone.backend.domain.article.entity.Article;
import com.numberone.backend.domain.article.repository.ArticleRepository;
import com.numberone.backend.domain.articleparticipant.entity.ArticleParticipant;
import com.numberone.backend.domain.articleparticipant.repository.ArticleParticipantRepository;
import com.numberone.backend.domain.comment.dto.request.CreateChildCommentRequest;
import com.numberone.backend.domain.comment.dto.response.CreateChildCommentResponse;
import com.numberone.backend.domain.comment.dto.response.GetCommentDto;
import com.numberone.backend.domain.comment.entity.CommentEntity;
import com.numberone.backend.domain.comment.repository.CommentRepository;
import com.numberone.backend.domain.member.entity.Member;
import com.numberone.backend.domain.member.repository.MemberRepository;
import com.numberone.backend.domain.token.util.SecurityContextProvider;
import com.numberone.backend.exception.notfound.NotFoundArticleException;
import com.numberone.backend.exception.notfound.NotFoundCommentException;
import com.numberone.backend.exception.notfound.NotFoundMemberException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class CommentService {

private final CommentRepository commentRepository;
private final ArticleRepository articleRepository;
private final ArticleParticipantRepository articleParticipantRepository;
private final MemberRepository memberRepository;

@Transactional
public CreateChildCommentResponse createChildComment(
Long articleId,
Long parentCommentId,
CreateChildCommentRequest request) {

String principal = SecurityContextProvider.getAuthenticatedUserEmail();
Member member = memberRepository.findByEmail(principal)
.orElseThrow(NotFoundMemberException::new);
Article article = articleRepository.findById(articleId)
.orElseThrow(NotFoundArticleException::new);
CommentEntity parentComment = commentRepository.findById(parentCommentId)
.orElseThrow(NotFoundCommentException::new);

CommentEntity childComment = commentRepository.save(new CommentEntity(request.getContent(), article, member));
childComment.updateParent(parentComment);

articleParticipantRepository.save(new ArticleParticipant(article, member));

return CreateChildCommentResponse.of(childComment);
}

public List<GetCommentDto> getCommentsByArticle(Long articleId) {
Article article = articleRepository.findById(articleId)
.orElseThrow(NotFoundArticleException::new);
List<GetCommentDto> comments = commentRepository.findAllByArticle(article.getId());

// 계층 구조로 변환 (추후 리팩토링 필요)
List<GetCommentDto> result = new ArrayList<>();
Map<Long, GetCommentDto> map = new HashMap<>();
comments.forEach(
comment -> {
CommentEntity commentEntity = commentRepository.findById(comment.getCommentId())
.orElseThrow(NotFoundCommentException::new);
Optional<Member> author = memberRepository.findById(commentEntity.getAuthorId());
comment.updateCommentInfo(author);

map.put(comment.getCommentId(), comment);

if (comment.getParentCommentId() != null){
GetCommentDto parentComment = map.get(comment.getParentCommentId());
List<GetCommentDto> childComments = parentComment.getChildComments();
childComments.add(comment);
} else {
result.add(comment);
}
}
);

return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public enum CustomExceptionContext implements ExceptionContext {

// article image 관련 예외
NOT_FOUND_ARTICLE_IMAGE("해당 이미지를 찾을 수 없습니다.", 9000),

// comment 관련 예외
NOT_FOUND_COMMENT("해당 댓글을 찾을 수 없습니다.", 10000),
;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.numberone.backend.exception.notfound;

import static com.numberone.backend.exception.context.CustomExceptionContext.NOT_FOUND_COMMENT;

public class NotFoundCommentException extends NotFoundException {
public NotFoundCommentException(){
super(NOT_FOUND_COMMENT);
}
}

0 comments on commit 29c61b9

Please sign in to comment.