Skip to content

Commit

Permalink
Merge pull request #82 from gutanbug/dev
Browse files Browse the repository at this point in the history
feat #80 : 댓글 및 대댓글 서비스 추가
  • Loading branch information
gutanbug authored Mar 22, 2024
2 parents b6473ba + db1295c commit 2f3c1df
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.renew.sw.mentoring.domain.comment;

import com.renew.sw.mentoring.domain.comment.exception.CommentNotFoundException;
import com.renew.sw.mentoring.domain.comment.model.CommentStatus;
import com.renew.sw.mentoring.domain.comment.model.dto.response.SummarizedCommentDto;
import com.renew.sw.mentoring.domain.comment.model.dto.response.SummarizedReplyDto;
import com.renew.sw.mentoring.domain.comment.model.entity.Comment;
import com.renew.sw.mentoring.domain.comment.repository.CommentRepository;
import com.renew.sw.mentoring.domain.post.exception.PostNotFoundException;
import com.renew.sw.mentoring.domain.post.model.entity.Post;
import com.renew.sw.mentoring.domain.post.repository.PostRepository;
import com.renew.sw.mentoring.domain.user.exception.UserNotFoundException;
import com.renew.sw.mentoring.domain.user.model.entity.User;
import com.renew.sw.mentoring.domain.user.repository.UserRepository;
import com.renew.sw.mentoring.global.error.exception.NotGrantedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class CommentService {

private final PostRepository postRepository;
private final UserRepository userRepository;
private final CommentRepository commentRepository;

@Transactional(readOnly = true)
public List<SummarizedCommentDto> list(Long postId) {
List<Comment> comments = commentRepository.findByPostIdExceptReply(postId);
return comments.stream().map(e -> {
List<Comment> replies = commentRepository.findAllReplies(e.getId());
return new SummarizedCommentDto(e, replies.stream().map(SummarizedReplyDto::new).toList());
}).collect(Collectors.toList());
}

@Transactional
public Long create(Long postId, Long userId, String content) {
Post post = postRepository.findById(postId).orElseThrow(PostNotFoundException::new);
User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);

Comment comment = Comment.builder()
.user(user)
.post(post)
.content(content)
.commentStatus(CommentStatus.ACTIVE)
.build();

comment.changePost(post);
comment = commentRepository.save(comment);
return comment.getId();
}

@Transactional
public void edit(Long commentId, Long userId, String content) {
Comment comment = commentRepository.findById(commentId).orElseThrow(CommentNotFoundException::new);

if(!comment.getUser().getId().equals(userId)) {
throw new NotGrantedException();
}
if(comment.getCommentStatus() == CommentStatus.ACTIVE
|| comment.getCommentStatus() == CommentStatus.EDITED) {
comment.updateContent(content);
}
}

@Transactional
public void delete(Long commentId, Long userId) {
Comment comment = commentRepository.findById(commentId).orElseThrow(CommentNotFoundException::new);

if(comment.getUser().getUserRole().isAdmin()) {
comment.markedAsDeleted(true);
} else if(comment.getUser().getId().equals(userId)) {
comment.markedAsDeleted(false);
} else {
throw new NotGrantedException();
}
}

@Transactional
public Long createReply(Long commentId, Long userId, String content) {
Comment parentComment = commentRepository.findById(commentId).orElseThrow(CommentNotFoundException::new);
Long postId = parentComment.getPost().getId();
Post post = postRepository.findById(postId).orElseThrow(PostNotFoundException::new);
User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);

Comment comment = Comment.builder()
.user(user)
.post(post)
.content(content)
.commentStatus(CommentStatus.ACTIVE)
.parentCommentId(parentComment.getId())
.build();

comment.changePost(post);
comment = commentRepository.save(comment);
parentComment.addChildComment(comment);
return comment.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.renew.sw.mentoring.domain.comment.exception;

import com.renew.sw.mentoring.global.error.exception.LocalizedMessageException;
import org.springframework.http.HttpStatus;

