Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ public interface AnswerRepository extends JpaRepository<Answer, Long> {
boolean existsByQuestionAndMember(Question question, Member member);

@Query("SELECT a AS answer, " +
"(r IS NOT NULL) AS isReported, " +
"a.member.id AS writerId, " +
"a.member.profileImage AS writerProfileImage, " +
"a.member.nickname AS writerNickname," +
"a.member.academyGeneration AS writerAcademyGeneration " +
"FROM Answer a " +
"LEFT JOIN " +
"Report r ON r.answer = a " +
"WHERE (a.id < :lastIndex OR :lastIndex IS NULL) AND a.question.id = :questionId")
Slice<AnswerInfoInterface> findByQuestion(@Param("questionId") Long questionId, Long lastIndex, Pageable pageable);
"(r IS NOT NULL) AS isReported, " +
"a.member.id AS writerId, " +
"a.member.profileImage AS writerProfileImage, " +
"a.member.nickname AS writerNickname, " +
"a.member.academyGeneration AS writerAcademyGeneration, " +
"CASE WHEN ah.id IS NOT NULL THEN TRUE ELSE FALSE END AS isLiked " +
"FROM Answer a " +
"LEFT JOIN Report r ON r.answer = a " +
"LEFT JOIN AnswerHeart ah ON ah.answer = a AND ah.member = :member AND ah.isLiked = TRUE " +
"WHERE (a.id < :lastIndex OR :lastIndex IS NULL) AND a.question.id = :questionId")
Slice<AnswerInfoInterface> findByQuestion(@Param("questionId") Long questionId, @Param("lastIndex") Long lastIndex, @Param("member") Member member, Pageable pageable);

