Skip to content

Commit

Permalink
Merge pull request #162 from Team-Capple/feat/#159/questionAnswerPagi…
Browse files Browse the repository at this point in the history
…nation

[FEAT] 질문, 답변 목록 페이지네이션 구현
  • Loading branch information
jaewonLeeKOR authored Sep 17, 2024
2 parents 9b208ba + 1fc5ccb commit ce2049e
Show file tree
Hide file tree
Showing 17 changed files with 217 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
import com.server.capple.config.security.AuthMember;
import com.server.capple.domain.answer.dto.AnswerRequest;
import com.server.capple.domain.answer.dto.AnswerResponse;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerInfo;
import com.server.capple.domain.answer.service.AnswerService;
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.Parameters;
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.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.*;

@Tag(name = "답변 API", description = "답변 API입니다.")
Expand All @@ -26,62 +27,68 @@ public class AnswerController {
private final AnswerService answerService;

@Operation(summary = "답변 생성 API", description = " 답변 생성 API 입니다." +
"pathvariable 으로 questionId를 주세요.")
"pathvariable 으로 questionId를 주세요.")
@PostMapping("/question/{questionId}")
public BaseResponse<AnswerResponse.AnswerId> createAnswer(@AuthMember Member member, @PathVariable(value = "questionId") Long questionId,
@RequestBody @Valid AnswerRequest request) {
return BaseResponse.onSuccess(answerService.createAnswer(member, questionId, request));
}

@Operation(summary = "질문에 대한 답변 조회 API", description = "특정 질문에 대한 답변리스트를 조회하는 API입니다."
+ "pathVariable으로 questionId를 주세요."
+ "조회할 질문의 개수를 param으로 입력해주세요.")
@Parameters(value = {
@Parameter(name = "keyword", description = "검색"),
@Parameter(name = "size", description = "조회할 질문의 개수를 입력하세요."),
})
+ "pathVariable으로 questionId를 주세요.")
@GetMapping("/question/{questionId}")
public BaseResponse<AnswerResponse.AnswerList> getAnswerList(
@AuthMember Member member,
@PathVariable(value = "questionId") Long questionId,
@RequestParam(name = "keyword", required = false) String keyword,
@PageableDefault
@Parameter(hidden = true) Pageable pageable) {
Pageable tempPageable = PageRequest.of(pageable.getPageNumber(), 250);
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, keyword, tempPageable));
public BaseResponse<SliceResponse<AnswerInfo>> getAnswerList(
@AuthMember Member member,
@Parameter(description = "질문 식별자")
@PathVariable(value = "questionId") Long questionId,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getAnswerList(member.getId(), questionId, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "답변 수정 API", description = " 답변 수정 API 입니다." +
"pathvariable 으로 answerId를 주세요.")
"pathvariable 으로 answerId를 주세요.")
@PatchMapping("/{answerId}")
public BaseResponse<AnswerResponse.AnswerId> updateAnswer(@AuthMember Member member, @PathVariable(value = "answerId") Long answerId,
@RequestBody @Valid AnswerRequest request) {
return BaseResponse.onSuccess(answerService.updateAnswer(member, answerId, request));
}

@Operation(summary = "답변 삭제 API", description = " 답변 삭제 API 입니다." +
"pathvariable 으로 answerId를 주세요.")
"pathvariable 으로 answerId를 주세요.")
@DeleteMapping("/{answerId}")
public BaseResponse<AnswerResponse.AnswerId> deleteAnswer(@AuthMember Member member, @PathVariable(value = "answerId") Long answerId) {
return BaseResponse.onSuccess(answerService.deleteAnswer(member, answerId));
}

@Operation(summary = "답변 좋아요/취소 API", description = " 답변 좋아요/취소 API 입니다." +
"pathvariable 으로 answerId를 주세요.")
"pathvariable 으로 answerId를 주세요.")
@PostMapping("/{answerId}/heart")
public BaseResponse<AnswerResponse.AnswerLike> toggleAnswerHeart(@AuthMember Member member, @PathVariable(value = "answerId") Long answerId) {
return BaseResponse.onSuccess(answerService.toggleAnswerHeart(member, answerId));
}