public class CommentNotFoundException extends LocalizedMessageException {

public CommentNotFoundException() {
super(HttpStatus.NOT_FOUND, "notfound.comment");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.renew.sw.mentoring.domain.comment.model.dto.request;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RequestCreateCommentDto {

@NotBlank
private String content;

public RequestCreateCommentDto(String content) {
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.renew.sw.mentoring.domain.comment.model.dto.response;

import com.renew.sw.mentoring.domain.comment.model.CommentStatus;
import com.renew.sw.mentoring.domain.comment.model.entity.Comment;
import lombok.Getter;

import java.util.List;
import java.util.Objects;

@Getter
public class SummarizedCommentDto {

private final Long id;

private final String author;

private final String content;

private final List<SummarizedReplyDto> replies;

public SummarizedCommentDto(Comment comment, List<SummarizedReplyDto> replies) {
this.id = comment.getId();
if(checkDeletedComment(comment)) {
this.author = null;
this.content = "삭제된 댓글입니다.";
} else {
this.author = comment.getUser().getNickname();
this.content = comment.getContent();
}
this.replies = Objects.requireNonNullElseGet(replies, List::of);
}

private boolean checkDeletedComment(Comment comment) {
return comment.getCommentStatus() == CommentStatus.DELETED || comment.getCommentStatus() == CommentStatus.DELETED_BY_ADMIN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.renew.sw.mentoring.domain.comment.model.dto.response;

import com.renew.sw.mentoring.domain.comment.model.CommentStatus;
import com.renew.sw.mentoring.domain.comment.model.entity.Comment;
import lombok.Getter;

@Getter
public class SummarizedReplyDto {

private final Long id;

private final String author;

private final String content;

public SummarizedReplyDto(Comment comment) {
this.id = comment.getId();
if(checkDeletedReply(comment)) {
this.author = null;
this.content = "삭제된 댓글입니다.";
} else {
this.author = comment.getUser().getNickname();
this.content = comment.getContent();
}
}

private boolean checkDeletedReply(Comment comment) {
return comment.getCommentStatus() == CommentStatus.DELETED || comment.getCommentStatus() == CommentStatus.DELETED_BY_ADMIN;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import com.renew.sw.mentoring.domain.comment.model.CommentStatus;
import com.renew.sw.mentoring.domain.post.model.entity.Post;
import com.renew.sw.mentoring.domain.post.model.entity.type.MissionBoard;
import com.renew.sw.mentoring.domain.user.model.entity.User;
import com.renew.sw.mentoring.global.base.BaseEntity;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import lombok.*;

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

import static javax.persistence.FetchType.LAZY;
import static lombok.AccessLevel.PROTECTED;

@Entity
Expand All @@ -30,6 +33,15 @@ public class Comment extends BaseEntity {
@JoinColumn(name = "user_id")
private User user;

@Setter
@Column(updatable = false)
private Long parentCommentId;

@ToString.Exclude
@OrderBy("createdAt ASC")
@OneToMany(mappedBy = "parentCommentId", cascade = CascadeType.ALL, fetch = LAZY)
private List<Comment> childComments = new ArrayList<>();

@Lob
private String content;

Expand All @@ -40,10 +52,40 @@ public class Comment extends BaseEntity {
private Comment(@NotNull Post post,
@NotNull User user,
String content,
CommentStatus commentStatus) {
CommentStatus commentStatus,
Long parentCommentId) {
this.post = post;
this.user = user;
this.content = content;
this.commentStatus = commentStatus;
this.parentCommentId = parentCommentId;
}


public void addChildComment(Comment child) {
child.setParentCommentId(this.getId());
this.getChildComments().add(child);
}

public void changePost(Post post) {
if (this.post != null) {
this.post.getComments().remove(this);
}

this.post = post;
this.post.getComments().add(this);
}

public void updateContent(String content) {
this.content = content;
this.commentStatus = CommentStatus.EDITED;
}

public void markedAsDeleted(boolean isAdmin) {
if(isAdmin) {
this.commentStatus = CommentStatus.DELETED_BY_ADMIN;
} else {
this.commentStatus = CommentStatus.DELETED;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.renew.sw.mentoring.domain.comment.repository;

import com.renew.sw.mentoring.domain.comment.model.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface CommentRepository extends JpaRepository<Comment, Long> {
@Query("select c from Comment c " +
"join fetch c.post " +
"where c.post.id = :postId and c.parentCommentId is null " +
"order by c.createdAt asc ")
List<Comment> findByPostIdExceptReply(@Param("postId") Long postId);

@Query("select c from Comment c " +
"join fetch c.post " +
"where c.parentCommentId=:id order by c.createdAt asc ")
List<Comment> findAllReplies(Long id);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.renew.sw.mentoring.domain.post.controller;

import com.renew.sw.mentoring.domain.comment.CommentService;
import com.renew.sw.mentoring.domain.comment.model.dto.request.RequestCreateCommentDto;
import com.renew.sw.mentoring.domain.comment.model.dto.response.SummarizedCommentDto;
import com.renew.sw.mentoring.domain.post.model.entity.dto.list.SummarizedMissionBoardDto;
import com.renew.sw.mentoring.domain.post.model.entity.dto.request.RequestCreateMissionBoardDto;
import com.renew.sw.mentoring.domain.post.model.entity.dto.request.RequestUpdateMissionBoardDto;
Expand All @@ -19,6 +22,7 @@
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@Tag(name = "미션 인증 게시판", description = "미션 인증 관련 api")
@RestController
Expand All @@ -27,6 +31,7 @@
public class MissionBoardController {

private final MissionBoardService missionBoardService;
private final CommentService commentService;

/**
* 미션 인증 글 등록
Expand Down Expand Up @@ -113,4 +118,61 @@ public void delete(AppAuthentication auth,
@PathVariable Long id) {
missionBoardService.delete(auth.getUserId(), id, auth.getUserRole());
}

/**
* 모든 댓글을 조회합니다.
*/
@GetMapping("/comments/{postId}")
public List<SummarizedCommentDto> listComments(@PathVariable Long postId) {
return commentService.list(postId);
}

/**
* 게시글에 댓글을 생성합니다.
**/
@PostMapping("/comment/{postId}")
@UserAuth
public ResponseIdDto createComment(AppAuthentication auth,
@PathVariable Long postId,
@Valid @RequestBody RequestCreateCommentDto dto) {
Long result = commentService.create(postId, auth.getUserId(), dto.getContent());
return new ResponseIdDto(result);
}

/**
* 댓글을 수정합니다.
* <p>대댓글도 수정할 수 있습니다.</p>
*/
@PatchMapping("/comment/{commentId}")
@UserAuth
public void editComment(AppAuthentication auth,
@PathVariable Long commentId,
@Valid @RequestBody RequestCreateCommentDto dto) {
commentService.edit(commentId, auth.getUserId(), dto.getContent());
}

/**
* 댓글을 삭제합니다.
* <p>대댓글도 삭제할 수 있습니다.</p>
*/
@DeleteMapping("/comment/{commentId}")
@UserAuth
public void deleteComment(AppAuthentication auth,
@PathVariable Long commentId) {
commentService.delete(commentId, auth.getUserId());
}

/**
* 대댓글을 생성합니다.
*
* @param commentId 댓글 ID
*/
@PostMapping("/reply/{commentId}")
@UserAuth
public ResponseIdDto createReply(AppAuthentication auth,
@PathVariable Long commentId,
@Valid @RequestBody RequestCreateCommentDto dto) {
Long result = commentService.createReply(commentId, auth.getUserId(), dto.getContent());
return new ResponseIdDto(result);
}
}
Loading

0 comments on commit 2f3c1df

Please sign in to comment.