-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/#83 댓글 대댓글 crud api 구현 #97
The head ref may contain hidden characters: "Feature/#83-\uB313\uAE00_\uB300\uB313\uAE00_CRUD_API_\uAD6C\uD604"
Changes from all commits
629a086
c05f146
96eb475
328e140
164ce3e
3fcd4b5
9f878ed
4816a5a
dcd52a4
3a0042d
29b9788
fce3a79
68c52ed
d090c86
6a316eb
4596168
ee38f8a
df00603
1684186
e6a96e5
e8f03fa
404a370
98f0b0d
c29ed29
cb1fd05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.emmsale.comment.api; | ||
|
||
import com.emmsale.comment.application.CommentCommandService; | ||
import com.emmsale.comment.application.CommentQueryService; | ||
import com.emmsale.comment.application.dto.CommentAddRequest; | ||
import com.emmsale.comment.application.dto.CommentHierarchyResponse; | ||
import com.emmsale.comment.application.dto.CommentModifyRequest; | ||
import com.emmsale.comment.application.dto.CommentResponse; | ||
import com.emmsale.member.domain.Member; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PatchMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class CommentApi { | ||
|
||
private final CommentCommandService commentCommandService; | ||
private final CommentQueryService commentQueryService; | ||
|
||
@PostMapping("/comments") | ||
public CommentResponse create( | ||
@RequestBody final CommentAddRequest commentAddRequest, | ||
final Member member | ||
) { | ||
return commentCommandService.create(commentAddRequest, member); | ||
} | ||
|
||
@GetMapping("/comments") | ||
public List<CommentHierarchyResponse> findAll(@RequestParam final Long eventId) { | ||
return commentQueryService.findAllCommentsByEventId(eventId); | ||
} | ||
|
||
@DeleteMapping("/comments/{comment-id}") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
public void delete(@PathVariable("comment-id") final Long commentId, final Member member) { | ||
commentCommandService.delete(commentId, member); | ||
} | ||
|
||
@PatchMapping("/comments/{comment-id}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment 내용을 완전히 덮어씌우는 거라면 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현장 리뷰 완료요~~ |
||
public CommentResponse modify( | ||
@PathVariable("comment-id") final Long commentId, | ||
final Member member, | ||
@RequestBody final CommentModifyRequest commentModifyRequest | ||
) { | ||
return commentCommandService.modify(commentId, member, commentModifyRequest); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package com.emmsale.comment.application; | ||
|
||
import static com.emmsale.comment.exception.CommentExceptionType.FORBIDDEN_DELETE_COMMENT; | ||
import static com.emmsale.comment.exception.CommentExceptionType.FORBIDDEN_MODIFY_COMMENT; | ||
import static com.emmsale.comment.exception.CommentExceptionType.FORBIDDEN_MODIFY_DELETED_COMMENT; | ||
import static com.emmsale.comment.exception.CommentExceptionType.NOT_FOUND_COMMENT; | ||
|
||
import com.emmsale.comment.application.dto.CommentAddRequest; | ||
import com.emmsale.comment.application.dto.CommentModifyRequest; | ||
import com.emmsale.comment.application.dto.CommentResponse; | ||
import com.emmsale.comment.domain.Comment; | ||
import com.emmsale.comment.domain.CommentRepository; | ||
import com.emmsale.comment.exception.CommentException; | ||
import com.emmsale.comment.exception.CommentExceptionType; | ||
import com.emmsale.event.domain.Event; | ||
import com.emmsale.event.domain.repository.EventRepository; | ||
import com.emmsale.event.exception.EventException; | ||
import com.emmsale.event.exception.EventExceptionType; | ||
import com.emmsale.member.domain.Member; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional | ||
public class CommentCommandService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 Command가 나타내는게 Query 이외의 모든 동작인가요? 지금은 Query와 Command밖에 없어서 쉽게 예측이 되지만 더 늘어난다면 Command에 어떤 동작이 있는지 예측하기 어려울 것 같아요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현장 리뷰 완료요~~ |
||
|
||
private final CommentRepository commentRepository; | ||
private final EventRepository eventRepository; | ||
|
||
public CommentResponse create( | ||
final CommentAddRequest commentAddRequest, | ||
final Member member | ||
) { | ||
final Event savedEvent = eventRepository.findById(commentAddRequest.getEventId()) | ||
.orElseThrow(() -> new EventException(EventExceptionType.EVENT_NOT_FOUND_EXCEPTION)); | ||
final String content = commentAddRequest.getContent(); | ||
|
||
final Comment comment = commentAddRequest.optionalParentId() | ||
.map(commentId -> Comment.createChild( | ||
savedEvent, | ||
findSavedComment(commentId), | ||
member, | ||
content) | ||
) | ||
.orElseGet(() -> Comment.createRoot(savedEvent, member, content)); | ||
|
||
return CommentResponse.from(commentRepository.save(comment)); | ||
} | ||
|
||
private Comment findSavedComment(final Long commentId) { | ||
return commentRepository.findById(commentId) | ||
.orElseThrow(() -> new CommentException(NOT_FOUND_COMMENT)); | ||
} | ||
|
||
public void delete(final Long commentId, final Member loginMember) { | ||
|
||
final Comment comment = findSavedComment(commentId); | ||
|
||
validateSameWriter(loginMember, comment, FORBIDDEN_DELETE_COMMENT); | ||
|
||
comment.delete(); | ||
} | ||
|
||
private void validateSameWriter( | ||
final Member loginMember, | ||
final Comment comment, | ||
final CommentExceptionType commentExceptionType | ||
) { | ||
if (loginMember.isNotMe(comment.getMember())) { | ||
throw new CommentException(commentExceptionType); | ||
} | ||
} | ||
|
||
public CommentResponse modify( | ||
final Long commentId, | ||
final Member loginMember, | ||
final CommentModifyRequest commentModifyRequest | ||
) { | ||
|
||
final Comment comment = findSavedComment(commentId); | ||
|
||
validateAlreadyDeleted(comment); | ||
validateSameWriter(loginMember, comment, FORBIDDEN_MODIFY_COMMENT); | ||
|
||
comment.modify(commentModifyRequest.getContent()); | ||
|
||
return CommentResponse.from(comment); | ||
} | ||
|
||
private void validateAlreadyDeleted(final Comment comment) { | ||
if (comment.isDeleted()) { | ||
throw new CommentException(FORBIDDEN_MODIFY_DELETED_COMMENT); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.emmsale.comment.application; | ||
|
||
import com.emmsale.comment.application.dto.CommentHierarchyResponse; | ||
import com.emmsale.comment.domain.Comment; | ||
import com.emmsale.comment.domain.CommentRepository; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Transactional(readOnly = true) | ||
public class CommentQueryService { | ||
|
||
private final CommentRepository commentRepository; | ||
|
||
public List<CommentHierarchyResponse> findAllCommentsByEventId(final Long eventId) { | ||
|
||
final List<Comment> comments = commentRepository.findByEventId(eventId); | ||
|
||
return CommentHierarchyResponse.from(comments); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.emmsale.comment.application.dto; | ||
|
||
import java.util.Optional; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public class CommentAddRequest { | ||
|
||
private final String content; | ||
private final Long eventId; | ||
private final Long parentId; | ||
|
||
public Optional<Long> optionalParentId() { | ||
return Optional.ofNullable(parentId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.emmsale.comment.application.dto; | ||
|
||
import com.emmsale.base.BaseEntity; | ||
import com.emmsale.comment.domain.Comment; | ||
import java.util.ArrayList; | ||
import java.util.Comparator; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.stream.Collectors; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public class CommentHierarchyResponse { | ||
|
||
private final CommentResponse parentComment; | ||
private final List<CommentResponse> childComments; | ||
|
||
public static List<CommentHierarchyResponse> from(final List<Comment> comments) { | ||
|
||
final Map<Comment, List<Comment>> groupedByParent = | ||
groupingByParentAndSortedByCreatedAt(comments); | ||
|
||
final List<CommentHierarchyResponse> result = new ArrayList<>(); | ||
|
||
for (final Entry<Comment, List<Comment>> entry : groupedByParent.entrySet()) { | ||
final Comment parentComment = entry.getKey(); | ||
final List<CommentResponse> childCommentResponses = | ||
mapToCommentResponse(entry, parentComment); | ||
|
||
result.add( | ||
new CommentHierarchyResponse(CommentResponse.from(parentComment), childCommentResponses) | ||
); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static LinkedHashMap<Comment, List<Comment>> groupingByParentAndSortedByCreatedAt( | ||
final List<Comment> comments | ||
) { | ||
return comments.stream() | ||
.sorted(Comparator.comparing(BaseEntity::getCreatedAt)) | ||
.collect(Collectors.groupingBy( | ||
it -> it.getParent().orElse(it), | ||
LinkedHashMap::new, Collectors.toList()) | ||
); | ||
} | ||
|
||
private static List<CommentResponse> mapToCommentResponse( | ||
final Entry<Comment, List<Comment>> entry, | ||
final Comment parentComment | ||
) { | ||
return entry.getValue().stream() | ||
.filter(it -> isNotSameKeyAndValue(parentComment, it)) | ||
.map(CommentResponse::from) | ||
.sorted(Comparator.comparing(CommentResponse::getCreatedAt)) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private static boolean isNotSameKeyAndValue(final Comment parentComment, final Comment target) { | ||
return !target.equals(parentComment); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.emmsale.comment.application.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RequiredArgsConstructor | ||
@Getter | ||
public class CommentModifyRequest { | ||
|
||
private final String content; | ||
|
||
private CommentModifyRequest() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 이게 왜 필요한지 알 수 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현장 리뷰 완료요~~ |
||
this(null); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이건 개인적으로 궁금해서 여쭤보는건데 혹시 comment-id로 받으시는 이유가 있으신가요?
commentId로 받으면 PathVariable에 name을 생략할 수 있는데 굳이 comment-id로 받아서 name을 명시한 이유가 있는지 궁금합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현장 리뷰 완료요~~