@Query("""
SELECT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.server.capple.domain.answer.repository.AnswerRepository;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.domain.member.service.MemberService;
import com.server.capple.domain.member.service.MemberServiceImpl;
import com.server.capple.domain.notifiaction.service.NotificationService;
import com.server.capple.domain.question.entity.Question;
import com.server.capple.domain.question.service.QuestionService;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class AnswerServiceImpl implements AnswerService {
private final AnswerHeartRepository answerHeartRepository;
private final AnswerHeartMapper answerHeartMapper;
private final AnswerConcurrentService answerConcurrentService;
private final MemberServiceImpl memberServiceImpl;

@Transactional
@Override
Expand Down Expand Up @@ -105,8 +107,6 @@ public AnswerResponse.AnswerId deleteAnswer(Member loginMember, Long answerId) {
return new AnswerResponse.AnswerId(answerId);
}


//답변 좋아요 / 취소
@Override
@Transactional
public AnswerLike toggleAnswerHeart(Member loginMember, Long answerId) {
Expand All @@ -133,7 +133,8 @@ public AnswerLike toggleAnswerHeart(Member loginMember, Long answerId) {
@Override
public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Long lastIndex, Pageable pageable) {

Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, lastIndex, pageable);
Member member = memberService.findMember(memberId);
Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, lastIndex, member, pageable);
lastIndex = getLastIndexFromAnswerInfoInterface(answerInfoSliceInterface);
return SliceResponse.toSliceResponse(answerInfoSliceInterface, answerInfoSliceInterface.getContent().stream().map(
answerInfoDto -> answerMapper.toAnswerInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
import com.server.capple.domain.answerComment.service.AnswerCommentService;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.global.common.BaseResponse;
import com.server.capple.global.common.SliceResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

@Tag(name = "답변 댓글 API", description = "답변 댓글 API입니다.")
Expand Down Expand Up @@ -40,14 +44,22 @@ public BaseResponse<AnswerCommentId> updateAnswerComment(@AuthMember Member memb

@Operation(summary = "답변 댓글 좋아요/취소 토글 API", description = " 답변 댓글 좋아요/취소 토글 API 입니다. pathvariable 으로 commentId를 주세요.")
@PatchMapping("/heart/{commentId}")
public BaseResponse<AnswerCommentHeart> heartAnswerComment(@AuthMember Member member, @PathVariable(value = "commentId") Long commentId) {
return BaseResponse.onSuccess(answerCommentService.heartAnswerComment(member, commentId));
public BaseResponse<AnswerCommentLike> heartAnswerComment(@AuthMember Member member, @PathVariable(value = "commentId") Long commentId) {
return BaseResponse.onSuccess(answerCommentService.toggleAnswerCommentHeart(member, commentId));
}

@Operation(summary = "답변에 대한 댓글 조회 API", description = " 답변에 대한 댓글 조회 API 입니다. pathvariable 으로 answerId를 주세요.")
@Operation(summary = "답변에 대한 댓글 조회 API", description = " 답변에 대한 댓글 조회 API 입니다. pathvariable 으로 answerId를 주세요."
+ "pathVariable으로 answerId 주세요.<BR>**첫 번째 조회 시 threshold를 비워 보내고, 이후 조회 시 앞선 조회의 반환값으로 받은 threshold를 보내주세요.**")
@GetMapping("/{answerId}")
public BaseResponse<AnswerCommentInfos> getAnswerCommentInfos(@PathVariable(value = "answerId") Long answerId) {
return BaseResponse.onSuccess(answerCommentService.getAnswerCommentInfos(answerId));
public BaseResponse<SliceResponse<AnswerCommentInfo>> getAnswerCommentInfos(
@AuthMember Member member,
@Parameter(description = "답변 식별자")
@PathVariable(value = "answerId") Long answerId,
@Parameter(description = "이전 조회의 마지막 index")
@RequestParam(required = false, name = "threshold") Long lastIndex,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerCommentService.getAnswerCommentInfos(answerId, member.getId(), lastIndex, PageRequest.of(0, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.server.capple.domain.answerComment.dao;

import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.member.entity.Member;

import java.time.LocalDateTime;

public class AnswerCommentRDBDao {
public interface AnswerCommentInfoInterface {
public AnswerComment getAnswerComment();
public Member getWriter();
public String getContent();
public LocalDateTime getCreatedAt();
public Boolean getIsLiked();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.server.capple.domain.answerComment.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -16,20 +17,22 @@ public static class AnswerCommentId {

@Getter
@AllArgsConstructor
public static class AnswerCommentHeart {
public static class AnswerCommentLike {
private Long answerCommentId;
private Boolean isLiked;
}

@Getter
@AllArgsConstructor
@Builder
public static class AnswerCommentInfo {
private Long answerCommentId;
private Long writerId;
private String content;
private Integer heartCount;
private Boolean isLiked;
private Boolean isMine;
private LocalDateTime createdAt;

}

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.server.capple.domain.answerComment.entity;

import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.global.common.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Getter
@Builder
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@SQLRestriction("deleted_at is null")
public class AnswerCommentHeart extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "answerComment_id", nullable = false)
private AnswerComment answerComment;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member;

private boolean isLiked;

public boolean toggleHeart() {
this.isLiked = !this.isLiked;
return isLiked;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.server.capple.domain.answerComment.mapper;

import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.answer.entity.AnswerHeart;
import com.server.capple.domain.answerComment.dto.AnswerCommentResponse;
import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.answerComment.entity.AnswerCommentHeart;
import com.server.capple.domain.member.entity.Member;
import org.springframework.stereotype.Component;

@Component
public class AnswerCommentHeartMapper {
public AnswerCommentHeart toAnswerCommentHeart(AnswerComment answerComment, Member member) {
return AnswerCommentHeart.builder()
.answerComment(answerComment)
.member(member)
.isLiked(false)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.server.capple.domain.answerComment.mapper;

import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.answerComment.dao.AnswerCommentRDBDao;
import com.server.capple.domain.answerComment.dto.AnswerCommentResponse.*;
import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.member.entity.Member;
Expand All @@ -17,14 +18,15 @@ public AnswerComment toAnswerCommentEntity(Member member, Answer answer, String
.build();
}

public AnswerCommentInfo toAnswerCommentInfo(AnswerComment comment) {
public AnswerCommentInfo toAnswerCommentInfo(AnswerCommentRDBDao.AnswerCommentInfoInterface answerCommentInfoDto, Long memberId) {
return AnswerCommentInfo.builder()
.answerCommentId(comment.getId())
.writerId(comment.getMember().getId())
.content(comment.getContent())
.heartCount(comment.getHeartCount())
.createdAt(comment.getCreatedAt())
.answerCommentId(answerCommentInfoDto.getAnswerComment().getId())
.writerId(answerCommentInfoDto.getWriter().getId())
.content(answerCommentInfoDto.getAnswerComment().getContent())
.heartCount(answerCommentInfoDto.getAnswerComment().getHeartCount())
.isMine(answerCommentInfoDto.getWriter().getId().equals(memberId))
.isLiked(answerCommentInfoDto.getIsLiked() == null ? false : answerCommentInfoDto.getIsLiked())
.createdAt(answerCommentInfoDto.getAnswerComment().getCreatedAt())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.server.capple.domain.answerComment.repository;

import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.answer.entity.AnswerHeart;
import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.answerComment.entity.AnswerCommentHeart;
import com.server.capple.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface AnswerCommentHeartRepository extends JpaRepository<AnswerCommentHeart, Long> {

Optional<AnswerCommentHeart> findByMemberAndAnswerComment(Member member, AnswerComment answerComment);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
package com.server.capple.domain.answerComment.repository;

import com.server.capple.domain.answerComment.dao.AnswerCommentRDBDao;
import com.server.capple.domain.answerComment.dto.AnswerCommentDBResponse.AnswerCommentAuthorNAnswerNQuestionInfo;
import com.server.capple.domain.answerComment.dto.AnswerCommentResponse;
import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.member.entity.Member;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Optional;

public interface AnswerCommentRepository extends JpaRepository<AnswerComment, Long> {
@Query("SELECT a FROM AnswerComment a WHERE a.answer.id = :answerId ORDER BY a.createdAt")
List<AnswerComment> findAnswerCommentByAnswerId(Long answerId);

@Query("SELECT ac AS answerComment, " +
"ac.member AS writer, " +
"ac.content AS content, " +
"ac.heartCount AS heartCount, " +
"ac.createdAt AS createdAt, " +
"CASE WHEN h.id IS NOT NULL THEN TRUE ELSE FALSE END AS isLiked " +
"FROM AnswerComment ac " +
"LEFT JOIN AnswerCommentHeart h ON h.answerComment = ac AND h.member = :member AND h.isLiked = TRUE " +
"WHERE ac.answer.id = :answerId AND (:lastIndex IS NULL OR ac.id < :lastIndex) " +
"ORDER BY ac.createdAt DESC")
Slice<AnswerCommentRDBDao.AnswerCommentInfoInterface> findAnswerCommentByAnswerId(
@Param("answerId") Long answerId,
@Param("member") Member member,
@Param("lastIndex") Long lastIndex,
Pageable pageable);

@Query("""
SELECT
ac answerComment,
Expand All @@ -29,4 +50,10 @@ public interface AnswerCommentRepository extends JpaRepository<AnswerComment, Lo
LIMIT 1
""")
Optional<AnswerCommentAuthorNAnswerNQuestionInfo> findAnswerCommentInfo(AnswerComment answerComment);

@Query("SELECT COUNT(ac) FROM AnswerComment ac WHERE ac.answer.id = :answerId")
Integer getAnswerCommentCountByAnswerId(Long answerId);

@Query("SELECT COUNT(ac.id) FROM AnswerComment ac WHERE ac.member = :member")
Integer getAnswerCommentCountByMember(Member member);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.server.capple.domain.answerComment.service;

import com.server.capple.domain.answer.repository.AnswerRepository;
import com.server.capple.domain.answerComment.repository.AnswerCommentRepository;
import com.server.capple.domain.member.entity.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
@RequiredArgsConstructor
public class AnswerCommentCountService {
private final AnswerCommentRepository answerCommentRepository;

@Cacheable(value = "answerComment", key = "#answerId", cacheManager = "oneDayExpireCacheManager")
public Integer getAnswerCommentCount(Long answerId) {
return answerCommentRepository.getAnswerCommentCountByAnswerId(answerId);
}

@Async
@CachePut(value = "answerComment", key = "#answerId", cacheManager = "oneDayExpireCacheManager")
public CompletableFuture<Integer> updateAnswerCommentCount(Long answerId) {
return CompletableFuture.completedFuture(answerCommentRepository.getAnswerCommentCountByAnswerId(answerId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import com.server.capple.domain.answerComment.dto.AnswerCommentResponse.*;
import com.server.capple.domain.answerComment.entity.AnswerComment;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.global.common.SliceResponse;
import org.springframework.data.domain.Pageable;

public interface AnswerCommentService {

AnswerComment findAnswerComment(Long answerCommentId);
AnswerCommentId createAnswerComment(Member member, Long answerId, AnswerCommentRequest request);
AnswerCommentId deleteAnswerComment(Member member, Long commentId);
AnswerCommentId updateAnswerComment(Member member, Long commentId, AnswerCommentRequest request);
AnswerCommentHeart heartAnswerComment(Member member, Long commentId);
AnswerCommentInfos getAnswerCommentInfos(Long answerId);
AnswerCommentLike toggleAnswerCommentHeart(Member member, Long commentId);
SliceResponse<AnswerCommentInfo> getAnswerCommentInfos(Long answerId, Long memberId, Long lastIndex, Pageable pageable);

}
Loading
Loading