HighlightCommentService.java
package qwerty.chaekit.service.highlight.comment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import qwerty.chaekit.domain.highlight.Highlight;
import qwerty.chaekit.domain.highlight.comment.HighlightComment;
import qwerty.chaekit.domain.highlight.reaction.HighlightReaction;
import qwerty.chaekit.domain.highlight.repository.HighlightRepository;
import qwerty.chaekit.domain.highlight.comment.repository.HighlightCommentRepository;
import qwerty.chaekit.domain.highlight.reaction.repository.HighlightReactionRepository;
import qwerty.chaekit.domain.member.user.UserProfile;
import qwerty.chaekit.domain.member.user.UserProfileRepository;
import qwerty.chaekit.dto.highlight.comment.HighlightCommentRequest;
import qwerty.chaekit.dto.highlight.comment.HighlightCommentResponse;
import qwerty.chaekit.global.enums.ErrorCode;
import qwerty.chaekit.global.exception.ForbiddenException;
import qwerty.chaekit.global.exception.NotFoundException;
import qwerty.chaekit.global.security.resolver.UserToken;
import qwerty.chaekit.mapper.HighlightCommentMapper;
import qwerty.chaekit.service.group.ActivityPolicy;
import qwerty.chaekit.service.notification.NotificationService;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class HighlightCommentService {
private final HighlightRepository highlightRepository;
private final HighlightCommentRepository commentRepository;
private final HighlightReactionRepository reactionRepository;
private final UserProfileRepository userRepository;
private final NotificationService notificationService;
private final ActivityPolicy activityPolicy;
private final HighlightCommentMapper highlightCommentMapper;
public HighlightCommentResponse createComment(UserToken userToken, Long highlightId, HighlightCommentRequest request) {
Long userId = userToken.userId();
UserProfile commentAuthor = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
Highlight highlight = highlightRepository.findById(highlightId)
.orElseThrow(() -> new NotFoundException(ErrorCode.HIGHLIGHT_NOT_FOUND));
activityPolicy.assertJoined(commentAuthor, highlight.getActivity());
if (!highlight.isPublic()) {
throw new ForbiddenException(ErrorCode.HIGHLIGHT_NOT_PUBLIC);
}
HighlightComment parent;
if (request.parentId() != null) {
parent = commentRepository.findById(request.parentId())
.orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND));
if (!parent.getHighlight().getId().equals(highlightId)) {
throw new ForbiddenException(ErrorCode.COMMENT_PARENT_MISMATCH);
}
} else {
parent = null;
}
HighlightComment comment = HighlightComment.builder()
.author(commentAuthor)
.highlight(highlight)
.content(request.content())
.parent(parent)
.build();
HighlightComment savedComment = commentRepository.save(comment);
if (parent != null) {
parent.addReply(savedComment);
}
if (parent != null) {
if (!parent.getAuthor().getId().equals(userId)) {
notificationService.createHighlightCommentReplyNotification(parent.getAuthor(), commentAuthor, parent);
}
if (!highlight.getAuthor().getId().equals(userId) && !highlight.getAuthor().getId().equals(parent.getAuthor().getId())) {
notificationService.createHighlightCommentReplyNotification(highlight.getAuthor(), commentAuthor, parent);
}
} else {
if (!highlight.getAuthor().getId().equals(userId)) {
notificationService.createHighlightCommentNotification(highlight.getAuthor(), commentAuthor, highlight);
}
}
return highlightCommentMapper.toResponse(savedComment);
}
@Transactional(readOnly = true)
public List<HighlightCommentResponse> getComments(UserToken userToken, Long highlightId) {
UserProfile author = userRepository.findById(userToken.userId())
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
Highlight highlight = highlightRepository.findById(highlightId)
.orElseThrow(() -> new NotFoundException(ErrorCode.HIGHLIGHT_NOT_FOUND));
if (!highlight.isPublic()) {
throw new ForbiddenException(ErrorCode.HIGHLIGHT_NOT_PUBLIC);
}
activityPolicy.assertJoined(author, highlight.getActivity());
List<HighlightComment> rootComments = commentRepository.findRootCommentsByHighlightId(highlightId);
Set<Long> allCommentIds = new HashSet<>();
for (HighlightComment rootComment : rootComments) {
allCommentIds.add(rootComment.getId());
for (HighlightComment reply : rootComment.getReplies()) {
allCommentIds.add(reply.getId());
}
}
final Map<Long, List<HighlightReaction>> reactionsByCommentId;
if (!allCommentIds.isEmpty()) {
List<HighlightReaction> allReactions = reactionRepository.findByCommentIdIn(new ArrayList<>(allCommentIds));
reactionsByCommentId = allReactions.stream()
.collect(Collectors.groupingBy(reaction -> reaction.getComment().getId()));
} else {
reactionsByCommentId = Collections.emptyMap();
}
return rootComments.stream()
.map(comment -> highlightCommentMapper.toResponse(comment, reactionsByCommentId))
.collect(Collectors.toList());
}
public HighlightCommentResponse updateComment(UserToken userToken, Long commentId, HighlightCommentRequest request) {
Long userId = userToken.userId();
HighlightComment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND));
if (!comment.getAuthor().getId().equals(userId)) {
throw new ForbiddenException(ErrorCode.COMMENT_NOT_YOURS);
}
comment.updateContent(request.content());
return highlightCommentMapper.toResponse(commentRepository.save(comment));
}
public void deleteComment(UserToken userToken, Long commentId) {
Long userId = userToken.userId();
HighlightComment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND));
if (!comment.getAuthor().getId().equals(userId)) {
throw new ForbiddenException(ErrorCode.COMMENT_NOT_YOURS);
}
List<HighlightReaction> reactions = reactionRepository.findByCommentId(commentId);
for (HighlightReaction reaction : reactions) {
reactionRepository.delete(reaction);
}
if (!comment.getReplies().isEmpty()) {
List<Long> replyIds = comment.getReplies().stream()
.map(HighlightComment::getId)
.collect(Collectors.toList());
List<HighlightReaction> replyReactions = reactionRepository.findByCommentIdIn(replyIds);
for (HighlightReaction reaction : replyReactions) {
reactionRepository.delete(reaction);
}
}
commentRepository.delete(comment);
}
}