diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index 95ce8bfb..265d99bb 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -12,7 +12,7 @@ include::{snippets}/member/mypage/request-headers.adoc[] include::{snippets}/member/mypage/http-response.adoc[] include::{snippets}/member/mypage/response-fields.adoc[] -=== 팀 대시보드와 챌린지 정보 조회 API +=== 대시보드와 챌린지 정보 조회 API ==== 요청 diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepository.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepository.java index 4329afe8..66a36cd5 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepository.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepository.java @@ -12,6 +12,8 @@ public interface DashboardCustomRepository { List findForPersonalDashboard(Member member); + Page findForPersonalDashboard(Member member, Pageable pageable); + Set findCategoriesForDashboard(Member member); Page findForTeamDashboard(Member member, Pageable pageable); @@ -22,4 +24,5 @@ public interface DashboardCustomRepository { double calculateCompletionPercentage(Long dashboardId); + Page findPublicPersonalDashboard(Member member, Pageable pageable); } diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepositoryImpl.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepositoryImpl.java index 83a9f615..70b57371 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepositoryImpl.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/domain/repository/DashboardCustomRepositoryImpl.java @@ -40,6 +40,25 @@ public List findForPersonalDashboard(Member member) { .fetch(); } + @Override + public Page findForPersonalDashboard(Member member, Pageable pageable) { + long total = queryFactory + .selectFrom(personalDashboard) + .where(personalDashboard._super.member.eq(member) + .and(personalDashboard._super.status.eq(Status.ACTIVE))) + .fetchCount(); + + List dashboards = queryFactory + .selectFrom(personalDashboard) + .where(personalDashboard._super.member.eq(member) + .and(personalDashboard._super.status.eq(Status.ACTIVE))) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + return new PageImpl<>(dashboards, pageable, total); + } + @Override public Set findCategoriesForDashboard(Member member) { return queryFactory @@ -118,4 +137,25 @@ public double calculateCompletionPercentage(Long dashboardId) { return (double) completedBlocks / totalBlocks * 100; } + + @Override + public Page findPublicPersonalDashboard(Member member, Pageable pageable) { + long total = queryFactory + .selectFrom(personalDashboard) + .where(personalDashboard._super.member.eq(member) + .and(personalDashboard._super.status.eq(Status.ACTIVE)) + .and(personalDashboard.isPublic.eq(true))) // isPublic이 true인 경우 + .fetchCount(); + + List dashboards = queryFactory + .selectFrom(personalDashboard) + .where(personalDashboard._super.member.eq(member) + .and(personalDashboard._super.status.eq(Status.ACTIVE)) + .and(personalDashboard.isPublic.eq(true))) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + return new PageImpl<>(dashboards, pageable, total); + } } diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/api/dto/response/PersonalDashboardPageListResDto.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/api/dto/response/PersonalDashboardPageListResDto.java new file mode 100644 index 00000000..3288b7d6 --- /dev/null +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/api/dto/response/PersonalDashboardPageListResDto.java @@ -0,0 +1,27 @@ +package shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response; + +import lombok.Builder; +import shop.kkeujeok.kkeujeokbackend.dashboard.team.api.dto.response.TeamDashboardInfoResDto; +import shop.kkeujeok.kkeujeokbackend.global.dto.PageInfoResDto; + +import java.util.List; + +@Builder +public record PersonalDashboardPageListResDto( + List personalDashboardInfoResDto, + PageInfoResDto pageInfoResDto +) { + public static PersonalDashboardPageListResDto of(List personalDashboards, + PageInfoResDto pageInfoResDto) { + return PersonalDashboardPageListResDto.builder() + .personalDashboardInfoResDto(personalDashboards) + .pageInfoResDto(pageInfoResDto) + .build(); + } + + public static PersonalDashboardPageListResDto from(List personalDashboards) { + return PersonalDashboardPageListResDto.builder() + .personalDashboardInfoResDto(personalDashboards) + .build(); + } +} diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/application/PersonalDashboardService.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/application/PersonalDashboardService.java index a3e643ae..dd463b75 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/application/PersonalDashboardService.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/dashboard/personal/application/PersonalDashboardService.java @@ -2,7 +2,10 @@ import java.util.List; import java.util.Set; + import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.kkeujeok.kkeujeokbackend.dashboard.exception.DashboardNotFoundException; @@ -12,9 +15,11 @@ import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardCategoriesResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardInfoResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardListResDto; +import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardPageListResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.personal.domain.PersonalDashboard; import shop.kkeujeok.kkeujeokbackend.dashboard.personal.domain.repository.PersonalDashboardRepository; import shop.kkeujeok.kkeujeokbackend.dashboard.personal.exception.DashboardAccessDeniedException; +import shop.kkeujeok.kkeujeokbackend.global.dto.PageInfoResDto; import shop.kkeujeok.kkeujeokbackend.member.domain.Member; import shop.kkeujeok.kkeujeokbackend.member.domain.repository.MemberRepository; import shop.kkeujeok.kkeujeokbackend.member.exception.MemberNotFoundException; @@ -115,4 +120,32 @@ private void verifyMemberIsAuthor(PersonalDashboard dashboard, Member member) { } } -} + // isPublic이 true인 대시보드 조회 메서드 + public PersonalDashboardPageListResDto findPublicPersonalDashboards(String email, Pageable pageable) { + Member member = memberRepository.findByEmail(email).orElseThrow(MemberNotFoundException::new); + + Page publicDashboards = personalDashboardRepository.findPublicPersonalDashboard(member, pageable); + + List publicDashboardInfoResDtoList = publicDashboards.stream() + .map(dashboard -> PersonalDashboardInfoResDto.of(member, dashboard)) + .toList(); + + return PersonalDashboardPageListResDto.of(publicDashboardInfoResDtoList, PageInfoResDto.from(publicDashboards)); + } + + // 개인 대시보드 전체 조회(페이지네이션 적용) + public PersonalDashboardPageListResDto findForPersonalDashboard(String email, Pageable pageable) { + Member member = memberRepository.findByEmail(email).orElseThrow(MemberNotFoundException::new); + + // Repository를 통해 페이징된 개인 대시보드 조회 + Page personalDashboards = personalDashboardRepository.findForPersonalDashboard(member, pageable); + + // DTO 변환 + List personalDashboardInfoResDtoList = personalDashboards.stream() + .map(p -> PersonalDashboardInfoResDto.of(member, p)) + .toList(); + + // 페이징 정보를 포함한 응답 반환 + return PersonalDashboardPageListResDto.of(personalDashboardInfoResDtoList, PageInfoResDto.from(personalDashboards)); + } +} \ No newline at end of file diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberController.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberController.java index 51ac9a04..ed3380dd 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberController.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberController.java @@ -2,7 +2,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import shop.kkeujeok.kkeujeokbackend.global.annotation.CurrentUserEmail; @@ -12,8 +11,6 @@ import shop.kkeujeok.kkeujeokbackend.member.mypage.api.dto.response.TeamDashboardsAndChallengesResDto; import shop.kkeujeok.kkeujeokbackend.member.mypage.application.MyPageService; -import java.util.Set; - @RestController @RequiredArgsConstructor @RequestMapping("/api/members") @@ -35,9 +32,10 @@ public RspTemplate update(@CurrentUserEmail String email, @GetMapping("/mypage/dashboard-challenges") public RspTemplate getTeamDashboardsAndChallenges(@CurrentUserEmail String email, + @RequestParam(name = "requestEmail") String requestEmail, @RequestParam(name = "page", defaultValue = "0") int page, @RequestParam(name = "size", defaultValue = "10") int size) { - TeamDashboardsAndChallengesResDto response = myPageService.findTeamDashboardsAndChallenges(email, PageRequest.of(page, size)); - return new RspTemplate<>(HttpStatus.OK, "팀 대시보드와 챌린지 정보 조회", response); + TeamDashboardsAndChallengesResDto response = myPageService.findTeamDashboardsAndChallenges(email, requestEmail, PageRequest.of(page, size)); + return new RspTemplate<>(HttpStatus.OK, "대시보드와 챌린지 정보 조회", response); } } diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/api/dto/response/TeamDashboardsAndChallengesResDto.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/api/dto/response/TeamDashboardsAndChallengesResDto.java index 093700d0..fc845c1e 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/api/dto/response/TeamDashboardsAndChallengesResDto.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/api/dto/response/TeamDashboardsAndChallengesResDto.java @@ -1,14 +1,19 @@ 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 TeamDashboardsAndChallengesResDto( + PersonalDashboardPageListResDto personalDashboardList, TeamDashboardListResDto teamDashboardList, ChallengeListResDto challengeList ) { - public static TeamDashboardsAndChallengesResDto of(TeamDashboardListResDto teamDashboardList, ChallengeListResDto challengeList) { + public static TeamDashboardsAndChallengesResDto of(PersonalDashboardPageListResDto personalDashboardList, + TeamDashboardListResDto teamDashboardList, + ChallengeListResDto challengeList) { return new TeamDashboardsAndChallengesResDto( + personalDashboardList, teamDashboardList, challengeList ); diff --git a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageService.java b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageService.java index 3e670846..04cf6a88 100644 --- a/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageService.java +++ b/src/main/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageService.java @@ -7,6 +7,8 @@ import shop.kkeujeok.kkeujeokbackend.auth.exception.EmailNotFoundException; import shop.kkeujeok.kkeujeokbackend.challenge.api.dto.response.ChallengeListResDto; import shop.kkeujeok.kkeujeokbackend.challenge.application.ChallengeService; +import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardPageListResDto; +import shop.kkeujeok.kkeujeokbackend.dashboard.personal.application.PersonalDashboardService; import shop.kkeujeok.kkeujeokbackend.dashboard.team.api.dto.response.TeamDashboardListResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.team.application.TeamDashboardService; import shop.kkeujeok.kkeujeokbackend.member.domain.Member; @@ -22,6 +24,7 @@ public class MyPageService { private final MemberRepository memberRepository; + private final PersonalDashboardService personalDashboardService; private final TeamDashboardService teamDashboardService; private final ChallengeService challengeService; @@ -46,13 +49,24 @@ public MyPageInfoResDto update(String email, MyPageUpdateReqDto myPageUpdateReqD return MyPageInfoResDto.From(member); } - // 팀 대시보드 & 챌린지 정보 조회 + // 대시보드 & 챌린지 정보 조회 @Transactional(readOnly = true) - public TeamDashboardsAndChallengesResDto findTeamDashboardsAndChallenges(String email, Pageable pageable) { - TeamDashboardListResDto teamDashboardListResDto = teamDashboardService.findForTeamDashboard(email, pageable); - ChallengeListResDto challengeListResDto = challengeService.findChallengeForMemberId(email, pageable); + public TeamDashboardsAndChallengesResDto findTeamDashboardsAndChallenges(String email, + String requestEmail, + Pageable pageable) { + TeamDashboardListResDto teamDashboardListResDto = teamDashboardService.findForTeamDashboard(requestEmail, pageable); + ChallengeListResDto challengeListResDto = challengeService.findChallengeForMemberId(requestEmail, pageable); - return TeamDashboardsAndChallengesResDto.of(teamDashboardListResDto, challengeListResDto); + PersonalDashboardPageListResDto personalDashboardPageListResDto; + + if (email.equals(requestEmail)) { + // 본인이면 isPublic과 상관없이 모든 대시보드 및 챌린지 조회 + personalDashboardPageListResDto = personalDashboardService.findForPersonalDashboard(requestEmail, pageable); + } else { + // 타인이면 isPublic이 true인 대시보드 및 챌린지 조회 + personalDashboardPageListResDto = personalDashboardService.findPublicPersonalDashboards(requestEmail, pageable); + } + return TeamDashboardsAndChallengesResDto.of(personalDashboardPageListResDto,teamDashboardListResDto, challengeListResDto); } private boolean isNicknameChanged(Member member, String newNickname) { diff --git a/src/test/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberControllerTest.java b/src/test/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberControllerTest.java index 8d391b41..20fcb1ff 100644 --- a/src/test/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberControllerTest.java +++ b/src/test/java/shop/kkeujeok/kkeujeokbackend/member/api/MemberControllerTest.java @@ -5,14 +5,12 @@ import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; -import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import shop.kkeujeok.kkeujeokbackend.challenge.api.dto.response.ChallengeListResDto; import shop.kkeujeok.kkeujeokbackend.common.annotation.ControllerTest; +import shop.kkeujeok.kkeujeokbackend.dashboard.personal.api.dto.response.PersonalDashboardPageListResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.team.api.dto.response.TeamDashboardListResDto; import shop.kkeujeok.kkeujeokbackend.global.annotationresolver.CurrentUserEmailArgumentResolver; import shop.kkeujeok.kkeujeokbackend.auth.api.dto.request.TokenReqDto; @@ -21,16 +19,13 @@ import shop.kkeujeok.kkeujeokbackend.member.domain.Member; import shop.kkeujeok.kkeujeokbackend.member.domain.Role; import shop.kkeujeok.kkeujeokbackend.member.domain.SocialType; -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.TeamDashboardsAndChallengesResDto; import java.util.ArrayList; -import java.util.Collections; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.when; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; @@ -49,7 +44,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.hamcrest.Matchers.is; -import static shop.kkeujeok.kkeujeokbackend.global.restdocs.RestDocsHandler.requestFields; import static shop.kkeujeok.kkeujeokbackend.global.restdocs.RestDocsHandler.responseFields; public class MemberControllerTest extends ControllerTest { @@ -121,19 +115,24 @@ void setUp(RestDocumentationContextProvider restDocumentation) { .andExpect(jsonPath("$.data").exists()); } - @DisplayName("팀 대시보드와 챌린지 정보를 가져옵니다.") + @DisplayName("대시보드와 챌린지 정보를 가져옵니다.") @Test void 팀_대시보드와_챌린지_정보를_가져옵니다() throws Exception { + // Given TeamDashboardsAndChallengesResDto resDto = new TeamDashboardsAndChallengesResDto( + new PersonalDashboardPageListResDto(new ArrayList<>(), new PageInfoResDto(0, 0, 0)), new TeamDashboardListResDto(new ArrayList<>(), new PageInfoResDto(0, 0, 0)), new ChallengeListResDto(new ArrayList<>(), new PageInfoResDto(0, 0, 0)) ); - when(myPageService.findTeamDashboardsAndChallenges(anyString(), any())).thenReturn(resDto); - when(tokenProvider.getUserEmailFromToken(any(TokenReqDto.class))).thenReturn("email"); + when(myPageService.findTeamDashboardsAndChallenges(anyString(), anyString(), any(PageRequest.class))) + .thenReturn(resDto); + + when(tokenProvider.getUserEmailFromToken(any(TokenReqDto.class))).thenReturn("test@example.com"); mockMvc.perform(get("/api/members/mypage/dashboard-challenges") .header("Authorization", "Bearer valid-token") + .param("requestEmail", "request@example.com") .param("page", "0") .param("size", "10")) .andDo(print()) @@ -144,12 +143,17 @@ void setUp(RestDocumentationContextProvider restDocumentation) { headerWithName("Authorization").description("JWT 토큰") ), queryParameters( + parameterWithName("requestEmail").description("조회할 사용자의 이메일"), 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.teamDashboardList.teamDashboardInfoResDto").description("팀 대시보드 정보 목록"), fieldWithPath("data.teamDashboardList.pageInfoResDto.currentPage").description("현재 페이지 번호"), fieldWithPath("data.teamDashboardList.pageInfoResDto.totalPages").description("총 페이지 수"), @@ -161,7 +165,7 @@ void setUp(RestDocumentationContextProvider restDocumentation) { ) )) .andExpect(status().isOk()) - .andExpect(jsonPath("$.message", is("팀 대시보드와 챌린지 정보 조회"))) + .andExpect(jsonPath("$.message", is("대시보드와 챌린지 정보 조회"))) .andExpect(jsonPath("$.data").exists()); } diff --git a/src/test/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageServiceTest.java b/src/test/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageServiceTest.java index d81ba853..d07beec3 100644 --- a/src/test/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageServiceTest.java +++ b/src/test/java/shop/kkeujeok/kkeujeokbackend/member/mypage/application/MyPageServiceTest.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.Pageable; import shop.kkeujeok.kkeujeokbackend.challenge.api.dto.response.ChallengeListResDto; import shop.kkeujeok.kkeujeokbackend.challenge.application.ChallengeService; +import shop.kkeujeok.kkeujeokbackend.dashboard.personal.application.PersonalDashboardService; import shop.kkeujeok.kkeujeokbackend.dashboard.team.api.dto.response.TeamDashboardListResDto; import shop.kkeujeok.kkeujeokbackend.dashboard.team.application.TeamDashboardService; import shop.kkeujeok.kkeujeokbackend.member.domain.Member; @@ -23,12 +24,10 @@ import java.util.Collections; import java.util.Optional; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.times; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class MyPageServiceTest { @@ -36,6 +35,9 @@ public class MyPageServiceTest { @Mock private MemberRepository memberRepository; + @Mock + private PersonalDashboardService personalDashboardService; + @Mock private TeamDashboardService teamDashboardService; @@ -123,7 +125,7 @@ void setUp() { when(challengeService.findChallengeForMemberId(email, pageable)).thenReturn(challengeListResDto); // When - TeamDashboardsAndChallengesResDto result = myPageService.findTeamDashboardsAndChallenges(email, pageable); + TeamDashboardsAndChallengesResDto result = myPageService.findTeamDashboardsAndChallenges(email,"test@example.com", pageable); // Then assertEquals(teamDashboardListResDto, result.teamDashboardList());