Skip to content

Commit

Permalink
Refactor(#197): 친구 수락 중복시 에러처리, 친구 검색 이름으로도 가능하게, 친구 마이페이지 조회 api 구현 (#…
Browse files Browse the repository at this point in the history
…215)

* Feat(#197): 이미 수락한 친구신청 오류 반환 로직 추가  (#197)

* Feat(#197): 키워드로 조회할 때 이메일,이름으로 가능하도록 수정 (#197)

* Feat: 친구 조회 로직 추가 (팀대시보드, 챌린지 정보는 아직 보류)

* Test: 친구 조회 로직 추가 (팀대시보드, 챌린지 정보는 아직 보류) Test

* Refactor: 개행 삭제

* Feat: 친구 마이페이지 public 개인 대시보드, 챌린지 목록 조회 추가

* Test: 친구 public 개인 대시보드와 챌린지 정보 조회 test 코드 작성

* Refactor: 파라미터로 넘어가는 memberId -> friendId로 변수명 변경
  • Loading branch information
inhooo00 authored Nov 16, 2024
1 parent cd944ed commit 159d549
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 10 deletions.
25 changes: 24 additions & 1 deletion src/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,27 @@ include::{snippets}/member/mypage-update/request-fields.adoc[]
==== 응답

include::{snippets}/member/mypage/http-response.adoc[]
include::{snippets}/member/mypage/response-fields.adoc[]
include::{snippets}/member/mypage/response-fields.adoc[]

=== 친구 프로필 정보 조회 API

==== 요청

include::{snippets}/member/friend-profile/http-request.adoc[]

==== 응답

include::{snippets}/member/friend-profile/http-response.adoc[]
include::{snippets}/member/friend-profile/response-fields.adoc[]

=== 친구 public 개인 대시보드와 챌린지 정보 조회 API

==== 요청

include::{snippets}/member/friend-dashboard-challenges/http-request.adoc[]
include::{snippets}/member/friend-dashboard-challenges/query-parameters.adoc[]

==== 응답

include::{snippets}/member/friend-dashboard-challenges/http-response.adoc[]
include::{snippets}/member/friend-dashboard-challenges/response-fields.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import shop.kkeujeok.kkeujeokbackend.global.template.RspTemplate;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.request.MyPageUpdateReqDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.MyPageInfoResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.PersonalDashboardsAndChallengesResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.TeamDashboardsAndChallengesResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.application.MyPageService;

Expand Down Expand Up @@ -35,7 +36,23 @@ public RspTemplate<TeamDashboardsAndChallengesResDto> getTeamDashboardsAndChalle
@RequestParam(name = "requestEmail") String requestEmail,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size) {
TeamDashboardsAndChallengesResDto response = myPageService.findTeamDashboardsAndChallenges(email, requestEmail, PageRequest.of(page, size));
TeamDashboardsAndChallengesResDto response = myPageService.findTeamDashboardsAndChallenges(email, requestEmail,
PageRequest.of(page, size));
return new RspTemplate<>(HttpStatus.OK, "대시보드와 챌린지 정보 조회", response);
}

@GetMapping("/mypage/{friendId}")
public RspTemplate<MyPageInfoResDto> getFriendProfileInfo(@PathVariable Long friendId) {
MyPageInfoResDto friendProfile = myPageService.findFriendProfile(friendId);
return new RspTemplate<>(HttpStatus.OK, "친구 프로필 정보 조회", friendProfile);
}

@GetMapping("/mypage/{friendId}/dashboard-challenges")
public RspTemplate<PersonalDashboardsAndChallengesResDto> getPersonalDashboardsAndChallenges(@PathVariable Long friendId,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size) {
PersonalDashboardsAndChallengesResDto response = myPageService.findFriendDashboardsAndChallenges(friendId,
PageRequest.of(page, size));
return new RspTemplate<>(HttpStatus.OK, "대시보드와 챌린지 정보 조회", response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.jpa.impl.JPAUpdateClause;
import jakarta.persistence.EntityManager;
Expand All @@ -26,6 +27,7 @@
import shop.kkeujeok.kkeujeokbackend.member.follow.api.dto.response.RecommendedFollowInfoResDto;
import shop.kkeujeok.kkeujeokbackend.member.follow.domain.Follow;
import shop.kkeujeok.kkeujeokbackend.member.follow.domain.FollowStatus;
import shop.kkeujeok.kkeujeokbackend.member.follow.exception.FollowAlreadyAcceptException;

@Repository
@RequiredArgsConstructor
Expand All @@ -50,14 +52,27 @@ public boolean existsByFromMemberAndToMember(Member fromMember, Member toMember)
}

@Override
@Transactional
public void acceptFollowingRequest(Long followId) {
checkIfAlreadyAccepted(followId);

new JPAUpdateClause(entityManager, follow)
.where(follow.id.eq(followId))
.set(follow.followStatus, FollowStatus.ACCEPT)
.execute();
}

private void checkIfAlreadyAccepted(Long followId) {
FollowStatus currentStatus = new JPAQuery<>(entityManager)
.select(follow.followStatus)
.from(follow)
.where(follow.id.eq(followId))
.fetchOne();

if (currentStatus == FollowStatus.ACCEPT) {
throw new FollowAlreadyAcceptException();
}
}

@Override
public Page<FollowInfoResDto> findFollowList(Long memberId, Pageable pageable) {
List<FollowInfoResDto> fetch = queryFactory
Expand Down Expand Up @@ -129,7 +144,8 @@ public Page<RecommendedFollowInfoResDto> findRecommendedFollowList(Long memberId
.from(follow)
.where(
(follow.fromMember.id.eq(memberId).and(follow.toMember.id.eq(teamMember.getId())))
.or(follow.fromMember.id.eq(teamMember.getId()).and(follow.toMember.id.eq(memberId)))
.or(follow.fromMember.id.eq(teamMember.getId())
.and(follow.toMember.id.eq(memberId)))
)
.fetchFirst() != null;

Expand All @@ -144,7 +160,6 @@ public Page<RecommendedFollowInfoResDto> findRecommendedFollowList(Long memberId
return new PageImpl<>(pagedRecommendedFollows, pageable, recommendedFollows.size());
}


@Override
public Optional<Follow> findByFromMemberAndToMember(Member fromMember, Member toMember) {
Follow followRecord = queryFactory
Expand All @@ -161,9 +176,7 @@ public Optional<Follow> findByFromMemberAndToMember(Member fromMember, Member to
@Override
public Page<MemberInfoForFollowResDto> searchFollowListUsingKeywords(Long memberId, String keyword,
Pageable pageable) {
BooleanExpression keywordCondition = keyword != null && !keyword.isBlank()
? member.name.containsIgnoreCase(keyword).or(member.email.containsIgnoreCase(keyword))
: null;
BooleanExpression keywordCondition = buildKeywordCondition(keyword);

List<MemberInfoForFollowResDto> members = queryFactory
.select(Projections.constructor(MemberInfoForFollowResDto.class,
Expand Down Expand Up @@ -199,6 +212,13 @@ public Page<MemberInfoForFollowResDto> searchFollowListUsingKeywords(Long member
return new PageImpl<>(members, pageable, total);
}

private BooleanExpression buildKeywordCondition(String keyword) {
if (keyword == null || keyword.isBlank()) {
return null;
}
return member.name.containsIgnoreCase(keyword).or(member.email.containsIgnoreCase(keyword));
}

@Override
public MyFollowsResDto findMyFollowsCount(Long memberId) {
int followCount = (int) queryFactory
Expand All @@ -213,5 +233,4 @@ public MyFollowsResDto findMyFollowsCount(Long memberId) {

return MyFollowsResDto.from(followCount);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package shop.kkeujeok.kkeujeokbackend.member.follow.exception;

import shop.kkeujeok.kkeujeokbackend.global.error.exception.AccessDeniedGroupException;

public class FollowAlreadyAcceptException extends AccessDeniedGroupException {
public FollowAlreadyAcceptException(String message) {
super(message);
}

public FollowAlreadyAcceptException() {
this("이미 친구를 요청을 수락한 상태입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response;

import shop.kkeujeok.kkeujeokbackend.challenge.api.dto.response.ChallengeListResDto;
import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardPageListResDto;
import shop.kkeujeok.kkeujeokbackend.dashboard.team.api.dto.response.TeamDashboardListResDto;

public record PersonalDashboardsAndChallengesResDto(
PersonalDashboardPageListResDto personalDashboardList,
ChallengeListResDto challengeList
) {
public static PersonalDashboardsAndChallengesResDto of(PersonalDashboardPageListResDto personalDashboardList,
ChallengeListResDto challengeList) {
return new PersonalDashboardsAndChallengesResDto(
personalDashboardList,
challengeList
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import shop.kkeujeok.kkeujeokbackend.dashboard.team.application.TeamDashboardService;
import shop.kkeujeok.kkeujeokbackend.member.domain.Member;
import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository;
import shop.kkeujeok.kkeujeokbackend.member.exception.MemberNotFoundException;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.request.MyPageUpdateReqDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.MyPageInfoResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.PersonalDashboardsAndChallengesResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.TeamDashboardsAndChallengesResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.exception.ExistsNicknameException;

Expand All @@ -30,7 +32,7 @@ public class MyPageService {

// 프로필 정보 조회
public MyPageInfoResDto findMyProfileByEmail(String email) {
Member member = memberRepository.findByEmail(email).orElseThrow();
Member member = memberRepository.findByEmail(email).orElseThrow(MemberNotFoundException::new);

return MyPageInfoResDto.From(member);
}
Expand Down Expand Up @@ -80,4 +82,23 @@ private boolean isNicknameDuplicate(String nickname) {
private String normalizeNickname(String nickname) {
return nickname.replaceAll("\\s+", "");
}

// 친구 프로필 정보 조회
public MyPageInfoResDto findFriendProfile(Long memberId) {
Member member = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new);

return MyPageInfoResDto.From(member);
}

// 친구 프로필에 표시되는 정보 조회
public PersonalDashboardsAndChallengesResDto findFriendDashboardsAndChallenges(Long memberId, Pageable pageable) {
Member member = memberRepository.findById(memberId).orElseThrow(MemberNotFoundException::new);

ChallengeListResDto challengeListResDto = challengeService.findChallengeForMemberId(member.getEmail(), pageable);

PersonalDashboardPageListResDto personalDashboardPageListResDto =
personalDashboardService.findPublicPersonalDashboards(member.getEmail(), pageable);

return PersonalDashboardsAndChallengesResDto.of(personalDashboardPageListResDto, challengeListResDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import shop.kkeujeok.kkeujeokbackend.member.domain.Role;
import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.MyPageInfoResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.PersonalDashboardsAndChallengesResDto;
import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.TeamDashboardsAndChallengesResDto;

import java.util.ArrayList;
Expand Down Expand Up @@ -211,4 +212,113 @@ void setUp(RestDocumentationContextProvider restDocumentation) {
// ))
// .andExpect(status().isOk());
// }

@DisplayName("친구의 프로필 정보를 가져옵니다.")
@Test
void 친구_프로필_정보를_가져옵니다() throws Exception {
// Given
Long friendId = 1L;
MyPageInfoResDto friendProfileDto = new MyPageInfoResDto(
"friendPicture",
"[email protected]",
"친구이름",
"친구닉네임",
SocialType.GOOGLE,
"친구소개"
);

when(myPageService.findFriendProfile(friendId)).thenReturn(friendProfileDto);

// When & Then
mockMvc.perform(get("/api/members/mypage/{memberId}", friendId)
.header("Authorization", "Bearer valid-token"))
.andDo(print())
.andDo(document("member/friend-profile",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath("statusCode").description("상태 코드"),
fieldWithPath("message").description("응답 메시지"),
fieldWithPath("data.picture").description("친구 사진"),
fieldWithPath("data.email").description("친구 이메일"),
fieldWithPath("data.name").description("친구 이름"),
fieldWithPath("data.nickName").description("친구 닉네임"),
fieldWithPath("data.socialType").description("친구 소셜 타입"),
fieldWithPath("data.introduction").description("친구 소개")
)
))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message", is("친구 프로필 정보 조회")))
.andExpect(jsonPath("$.data").exists())
.andExpect(jsonPath("$.data.email", is("[email protected]")))
.andExpect(jsonPath("$.data.name", is("친구이름")))
.andExpect(jsonPath("$.data.nickName", is("친구닉네임")))
.andExpect(jsonPath("$.data.socialType", is(SocialType.GOOGLE.name())))
.andExpect(jsonPath("$.data.introduction", is("친구소개")));
}

@DisplayName("친구의 public 개인 대시보드와 챌린지 정보를 가져옵니다.")
@Test
void 친구_대시보드와_챌린지_정보를_가져옵니다() throws Exception {
// Given
PersonalDashboardPageListResDto personalDashboardList = new PersonalDashboardPageListResDto(
new ArrayList<>(),
new PageInfoResDto(0, 0, 0)
);

ChallengeListResDto challengeList = new ChallengeListResDto(
new ArrayList<>(),
new PageInfoResDto(0, 0, 0)
);

PersonalDashboardsAndChallengesResDto resDto = new PersonalDashboardsAndChallengesResDto(
personalDashboardList,
challengeList
);

Long friendId = 1L;

when(myPageService.findFriendDashboardsAndChallenges(friendId, PageRequest.of(0, 10))).thenReturn(resDto);

// When & Then
mockMvc.perform(get("/api/members/mypage/{memberId}/dashboard-challenges", friendId)
.param("page", "0")
.param("size", "10"))
.andDo(print())
.andDo(document("member/friend-dashboard-challenges",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
queryParameters(
parameterWithName("page").description("페이지 번호 (기본값: 0)"),
parameterWithName("size").description("페이지 당 항목 수 (기본값: 10)")
),
responseFields(
fieldWithPath("statusCode").description("상태 코드"),
fieldWithPath("message").description("응답 메시지"),
fieldWithPath("data.personalDashboardList.personalDashboardInfoResDto").description("개인 대시보드 정보 목록"),
fieldWithPath("data.personalDashboardList.pageInfoResDto.currentPage").description("현재 페이지 번호"),
fieldWithPath("data.personalDashboardList.pageInfoResDto.totalPages").description("총 페이지 수"),
fieldWithPath("data.personalDashboardList.pageInfoResDto.totalItems").description("총 항목 수"),
fieldWithPath("data.challengeList.challengeInfoResDto").description("챌린지 정보 목록"),
fieldWithPath("data.challengeList.pageInfoResDto.currentPage").description("현재 페이지 번호"),
fieldWithPath("data.challengeList.pageInfoResDto.totalPages").description("총 페이지 수"),
fieldWithPath("data.challengeList.pageInfoResDto.totalItems").description("총 항목 수")
)
))
.andExpect(status().isOk())
.andExpect(jsonPath("$.statusCode", is(200)))
.andExpect(jsonPath("$.message", is("대시보드와 챌린지 정보 조회")))
.andExpect(jsonPath("$.data").exists())
.andExpect(jsonPath("$.data.personalDashboardList").exists())
.andExpect(jsonPath("$.data.personalDashboardList.personalDashboardInfoResDto").isArray())
.andExpect(jsonPath("$.data.personalDashboardList.pageInfoResDto.currentPage", is(0)))
.andExpect(jsonPath("$.data.personalDashboardList.pageInfoResDto.totalPages", is(0)))
.andExpect(jsonPath("$.data.personalDashboardList.pageInfoResDto.totalItems", is(0)))
.andExpect(jsonPath("$.data.challengeList").exists())
.andExpect(jsonPath("$.data.challengeList.challengeInfoResDto").isArray())
.andExpect(jsonPath("$.data.challengeList.pageInfoResDto.currentPage", is(0)))
.andExpect(jsonPath("$.data.challengeList.pageInfoResDto.totalPages", is(0)))
.andExpect(jsonPath("$.data.challengeList.pageInfoResDto.totalItems", is(0)));
}

}

0 comments on commit 159d549

Please sign in to comment.