Skip to content

Commit

Permalink
Merge pull request #113 from Team-WSS/feat/#68
Browse files Browse the repository at this point in the history
[FEAT] 소소피드 댓글 CRUD API 구현
  • Loading branch information
ChaeAg authored Aug 7, 2024
2 parents 97a5545 + 68a6e94 commit 0a3e838
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.websoso.WSSServer.domain.User;
import org.websoso.WSSServer.dto.comment.CommentCreateRequest;
import org.websoso.WSSServer.dto.comment.CommentUpdateRequest;
import org.websoso.WSSServer.dto.comment.CommentsGetResponse;
import org.websoso.WSSServer.dto.feed.FeedCreateRequest;
import org.websoso.WSSServer.dto.feed.FeedGetResponse;
import org.websoso.WSSServer.dto.feed.FeedUpdateRequest;
Expand Down Expand Up @@ -123,5 +126,52 @@ public ResponseEntity<FeedsGetResponse> getFeeds(Principal principal,
.status(OK)
.body(feedService.getFeeds(user, category, lastFeedId, size));
}


@PostMapping("/{feedId}/comments")
public ResponseEntity<Void> createComment(Principal principal,
@PathVariable("feedId") Long feedId,
@Valid @RequestBody CommentCreateRequest request) {
User user = userService.getUserOrException(Long.valueOf(principal.getName()));
feedService.createComment(user, feedId, request);

return ResponseEntity
.status(NO_CONTENT)
.build();
}

@PutMapping("/{feedId}/comments/{commentId}")
public ResponseEntity<Void> updateComment(Principal principal,
@PathVariable("feedId") Long feedId,
@PathVariable("commentId") Long commentId,
@Valid @RequestBody CommentUpdateRequest request) {
User user = userService.getUserOrException(Long.valueOf(principal.getName()));
feedService.updateComment(user, feedId, commentId, request);

return ResponseEntity
.status(NO_CONTENT)
.build();
}

@DeleteMapping("/{feedId}/comments/{commentId}")
public ResponseEntity<Void> deleteComment(Principal principal,
@PathVariable("feedId") Long feedId,
@PathVariable("commentId") Long commentId) {
User user = userService.getUserOrException(Long.valueOf(principal.getName()));
feedService.deleteComment(user, feedId, commentId);

return ResponseEntity
.status(NO_CONTENT)
.build();
}

@GetMapping("/{feedId}/comments")
public ResponseEntity<CommentsGetResponse> getComments(Principal principal,
@PathVariable("feedId") Long feedId) {
User user = userService.getUserOrException(Long.valueOf(principal.getName()));

return ResponseEntity
.status(OK)
.body(feedService.getComments(user, feedId));
}

}
36 changes: 36 additions & 0 deletions src/main/java/org/websoso/WSSServer/domain/Comment.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.websoso.WSSServer.domain;

import static jakarta.persistence.GenerationType.IDENTITY;
import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_BELONG_TO_FEED;
import static org.websoso.WSSServer.exception.error.CustomUserError.INVALID_AUTHORIZED;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -9,13 +11,19 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicInsert;
import org.websoso.WSSServer.domain.common.Action;
import org.websoso.WSSServer.domain.common.BaseEntity;
import org.websoso.WSSServer.exception.exception.CustomCommentException;
import org.websoso.WSSServer.exception.exception.CustomUserException;

