HighlightReactionService.java

package qwerty.chaekit.service.highlight.reaction;

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.reaction.HighlightReactionRequest;
import qwerty.chaekit.dto.highlight.reaction.HighlightReactionResponse;
import qwerty.chaekit.global.enums.ErrorCode;
import qwerty.chaekit.global.exception.BadRequestException;
import qwerty.chaekit.global.exception.ForbiddenException;
import qwerty.chaekit.global.exception.NotFoundException;
import qwerty.chaekit.global.security.resolver.UserToken;
import qwerty.chaekit.service.group.ActivityPolicy;
import qwerty.chaekit.service.highlight.HighlightPolicy;
import qwerty.chaekit.service.util.EntityFinder;

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

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class HighlightReactionService {
    //private final HighlightRepository highlightRepository;
    private final HighlightCommentRepository commentRepository;
    private final HighlightReactionRepository reactionRepository;
    private final UserProfileRepository userRepository;
    private final ActivityPolicy activityPolicy;
    private final HighlightPolicy highlightPolicy;
    private final EntityFinder entityFinder;

    public HighlightReactionResponse addReaction(UserToken userToken, Long highlightId, HighlightReactionRequest request) {
        Long userId = userToken.userId();

        UserProfile author = entityFinder.findUser(userId);
        Highlight highlight = entityFinder.findHighlight(highlightId);

        activityPolicy.assertJoined(author, highlight.getActivity());
        
        if (!highlight.isPublic()) {
            throw new ForbiddenException(ErrorCode.HIGHLIGHT_NOT_PUBLIC);
        }

        HighlightComment comment = null;
        Optional<HighlightReaction> highlightReaction;

        if (request.commentId() != null) {
            comment = commentRepository.findById(request.commentId())
                    .orElseThrow(() -> new NotFoundException(ErrorCode.COMMENT_NOT_FOUND));

            if (!comment.getHighlight().getId().equals(highlightId)) {
                throw new ForbiddenException(ErrorCode.COMMENT_PARENT_MISMATCH);
            }

            highlightReaction = reactionRepository.findByAuthorIdAndCommentIdAndReactionType(
                    userId, comment.getId(), request.reactionType());
        } else {
            highlightReaction = reactionRepository.findByAuthorIdAndHighlightIdAndReactionTypeAndCommentIdIsNull(
                    userId, highlightId, request.reactionType());
        }
        
        if (highlightReaction.isPresent()) {
            throw new BadRequestException(ErrorCode.REACTION_ALREADY_EXISTS);
        }

        HighlightReaction reaction = HighlightReaction.builder()
                .author(userRepository.getReferenceById(userId))
                .highlight(highlight)
                .comment(comment)
                .reactionType(request.reactionType())
                .build();

        HighlightReaction savedReaction = reactionRepository.save(reaction);

        return HighlightReactionResponse.of(savedReaction);
    }

    public List<HighlightReactionResponse> getHighlightReactions(UserToken userToken, Long highlightId) {
        Highlight highlight = entityFinder.findHighlight(highlightId);
        if(highlight.isPublic()) {
            activityPolicy.assertJoined(userToken.userId(), highlight.getActivity().getId());
        } else {
            highlightPolicy.assertUpdatable(userToken.userId(), highlight);
        }

        List<HighlightReaction> reactions = reactionRepository.findByHighlightIdAndCommentIdIsNull(highlightId);

        return reactions.stream()
                .map(HighlightReactionResponse::of)
                .collect(Collectors.toList());
    }

    public void deleteReaction(UserToken userToken, Long reactionId) {
        Long userId = userToken.userId();

        HighlightReaction reaction = reactionRepository.findById(reactionId)
                .orElseThrow(() -> new NotFoundException(ErrorCode.REACTION_NOT_FOUND));

        if (!reaction.getAuthor().getId().equals(userId)) {
            throw new ForbiddenException(ErrorCode.NOT_REACTION_AUTHOR);
        }

        reactionRepository.delete(reaction);
    }
}