@Operation(summary = "작성한 답변 조회 API", description = " 작성한 답변 조회 API 입니다.")
@GetMapping
public BaseResponse<AnswerResponse.MemberAnswerList> getMemberAnswer(@AuthMember Member member) {
return BaseResponse.onSuccess(answerService.getMemberAnswer(member));
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberAnswer(
@AuthMember Member member,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberAnswer(member, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}

@Operation(summary = "좋아한 답변 조회 API", description = " 좋아한 답변 조회 API 입니다.")
@GetMapping("/heart")
public BaseResponse<AnswerResponse.MemberAnswerList> getMemberHeartAnswer(@AuthMember Member member) {
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member));
public BaseResponse<SliceResponse<MemberAnswerInfo>> getMemberHeartAnswer(
@AuthMember Member member,
@Parameter(description = "조회할 페이지 번호<br>0부터 시작")
@RequestParam(defaultValue = "0", required = false) Integer pageNumber,
@Parameter(description = "조회할 페이지 크기")
@RequestParam(defaultValue = "1000", required = false) Integer pageSize) {
return BaseResponse.onSuccess(answerService.getMemberHeartAnswer(member, PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.DESC, "createdAt"))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.server.capple.domain.answer.dao;

import com.server.capple.domain.answer.entity.Answer;

public class AnswerRDBDao {
public interface AnswerInfoInterface {
public Answer getAnswer();
public Boolean getIsReported();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.server.capple.domain.answer.dto;

import java.util.List;

import lombok.*;

public class AnswerResponse {
Expand All @@ -26,14 +24,6 @@ public static class AnswerInfo {
private String writeAt;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class AnswerList {
private List<AnswerInfo> answerInfos;
}

@Getter
@AllArgsConstructor
public static class AnswerLike {
Expand All @@ -55,10 +45,4 @@ public static class MemberAnswerInfo {
private String writeAt;
private Boolean isLiked;
}

@Getter
@AllArgsConstructor
public static class MemberAnswerList {
private List<MemberAnswerInfo> memberAnswerInfos;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import com.server.capple.domain.answer.dto.AnswerRequest;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerList;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerList;
import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.domain.question.entity.Question;
Expand All @@ -22,12 +20,6 @@ public Answer toAnswerEntity(AnswerRequest request, Member member, Question ques
.build();
}

public AnswerList toAnswerList(List<AnswerInfo> answerInfoList) {
return AnswerList.builder()
.answerInfos(answerInfoList)
.build();
}

public AnswerInfo toAnswerInfo(Answer answer, Long memberId, Boolean isReported, Boolean isLiked, Boolean isMine) {
return AnswerInfo.builder()
.answerId(answer.getId())
Expand Down Expand Up @@ -55,9 +47,4 @@ public MemberAnswerInfo toMemberAnswerInfo(Answer answer, int heartCount, Boolea
.isLiked(isLiked)
.build();
}

public MemberAnswerList toMemberAnswerList(List<MemberAnswerInfo> memberAnswerInfos) {
return new MemberAnswerList(memberAnswerInfos);
}

}
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
package com.server.capple.domain.answer.repository;

import com.server.capple.domain.answer.dao.AnswerRDBDao.AnswerInfoInterface;
import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.domain.question.entity.Question;
import io.lettuce.core.dynamic.annotation.Param;
import java.util.List;
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.Optional;
import java.util.Set;

public interface AnswerRepository extends JpaRepository<Answer, Long> {
Optional<Answer> findById(Long answerId);

boolean existsByQuestionAndMember(Question question, Member member);
Slice<Answer> findByIdIn(Set<Long> answerIds, Pageable pageable);

@Query("SELECT a FROM Answer a WHERE a.question.id = :questionId ORDER BY a.createdAt DESC")
Optional<List<Answer>> findByQuestion(
@Param("questionId") Long questionId,
Pageable pageable);
boolean existsByQuestionAndMember(Question question, Member member);

@Query("SELECT a FROM Answer a WHERE a.question.id = :questionId AND a.content LIKE %:keyword%")
Optional<List<Answer>> findByQuestionAndKeyword(
@Param("questionId") Long questionId,
@Param("keyword") String keyword,
Pageable pageable);
@Query("SELECT a AS answer, (r IS NOT NULL) AS isReported " +
"FROM Answer a " +
"LEFT JOIN " +
"Report r ON r.answer = a " +
"WHERE a.question.id = :questionId")
Slice<AnswerInfoInterface> findByQuestion(
@Param("questionId") Long questionId,
Pageable pageable);

@Query("SELECT a FROM Answer a WHERE a.member = :member and a.deletedAt is null ORDER BY a.createdAt DESC")
Optional<List<Answer>> findByMember(@Param("member") Member member);
@Query("SELECT a FROM Answer a WHERE a.member = :member and a.deletedAt is null")
Slice<Answer> findByMember(@Param("member") Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import com.server.capple.domain.answer.dto.AnswerRequest;
import com.server.capple.domain.answer.dto.AnswerResponse;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerList;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerList;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerInfo;
import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.member.entity.Member;
import com.server.capple.global.common.SliceResponse;
import org.springframework.data.domain.Pageable;


Expand All @@ -20,9 +21,9 @@ public interface AnswerService {

AnswerResponse.AnswerLike toggleAnswerHeart(Member loginMember, Long answerId);

AnswerList getAnswerList(Long memberId, Long questionId, String keyword, Pageable pageable);
SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Pageable pageable);

MemberAnswerList getMemberAnswer(Member member);
SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Pageable pageable);

MemberAnswerList getMemberHeartAnswer(Member member);
SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.server.capple.domain.answer.service;

import com.server.capple.domain.answer.dao.AnswerRDBDao.AnswerInfoInterface;
import com.server.capple.domain.answer.dto.AnswerRequest;
import com.server.capple.domain.answer.dto.AnswerResponse;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerInfo;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerLike;
import com.server.capple.domain.answer.dto.AnswerResponse.AnswerList;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerList;
import com.server.capple.domain.answer.dto.AnswerResponse.MemberAnswerInfo;
import com.server.capple.domain.answer.entity.Answer;
import com.server.capple.domain.answer.mapper.AnswerMapper;
import com.server.capple.domain.answer.repository.AnswerHeartRedisRepository;
Expand All @@ -14,15 +15,15 @@
import com.server.capple.domain.question.entity.Question;
import com.server.capple.domain.question.service.QuestionService;
import com.server.capple.domain.report.repository.ReportRepository;
import com.server.capple.global.common.SliceResponse;
import com.server.capple.global.exception.RestApiException;
import com.server.capple.global.exception.errorCode.AnswerErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
Expand Down Expand Up @@ -91,45 +92,43 @@ public AnswerLike toggleAnswerHeart(Member loginMember, Long answerId) {
}

@Override
public AnswerList getAnswerList(Long memberId, Long questionId, String keyword, Pageable pageable) {

if (keyword == null) {
return answerMapper.toAnswerList(
answerRepository.findByQuestion(questionId, pageable).orElseThrow(()
-> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND))
.stream()
.map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer), answerHeartRedisRepository.isMemberLikedAnswer(memberId, answer.getId()), answer.getMember().getId().equals(memberId)))
.toList());
} else {
return answerMapper.toAnswerList(
answerRepository.findByQuestionAndKeyword(questionId, keyword, pageable).orElseThrow(()
-> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND))
.stream()
.map(answer -> answerMapper.toAnswerInfo(answer, memberId, reportRepository.existsReportByAnswer(answer), answerHeartRedisRepository.isMemberLikedAnswer(memberId, answer.getId()), answer.getMember().getId().equals(memberId)))
.toList());
}

public SliceResponse<AnswerInfo> getAnswerList(Long memberId, Long questionId, Pageable pageable) {
Slice<AnswerInfoInterface> answerInfoSliceInterface = answerRepository.findByQuestion(questionId, pageable);
return SliceResponse.toSliceResponse(answerInfoSliceInterface, answerInfoSliceInterface.getContent().stream().map(
answerInfoDto -> answerMapper.toAnswerInfo(
answerInfoDto.getAnswer(),
memberId,
answerInfoDto.getIsReported(),
answerHeartRedisRepository.isMemberLikedAnswer(memberId, answerInfoDto.getAnswer().getId()),
answerInfoDto.getAnswer().getMember().getId().equals(memberId)
)
).toList());
}

// 유저가 작성한 답변 조회
@Override
public MemberAnswerList getMemberAnswer(Member member) {
List<Answer> answers = answerRepository.findByMember(member).orElse(null);
return answerMapper.toMemberAnswerList(
answers.stream()
.map(answer -> answerMapper.toMemberAnswerInfo(answer, answerHeartRedisRepository.getAnswerHeartsCount(answer.getId()), answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answer.getId())))
.toList()
public SliceResponse<MemberAnswerInfo> getMemberAnswer(Member member, Pageable pageable) {
Slice<Answer> answerSlice = answerRepository.findByMember(member, pageable);
return SliceResponse.toSliceResponse(
answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
answer,
answerHeartRedisRepository.getAnswerHeartsCount(answer.getId()),
answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answer.getId())
)).toList()
);
}

// 유저가 좋아한 답변 조회
@Override
public MemberAnswerList getMemberHeartAnswer(Member member) {
return answerMapper.toMemberAnswerList(
answerHeartRedisRepository.getMemberHeartsAnswer(member.getId())
.stream()
.map(answerId -> answerMapper.toMemberAnswerInfo(findAnswer((answerId)), answerHeartRedisRepository.getAnswerHeartsCount(answerId), answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answerId)))
.toList()
public SliceResponse<MemberAnswerInfo> getMemberHeartAnswer(Member member, Pageable pageable) {
Slice<Answer> answerSlice = answerRepository.findByIdIn(answerHeartRedisRepository.getMemberHeartsAnswer(member.getId()), pageable);
return SliceResponse.toSliceResponse(answerSlice, answerSlice.getContent().stream()
.map(answer -> answerMapper.toMemberAnswerInfo(
answer,
answerHeartRedisRepository.getAnswerHeartsCount(answer.getId()),
answerHeartRedisRepository.isMemberLikedAnswer(member.getId(), answer.getId())
)).toList()
);
}

Expand All @@ -144,7 +143,7 @@ private void checkPermission(Member loginMember, Answer answer) {
@Override
public Answer findAnswer(Long answerId) {
return answerRepository.findById(answerId).orElseThrow(
() -> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND)
() -> new RestApiException(AnswerErrorCode.ANSWER_NOT_FOUND)
);
}
}
Loading

0 comments on commit ce2049e

Please sign in to comment.