@Entity
@Getter
@DynamicInsert
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Comment extends BaseEntity {

Expand All @@ -37,4 +45,32 @@ public class Comment extends BaseEntity {
@JoinColumn(name = "feed_id", nullable = false)
private Feed feed;

public static Comment create(Long userId, Feed feed, String commentContent) {
return new Comment(commentContent, userId, feed);
}

public void validateUserAuthorization(Long userId, Action action) {
if (!Objects.equals(this.userId, userId)) {
throw new CustomUserException(INVALID_AUTHORIZED,
"only the author can " + action.getLabel() + " the comment");
}
}

public void updateContent(String commentContent) {
this.commentContent = commentContent;
}

public void validateFeedAssociation(Feed feed) {
if (this.feed != feed) {
throw new CustomCommentException(COMMENT_NOT_BELONG_TO_FEED,
"the comment does not belong to the specified feed");
}
}

private Comment(String commentContent, Long userId, Feed feed) {
this.commentContent = commentContent;
this.userId = userId;
this.feed = feed;
}

}
4 changes: 2 additions & 2 deletions src/main/java/org/websoso/WSSServer/domain/Novel.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.websoso.WSSServer.domain;

import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
Expand Down Expand Up @@ -38,7 +38,7 @@ public class Novel {
@Column(columnDefinition = "Boolean default false", nullable = false)
private Boolean isCompleted;

@OneToMany(mappedBy = "novel", fetch = LAZY)
@OneToMany(mappedBy = "novel", fetch = FetchType.LAZY)
private List<UserNovel> userNovels = new ArrayList<>();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.websoso.WSSServer.dto.comment;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CommentCreateRequest(
@NotBlank(message = "댓글 내용은 null 이거나, 공백일 수 없습니다.")
@Size(max = 100, message = "댓글 내용은 100자를 초과할 수 없습니다.")
String commentContent
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.websoso.WSSServer.dto.comment;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDate;
import org.websoso.WSSServer.domain.Comment;
import org.websoso.WSSServer.dto.user.UserBasicInfo;

public record CommentGetResponse(
Long userId,
String nickname,
String avatarImage,
Long commentId,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "M월 d일", timezone = "Asia/Seoul")
LocalDate createdDate,
String commentContent,
Boolean isModified,
Boolean isMyComment
) {
public static CommentGetResponse of(UserBasicInfo userBasicInfo, Comment comment, Boolean isMyComment) {
return new CommentGetResponse(
userBasicInfo.userId(),
userBasicInfo.nickname(),
userBasicInfo.avatarImage(),
comment.getCommentId(),
comment.getCreatedDate().toLocalDate(),
comment.getCommentContent(),
!comment.getCreatedDate().equals(comment.getModifiedDate()),
isMyComment
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.websoso.WSSServer.dto.comment;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CommentUpdateRequest(
@NotBlank(message = "댓글 내용은 null 이거나, 공백일 수 없습니다.")
@Size(max = 100, message = "댓글 내용은 100자를 초과할 수 없습니다.")
String commentContent
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.websoso.WSSServer.dto.comment;

import java.util.List;

public record CommentsGetResponse(
Integer commentsCount,
List<CommentGetResponse> comments
) {
public static CommentsGetResponse of(Integer commentsCount, List<CommentGetResponse> comments) {
return new CommentsGetResponse(
commentsCount,
comments
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.websoso.WSSServer.exception.error;

import static org.springframework.http.HttpStatus.NOT_FOUND;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.websoso.WSSServer.exception.common.ICustomError;

@Getter
@AllArgsConstructor
public enum CustomCommentError implements ICustomError {

COMMENT_NOT_FOUND("COMMENT-001", "해당 ID를 가진 댓글을 찾을 수 없습니다.", NOT_FOUND),
COMMENT_NOT_BELONG_TO_FEED("COMMENT-002", "댓글이 지정된 피드에 속하지 않습니다.", NOT_FOUND);

private final String code;
private final String description;
private final HttpStatus statusCode;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.websoso.WSSServer.exception.exception;

import lombok.Getter;
import org.websoso.WSSServer.exception.common.AbstractCustomException;
import org.websoso.WSSServer.exception.error.CustomCommentError;

@Getter
public class CustomCommentException extends AbstractCustomException {

public CustomCommentException(CustomCommentError customCommentError, String message) {
super(customCommentError, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ public ResponseEntity<ErrorResult> CustomExceptionHandler(AbstractCustomExceptio
.status(iCustomError.getStatusCode())
.body(new ErrorResult(iCustomError.getCode(), iCustomError.getDescription()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public interface BlockRepository extends JpaRepository<Block, Long>, BlockCustom
boolean existsByBlockingIdAndBlockedId(Long blockingId, Long blockedId);

List<Block> findByBlockingId(Long blockingId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.websoso.WSSServer.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.websoso.WSSServer.domain.Comment;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
}
87 changes: 87 additions & 0 deletions src/main/java/org/websoso/WSSServer/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.websoso.WSSServer.service;

import static org.websoso.WSSServer.domain.common.Action.DELETE;
import static org.websoso.WSSServer.domain.common.Action.UPDATE;
import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_FOUND;

import java.util.AbstractMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.websoso.WSSServer.domain.Comment;
import org.websoso.WSSServer.domain.Feed;
import org.websoso.WSSServer.domain.User;
import org.websoso.WSSServer.dto.comment.CommentGetResponse;
import org.websoso.WSSServer.dto.comment.CommentsGetResponse;
import org.websoso.WSSServer.dto.user.UserBasicInfo;
import org.websoso.WSSServer.exception.exception.CustomCommentException;
import org.websoso.WSSServer.repository.CommentRepository;

@Service
@RequiredArgsConstructor
@Transactional
public class CommentService {

private final CommentRepository commentRepository;
private final UserService userService;
private final AvatarService avatarService;
private final BlockService blockService;

public void createComment(Long userId, Feed feed, String commentContent) {
commentRepository.save(Comment.create(userId, feed, commentContent));
}

public void updateComment(Long userId, Feed feed, Long commentId, String commentContent) {
Comment comment = getCommentOrException(commentId);
comment.validateFeedAssociation(feed);
comment.validateUserAuthorization(userId, UPDATE);
comment.updateContent(commentContent);
}

public void deleteComment(Long userId, Feed feed, Long commentId) {
Comment comment = getCommentOrException(commentId);
comment.validateFeedAssociation(feed);
comment.validateUserAuthorization(userId, DELETE);
commentRepository.delete(comment);
}

@Transactional(readOnly = true)
public CommentsGetResponse getComments(User user, Feed feed) {
List<Comment> comments = feed.getComments();

List<CommentGetResponse> responses = comments
.stream()
.map(comment -> new AbstractMap.SimpleEntry<>(
comment, userService.getUserOrException(comment.getUserId())
))
.filter(entry -> !entry.getKey().getIsHidden() && !isBlocked(entry.getValue(), user))
.map(entry -> CommentGetResponse.of(
getUserBasicInfo(entry.getValue()),
entry.getKey(),
isUserCommentOwner(entry.getValue(), user)))
.toList();

return CommentsGetResponse.of(comments.size(), responses);
}

private Comment getCommentOrException(Long commentId) {
return commentRepository.findById(commentId).orElseThrow(() ->
new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found"));
}

private UserBasicInfo getUserBasicInfo(User user) {
return user.getUserBasicInfo(
avatarService.getAvatarOrException(user.getAvatarId()).getAvatarImage()
);
}

private Boolean isUserCommentOwner(User createdUser, User user) {
return createdUser.equals(user);
}

private Boolean isBlocked(User createdFeedUser, User user) {
return blockService.isBlockedRelationship(user.getUserId(), createdFeedUser.getUserId());
}

}
Loading

0 comments on commit 0a3e838

Please sign in to comment.