Skip to content
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

[feat #42] 스크랩 목록 조회 API #50

Merged
merged 12 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -13,6 +13,7 @@
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.request.UpdateMemberProfileRequest;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.BookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.service.MemberService;
Expand Down Expand Up @@ -74,4 +75,16 @@ public ResponseEntity<PageResponse<AnsweredQuestionPostsByMemberResponse>> getAn
return ResponseEntity.ok(response);
}

@Operation(summary = "스크랩 질문 전체 조회 API", description = "스크랩한 질문을 전체 조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@GetMapping("/post-interaction/bookmarks")
dudxo marked this conversation as resolved.
Show resolved Hide resolved
public ResponseEntity<PageResponse<BookmarksByMemberResponse>> getBookmarksByMember(
@AuthenticationPrincipal Member member,
Pageable pageable) {
PageResponse<BookmarksByMemberResponse> response =
memberService.getBookmarksByMember(member, pageable);

return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.dnd.gongmuin.member.dto.response;

import com.dnd.gongmuin.post_interaction.domain.InteractionCount;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
import com.querydsl.core.annotations.QueryProjection;

public record BookmarksByMemberResponse(
Long questionPostId,
String title,
String content,
String jobGroup,
int reward,
String updatedAt,
boolean isChosen,
int savedTotalCount,
int recommendTotalCount
) {
@QueryProjection
public BookmarksByMemberResponse(
QuestionPost questionPost,
InteractionCount savedCount,
InteractionCount recommendCount
) {
this(
questionPost.getId(),
questionPost.getTitle(),
questionPost.getContent(),
questionPost.getJobGroup().getLabel(),
questionPost.getReward(),
questionPost.getUpdatedAt().toString(),
questionPost.getIsChosen(),
extractTotalCount(savedCount),
extractTotalCount(recommendCount)
);
}

private static int extractTotalCount(InteractionCount interactionCount) {
return interactionCount != null ? interactionCount.getCount() : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public enum MemberErrorCode implements ErrorCode {
NOT_ENOUGH_CREDIT("보유한 크레딧이 부족합니다.", "MEMBER_004"),
UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005"),
QUESTION_POSTS_BY_MEMBER_FAILED("작성한 게시글 목록을 찾는 도중 실패했습니다.", "MEMBER_006"),
ANSWERED_QUESTION_POSTS_BY_MEMBER_FAILED("댓글 단 게시글 목록을 찾는 도중 실패했습니다.", "MEMBER_007");
ANSWERED_QUESTION_POSTS_BY_MEMBER_FAILED("댓글 단 게시글 목록을 찾는 도중 실패했습니다.", "MEMBER_007"),
BOOKMARK_QUESTION_POSTS_BY_MEMBER_FAILED("스크랩 한 게시글 목록을 찾는 도중 실패했씁니다.", "MEMBER_008");
dudxo marked this conversation as resolved.
Show resolved Hide resolved

private final String message;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.BookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;

public interface MemberCustom {
Slice<QuestionPostsByMemberResponse> getQuestionPostsByMember(Member member, Pageable pageable);

Slice<AnsweredQuestionPostsByMemberResponse> getAnsweredQuestionPostsByMember(Member member, Pageable pageable);

Slice<BookmarksByMemberResponse> getBookmarksByMember(Member member, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
import com.dnd.gongmuin.answer.domain.QAnswer;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.BookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QAnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QBookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
import com.dnd.gongmuin.post_interaction.domain.InteractionType;
import com.dnd.gongmuin.post_interaction.domain.QInteraction;
import com.dnd.gongmuin.post_interaction.domain.QInteractionCount;
import com.dnd.gongmuin.question_post.domain.QQuestionPost;
import com.querydsl.jpa.JPAExpressions;
Expand Down Expand Up @@ -91,6 +94,34 @@ public Slice<AnsweredQuestionPostsByMemberResponse> getAnsweredQuestionPostsByMe
return new SliceImpl<>(content, pageable, hasNext);
}

@Override
public Slice<BookmarksByMemberResponse> getBookmarksByMember(Member member, Pageable pageable) {
QQuestionPost qp = QQuestionPost.questionPost;
QInteraction ir = QInteraction.interaction;
QInteractionCount saved = new QInteractionCount("SAVED");
QInteractionCount recommend = new QInteractionCount("RECOMMEND");

List<BookmarksByMemberResponse> content = queryFactory
.select(new QBookmarksByMemberResponse(qp, saved, recommend))
.from(qp)
.join(ir)
.on(qp.id.eq(ir.questionPostId).and(ir.type.eq(InteractionType.SAVED)))
.leftJoin(saved)
.on(qp.id.eq(saved.questionPostId).and(saved.type.eq(InteractionType.SAVED)))
.leftJoin(recommend)
.on(qp.id.eq(recommend.questionPostId).and(recommend.type.eq(InteractionType.RECOMMEND)))
.where(ir.memberId.eq(member.getId()))
.orderBy(qp.updatedAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1L)
.fetch();

boolean hasNext = hasNext(pageable.getPageSize(), content);

return new SliceImpl<>(content, pageable, hasNext);
}

// .on(qp.id.eq(ir.questionPostId).and(ir.type.eq(InteractionType.SAVED)))
private <T> boolean hasNext(int pageSize, List<T> content) {
if (content.size() <= pageSize) {
return false;
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.dnd.gongmuin.member.dto.request.UpdateMemberProfileRequest;
import com.dnd.gongmuin.member.dto.request.ValidateNickNameRequest;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.BookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.LogoutResponse;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
Expand Down Expand Up @@ -219,4 +220,16 @@ public PageResponse<AnsweredQuestionPostsByMemberResponse> getAnsweredQuestionPo
throw new NotFoundException(MemberErrorCode.ANSWERED_QUESTION_POSTS_BY_MEMBER_FAILED);
}
}

public PageResponse<BookmarksByMemberResponse> getBookmarksByMember(
Member member, Pageable pageable) {
try {
Slice<BookmarksByMemberResponse> responsePage =
memberRepository.getBookmarksByMember(member, pageable);

return PageMapper.toPageResponse(responsePage);
} catch (Exception e) {
throw new NotFoundException(MemberErrorCode.BOOKMARK_QUESTION_POSTS_BY_MEMBER_FAILED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,17 @@ public static Interaction interaction(
ReflectionTestUtils.setField(interaction, "id", 1L);
return interaction;
}

public static Interaction interaction2(
InteractionType type,
Long memberId,
Long questionPostId
) {
Interaction interaction = Interaction.of(
type,
memberId,
questionPostId
);
return interaction;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
import com.dnd.gongmuin.answer.repository.AnswerRepository;
import com.dnd.gongmuin.common.fixture.AnswerFixture;
import com.dnd.gongmuin.common.fixture.InteractionCountFixture;
import com.dnd.gongmuin.common.fixture.InteractionFixture;
import com.dnd.gongmuin.common.fixture.MemberFixture;
import com.dnd.gongmuin.common.fixture.QuestionPostFixture;
import com.dnd.gongmuin.common.support.ApiTestSupport;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.request.UpdateMemberProfileRequest;
import com.dnd.gongmuin.member.repository.MemberRepository;
import com.dnd.gongmuin.post_interaction.domain.Interaction;
import com.dnd.gongmuin.post_interaction.domain.InteractionCount;
import com.dnd.gongmuin.post_interaction.domain.InteractionType;
import com.dnd.gongmuin.post_interaction.repository.InteractionCountRepository;
import com.dnd.gongmuin.post_interaction.repository.InteractionRepository;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
import com.dnd.gongmuin.question_post.repository.QuestionPostRepository;

Expand All @@ -45,6 +48,9 @@ class MemberControllerTest extends ApiTestSupport {
@Autowired
InteractionCountRepository interactionCountRepository;

@Autowired
InteractionRepository interactionRepository;

@AfterEach
void tearDown() {
answerRepository.deleteAll();
Expand Down Expand Up @@ -163,4 +169,37 @@ void getAnsweredQuestionPostsByMember() throws Exception {
.andExpect(jsonPath("$.content[1].questionPostId").value(questionPost2.getId()))
.andExpect(jsonPath("$.content[1].answerId").value(answer1.getId()));
}

@DisplayName("로그인 된 회원의 스크랩 질문을 전체 조회한다.")
@Test
void getBookmarksByMember() throws Exception {
// given
Member member = MemberFixture.member2();
Member savedMember = memberRepository.save(member);

QuestionPost questionPost1 = QuestionPostFixture.questionPost(loginMember, "첫 번째 게시글입니다.");
QuestionPost questionPost2 = QuestionPostFixture.questionPost(savedMember, "두 번째 게시글입니다.");
QuestionPost questionPost3 = QuestionPostFixture.questionPost(loginMember, "세 번째 게시글입니다.");
questionPostRepository.saveAll(List.of(questionPost1, questionPost2, questionPost3));

Interaction interaction1 = InteractionFixture.interaction2(InteractionType.SAVED, loginMember.getId(),
questionPost1.getId());
Interaction interaction3 = InteractionFixture.interaction2(InteractionType.RECOMMEND, loginMember.getId(),
questionPost2.getId());
Interaction interaction2 = InteractionFixture.interaction2(InteractionType.SAVED, loginMember.getId(),
questionPost3.getId());
Interaction interaction4 = InteractionFixture.interaction2(InteractionType.RECOMMEND, loginMember.getId(),
questionPost3.getId());
interactionRepository.saveAll(List.of(interaction1, interaction2, interaction3, interaction4));

// when // then
mockMvc.perform(get("/api/members/post-interaction/bookmarks")
.header(AUTHORIZATION, accessToken)
)
.andExpect(status().isOk())
.andDo(MockMvcResultHandlers.print())
.andExpect(jsonPath("$.size").value(2))
.andExpect(jsonPath("$.content[0].questionPostId").value(questionPost3.getId()))
.andExpect(jsonPath("$.content[1].questionPostId").value(questionPost1.getId()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@
import com.dnd.gongmuin.answer.repository.AnswerRepository;
import com.dnd.gongmuin.common.fixture.AnswerFixture;
import com.dnd.gongmuin.common.fixture.InteractionCountFixture;
import com.dnd.gongmuin.common.fixture.InteractionFixture;
import com.dnd.gongmuin.common.fixture.MemberFixture;
import com.dnd.gongmuin.common.fixture.QuestionPostFixture;
import com.dnd.gongmuin.common.support.DataJpaTestSupport;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.BookmarksByMemberResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
import com.dnd.gongmuin.post_interaction.domain.Interaction;
import com.dnd.gongmuin.post_interaction.domain.InteractionCount;
import com.dnd.gongmuin.post_interaction.domain.InteractionType;
import com.dnd.gongmuin.post_interaction.repository.InteractionCountRepository;
import com.dnd.gongmuin.post_interaction.repository.InteractionRepository;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
import com.dnd.gongmuin.question_post.repository.QuestionPostRepository;

Expand All @@ -47,6 +51,9 @@ class MemberRepositoryTest extends DataJpaTestSupport {
@Autowired
InteractionCountRepository interactionCountRepository;

@Autowired
InteractionRepository interactionRepository;

@AfterEach
void tearDown() {
answerRepository.deleteAll();
Expand Down Expand Up @@ -91,8 +98,8 @@ void getQuestionPostsByMember() {
() -> assertThat(postsByMember).hasSize(2),
() -> assertThat(postsByMember).extracting(QuestionPostsByMemberResponse::title)
.containsExactly(
"두 번째 게시글입니다.",
"첫 번째 게시글입니다."
questionPost2.getTitle(),
questionPost1.getTitle()
),
() -> assertThat(postsByMember).extracting(QuestionPostsByMemberResponse::questionPostId)
.containsExactly(
Expand Down Expand Up @@ -139,8 +146,8 @@ void getQuestionPostsByMemberWithInteractionCount() {
),
() -> assertThat(postsByMember).extracting(QuestionPostsByMemberResponse::title)
.containsExactly(
"두 번째 게시글입니다.",
"첫 번째 게시글입니다."
questionPost2.getTitle(),
questionPost1.getTitle()
),
() -> assertThat(postsByMember).extracting(QuestionPostsByMemberResponse::savedTotalCount)
.containsExactly(
Expand Down Expand Up @@ -183,8 +190,8 @@ void getAnsweredQuestionPostsByMember() {
() -> assertThat(postsByMember).hasSize(2),
() -> assertThat(postsByMember).extracting(AnsweredQuestionPostsByMemberResponse::title)
.containsExactly(
"세 번째 게시글입니다.",
"첫 번째 게시글입니다."
questionPost3.getTitle(),
questionPost1.getTitle()
),
() -> assertThat(postsByMember).extracting(AnsweredQuestionPostsByMemberResponse::questionPostId)
.containsExactly(
Expand Down Expand Up @@ -237,8 +244,8 @@ void getAnsweredQuestionPostsByMemberWithInteractionCount() {
() -> assertThat(postsByMember).hasSize(2),
() -> assertThat(postsByMember).extracting(AnsweredQuestionPostsByMemberResponse::title)
.containsExactly(
"세 번째 게시글입니다.",
"첫 번째 게시글입니다."
questionPost3.getTitle(),
questionPost1.getTitle()
),
() -> assertThat(postsByMember).extracting(AnsweredQuestionPostsByMemberResponse::questionPostId)
.containsExactly(
Expand Down Expand Up @@ -329,19 +336,6 @@ void whenAnsweredQuestionPosts_thenGetQuestionPostsAtRecently() {

// then
Assertions.assertAll(
() -> postsByMember.forEach(post -> {
System.out.println("QuestionPostId: " + post.questionPostId());
System.out.println("Title: " + post.title());
System.out.println("Content: " + post.content());
System.out.println("JobGroup: " + post.jobGroup());
System.out.println("Reward: " + post.reward());
System.out.println("UpdatedAt: " + post.questionPostUpdatedAt());
System.out.println("IsChosen: " + post.isChosen());
System.out.println("answerId: " + post.answerId());
System.out.println("post.answerContent() = " + post.answerContent());
System.out.println("post.answerUpdatedAt() = " + post.answerUpdatedAt());
System.out.println("----------");
}),
() -> assertThat(postsByMember).hasSize(1),
() -> assertThat(postsByMember).extracting(AnsweredQuestionPostsByMemberResponse::answerId)
.containsExactly(
Expand All @@ -358,6 +352,49 @@ void whenAnsweredQuestionPosts_thenGetQuestionPostsAtRecently() {
);
}

@DisplayName("회원의 스크랩 질문 목록을 조회힌다.")
@Test
void getBookmarksByMember() {
// given
Member member1 = MemberFixture.member();
Member member2 = MemberFixture.member2();
memberRepository.saveAll(List.of(member1, member2));

QuestionPost questionPost1 = QuestionPostFixture.questionPost(member1, "첫 번째 게시글입니다.");
QuestionPost questionPost2 = QuestionPostFixture.questionPost(member2, "두 번째 게시글입니다.");
QuestionPost questionPost3 = QuestionPostFixture.questionPost(member2, "세 번째 게시글입니다.");
questionPostRepository.saveAll(List.of(questionPost1, questionPost2, questionPost3));

Interaction interaction1 = InteractionFixture.interaction2(InteractionType.SAVED, member1.getId(),
questionPost1.getId());
Interaction interaction2 = InteractionFixture.interaction2(InteractionType.SAVED, member1.getId(),
questionPost2.getId());
Interaction interaction3 = InteractionFixture.interaction2(InteractionType.RECOMMEND, member1.getId(),
questionPost2.getId());
Interaction interaction4 = InteractionFixture.interaction2(InteractionType.RECOMMEND, member1.getId(),
questionPost3.getId());
interactionRepository.saveAll(List.of(interaction1, interaction2, interaction3, interaction4));

// when
Slice<BookmarksByMemberResponse> bookmarksByMember = memberRepository.getBookmarksByMember(member1,
pageRequest);

// then
Assertions.assertAll(
() -> assertThat(bookmarksByMember).hasSize(2),
() -> assertThat(bookmarksByMember).extracting(BookmarksByMemberResponse::questionPostId)
.containsExactly(
questionPost2.getId(),
questionPost1.getId()
),
() -> assertThat(bookmarksByMember).extracting(BookmarksByMemberResponse::title)
.containsExactly(
questionPost2.getTitle(),
questionPost1.getTitle()
)
);
}

private Member createMember(String nickname, String socialName, String socialEmail, String officialEmail) {
return Member.builder()
.nickname(nickname)
Expand Down
Loading