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 #41] 나의활동 API #47

Merged
merged 22 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b0a5fe2
[feat] : MemberQueryDSL Repo 추가
dudxo Aug 15, 2024
f5c0ab6
[feat] : 작성한 질문 전체 조회 응답 DTO 추가
dudxo Aug 15, 2024
13df26b
[feat] : MemberErrorCode 추가
dudxo Aug 15, 2024
f4da065
[feat] : 작성한 질문 전체 조회 컨트롤러/비즈니스 로직 작성
dudxo Aug 15, 2024
c20044b
[test] : 특정 회원 작성 질문 목록 검색 테스트
dudxo Aug 15, 2024
0fd9dbb
[feat] : 댓글 단 질문 목록 응답 DTO 추가
dudxo Aug 15, 2024
68fcc48
[feat] : 댓글 단 질문 목록 QueryDSL 추가
dudxo Aug 15, 2024
c78b07e
[feat] : 댓글 단 질문 목록 에러코드 추가
dudxo Aug 15, 2024
a19f04d
[feat] : 댓글 단 질문 전체 조회 컨트롤러/비즈니스 로직 추가
dudxo Aug 15, 2024
bf0688c
[test] : 특정 회원 답변 작성 질문 목록 리스트 조회 테스트
dudxo Aug 15, 2024
0bc4f2a
[test] 마이페이지 - 나의활동 API 통합 테스트 추가
dudxo Aug 15, 2024
0ef0198
[feat #38] 질문글 상호작용 API (#44)
hyun2371 Aug 15, 2024
1b00e27
[feat #45] 게시글 상세 조회 응답 필드 추가 (#46)
hyun2371 Aug 16, 2024
b12a8f5
[fix] : PostInteractionCount -> InteractionCount 네이밍 변경으로 인한 오류 수정
dudxo Aug 16, 2024
f3267af
[test] : repoisitory 테스트 실패 수정
dudxo Aug 16, 2024
083ecd7
[fix] : 게시글 스크랩/추천해요 총 개수 제대로 매핑되도록 쿼리 수정
dudxo Aug 16, 2024
a315d55
[fix] : 작성 질문/답변 단 질문 응답 DTO 스크랩/추천해요 충 개수 필드 수정
dudxo Aug 16, 2024
3139d9f
[test] : 스크랩/추천해요 총 개수에 관한 테스트 추가
dudxo Aug 16, 2024
7fff790
[feat] : 답변 단 질문 목록 조회시 가장 최신 답변 1개만 가져오도록 쿼리 추가
dudxo Aug 16, 2024
c1ac55b
[test] : 답변 단 질문 목록 조회 관련 Repostiory/Controller 테스트 작성
dudxo Aug 16, 2024
f1271bc
[fix] : 연산 타입 일치화(+1 -> +1L)
dudxo Aug 16, 2024
64df7d3
[fix] : 질문 목록 id 값이 아닌 updatedAt() 내림차순으로 정렬 변경
dudxo Aug 16, 2024
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,7 +31,7 @@ public class AnswerService {
private final CreditHistoryService creditHistoryService;

private static void validateIfQuestioner(Member member, QuestionPost questionPost) {
if (!questionPost.isQuestioner(member)) {
if (!questionPost.isQuestioner(member.getId())) {
throw new ValidationException(QuestionPostErrorCode.NOT_AUTHORIZED);
}
}
Expand All @@ -43,7 +43,8 @@ public AnswerDetailResponse registerAnswer(
Member member
) {
QuestionPost questionPost = findQuestionPostById(questionPostId);
Answer answer = AnswerMapper.toAnswer(questionPostId, questionPost.isQuestioner(member), request, member);
Answer answer = AnswerMapper.toAnswer(questionPostId, questionPost.isQuestioner(member.getId()), request,
member);
return AnswerMapper.toAnswerDetailResponse(answerRepository.save(answer));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dnd.gongmuin.member.controller;

import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -8,9 +9,12 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.dnd.gongmuin.common.dto.PageResponse;
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.MemberProfileResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.service.MemberService;

import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -31,6 +35,7 @@ public class MemberController {
@GetMapping("/profile")
public ResponseEntity<MemberProfileResponse> getMemberProfile(@AuthenticationPrincipal Member member) {
MemberProfileResponse response = memberService.getMemberProfile(member);

return ResponseEntity.ok(response);
}

Expand All @@ -41,6 +46,31 @@ public ResponseEntity<MemberProfileResponse> updateMemberProfile(
@RequestBody UpdateMemberProfileRequest request,
@AuthenticationPrincipal Member member) {
MemberProfileResponse response = memberService.updateMemberProfile(request, member);

return ResponseEntity.ok(response);
}

@Operation(summary = "작성한 질문 전체 조회 API", description = "작성한 질문을 전체 조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@GetMapping("/question-posts")
public ResponseEntity<PageResponse<QuestionPostsByMemberResponse>> getQuestionPostsByMember(
@AuthenticationPrincipal Member member,
Pageable pageable) {
PageResponse<QuestionPostsByMemberResponse> response =
memberService.getQuestionPostsByMember(member, pageable);

return ResponseEntity.ok(response);
}

@Operation(summary = "댓글 단 질문 전체 조회 API", description = "댓글 단 질문을 전체 조회한다.")
@ApiResponse(useReturnTypeSchema = true)
@GetMapping("/answer-posts")
public ResponseEntity<PageResponse<AnsweredQuestionPostsByMemberResponse>> getAnsweredQuestionPostsByMember(
@AuthenticationPrincipal Member member,
Pageable pageable) {
PageResponse<AnsweredQuestionPostsByMemberResponse> response =
memberService.getAnsweredQuestionPostsByMember(member, pageable);

return ResponseEntity.ok(response);
}

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

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

public record AnsweredQuestionPostsByMemberResponse(
Long questionPostId,
String title,
String content,
String jobGroup,
int reward,
String questionPostUpdatedAt,
boolean isChosen,
int savedTotalCount,
int recommendTotalCount,
Long answerId,
String answerContent,
String answerUpdatedAt
) {

@QueryProjection
public AnsweredQuestionPostsByMemberResponse(
QuestionPost questionPost,
InteractionCount savedCount,
InteractionCount recommendCount,
Answer answer) {
this(
questionPost.getId(),
questionPost.getTitle(),
questionPost.getContent(),
questionPost.getJobGroup().getLabel(),
questionPost.getReward(),
questionPost.getUpdatedAt().toString(),
questionPost.getIsChosen(),
extractTotalCount(savedCount),
extractTotalCount(recommendCount),
answer.getId(),
answer.getContent(),
answer.getUpdatedAt().toString()
);
}

private static int extractTotalCount(InteractionCount interactionCount) {
return interactionCount != null ? interactionCount.getCount() : 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 QuestionPostsByMemberResponse(
Long questionPostId,
String title,
String content,
String jobGroup,
int reward,
String updatedAt,
boolean isChosen,
int savedTotalCount,
int recommendTotalCount
) {
hyun2371 marked this conversation as resolved.
Show resolved Hide resolved

@QueryProjection
public QuestionPostsByMemberResponse(
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 @@ -13,7 +13,9 @@ public enum MemberErrorCode implements ErrorCode {
NOT_FOUND_NEW_MEMBER("신규 회원이 아닙니다.", "MEMBER_002"),
LOGOUT_FAILED("로그아웃을 실패했습니다.", "MEMBER_003"),
NOT_ENOUGH_CREDIT("보유한 크레딧이 부족합니다.", "MEMBER_004"),
UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005");
UPDATE_PROFILE_FAILED("프로필 수정에 실패했습니다.", "MEMBER_005"),
QUESTION_POSTS_BY_MEMBER_FAILED("작성한 게시글 목록을 찾는 도중 실패했습니다.", "MEMBER_006"),
ANSWERED_QUESTION_POSTS_BY_MEMBER_FAILED("댓글 단 게시글 목록을 찾는 도중 실패했습니다.", "MEMBER_007");

private final String message;
private final String code;
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/repository/MemberCustom.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.dnd.gongmuin.member.repository;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.member.dto.response.AnsweredQuestionPostsByMemberResponse;
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);
}
101 changes: 101 additions & 0 deletions src/main/java/com/dnd/gongmuin/member/repository/MemberCustomImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.dnd.gongmuin.member.repository;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

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.QAnsweredQuestionPostsByMemberResponse;
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.QInteractionCount;
import com.dnd.gongmuin.question_post.domain.QQuestionPost;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class MemberCustomImpl implements MemberCustom {

private final JPAQueryFactory queryFactory;

@Override
public Slice<QuestionPostsByMemberResponse> getQuestionPostsByMember(Member member, Pageable pageable) {
QQuestionPost qp = QQuestionPost.questionPost;
QInteractionCount saved = new QInteractionCount("SAVED");
QInteractionCount recommend = new QInteractionCount("RECOMMEND");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 따로 변수로 빼야하나 생각이 듭니다! 변수명를을축약하니까 한 번 더 생각을 해야 하는 것 같아요! static import를 하면 별로 길어질 것 같지 않습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존 가현님 코드를 참고하며 static import를 하다보니 무언가 더 이해하기 힘들다라는 생각이 들더라고요!
이 부분은 추후 더 생각해보겠습니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QQuestionPost가 static import 해서 questionPost로 되는거라 이해가 안된다는 말씀이 잘 와닿지 않네요,, 저는 오히려 개인적으로 약어를 변수명으로 선언한게 한 번 더 생각하도록 만든다고 느꼈습니다!

List<QuestionPostsByMemberResponse> content = queryFactory
.select(new QQuestionPostsByMemberResponse(qp, saved, recommend))
.from(qp)
.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(qp.member.eq(member))
.orderBy(qp.updatedAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1L)
.fetch();
dudxo marked this conversation as resolved.
Show resolved Hide resolved

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

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

@Override
public Slice<AnsweredQuestionPostsByMemberResponse> getAnsweredQuestionPostsByMember(
Member member, Pageable pageable) {
QQuestionPost qp = QQuestionPost.questionPost;
QInteractionCount saved = new QInteractionCount("SAVED");
QInteractionCount recommend = new QInteractionCount("RECOMMEND");
QAnswer aw1 = new QAnswer("answer1");
QAnswer aw2 = new QAnswer("answer2");

List<AnsweredQuestionPostsByMemberResponse> content =
queryFactory
.select(new QAnsweredQuestionPostsByMemberResponse(qp, saved, recommend, aw1))
.from(qp)
.join(aw1)
.on(aw1.id.eq(
JPAExpressions
.select(aw2.id)
.from(aw2)
.where(aw2.questionPostId.eq(qp.id)
.and(aw2.member.eq(member))
.and(aw2.updatedAt.eq(
JPAExpressions
.select(aw2.updatedAt.max())
.from(aw2)
.where(aw2.questionPostId.eq(qp.id)
.and(aw2.member.eq(member)))
)))
))
.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)))
.orderBy(qp.updatedAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize() + 1L)
.fetch();

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

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

private <T> boolean hasNext(int pageSize, List<T> content) {
if (content.size() <= pageSize) {
return false;
}
content.remove(pageSize);
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import com.dnd.gongmuin.member.domain.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {
public interface MemberRepository extends JpaRepository<Member, Long>, MemberCustom {
Optional<Member> findBySocialEmail(String socialEmail);

boolean existsByNickname(String nickname);
Expand Down
30 changes: 30 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 @@ -4,12 +4,16 @@
import java.util.Date;
import java.util.Objects;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dnd.gongmuin.auth.domain.Provider;
import com.dnd.gongmuin.auth.exception.AuthErrorCode;
import com.dnd.gongmuin.common.dto.PageMapper;
import com.dnd.gongmuin.common.dto.PageResponse;
import com.dnd.gongmuin.common.exception.runtime.NotFoundException;
import com.dnd.gongmuin.common.exception.runtime.ValidationException;
import com.dnd.gongmuin.member.domain.JobCategory;
Expand All @@ -21,8 +25,10 @@
import com.dnd.gongmuin.member.dto.request.ReissueRequest;
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.LogoutResponse;
import com.dnd.gongmuin.member.dto.response.MemberProfileResponse;
import com.dnd.gongmuin.member.dto.response.QuestionPostsByMemberResponse;
import com.dnd.gongmuin.member.dto.response.ReissueResponse;
import com.dnd.gongmuin.member.dto.response.SignUpResponse;
import com.dnd.gongmuin.member.dto.response.ValidateNickNameResponse;
Expand Down Expand Up @@ -189,4 +195,28 @@ public MemberProfileResponse updateMemberProfile(UpdateMemberProfileRequest requ
throw new ValidationException(MemberErrorCode.UPDATE_PROFILE_FAILED);
}
}

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

return PageMapper.toPageResponse(responsePage);
} catch (Exception e) {
throw new NotFoundException(MemberErrorCode.QUESTION_POSTS_BY_MEMBER_FAILED);
}
}

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

return PageMapper.toPageResponse(responsePage);
} catch (Exception e) {
throw new NotFoundException(MemberErrorCode.ANSWERED_QUESTION_POSTS_BY_MEMBER_FAILED);
}
}
}
Loading
Loading