From 4b76e51c8001f954aec7d5667b062f00f10a633a Mon Sep 17 00:00:00 2001 From: JIMIN LEE <107912763+JJimini@users.noreply.github.com> Date: Sun, 30 Jun 2024 01:12:28 +0900 Subject: [PATCH] =?UTF-8?q?Feature/#166=20get=20my=20teams=20and=20studies?= =?UTF-8?q?=EC=97=90=20=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#169)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: getMyTeamsAndStudies에 정보 추가 * docs: feat: getMyTeamsAndStudies 테스트에 정보 추가 * docs: adoc 파일 정보 추가 * refactor : 불변list를 없애는 방법으로 코드 리팩토링 * fix : 사이드바 정보 api 도메인 위치 변경 * fix : 사이드바 정보 api 테스트 도메인 위치 변경 --- src/docs/asciidoc/member.adoc | 14 ++++++ .../doore/member/api/MemberController.java | 9 ++++ .../application/MemberQueryService.java | 39 ++++++++++++++++ .../MemberAndMyTeamsAndStudiesResponse.java | 24 ++++++++++ .../java/doore/team/api/TeamController.java | 7 --- .../team/application/TeamQueryService.java | 20 +++----- .../response/MyTeamsAndStudiesResponse.java | 10 ++++ .../java/doore/restdocs/RestDocsTest.java | 4 ++ .../restdocs/docs/MemberApiDocsTest.java | 46 +++++++++++++++++++ .../doore/restdocs/docs/TeamApiDocsTest.java | 40 ---------------- 10 files changed, 153 insertions(+), 60 deletions(-) create mode 100644 src/main/java/doore/member/application/MemberQueryService.java create mode 100644 src/main/java/doore/member/application/dto/response/MemberAndMyTeamsAndStudiesResponse.java diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index c7049897..9d299924 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -43,3 +43,17 @@ include::{snippets}/delete-member/http-response.adoc[] .HTTP Response include::{snippets}/delete-member/http-response.adoc[] + +== `GET`: 사이드바 정보 조회 + +.HTTP Request +include::{snippets}/get-sidebar-info/http-request.adoc[] + +.Path Parameters +include::{snippets}/get-sidebar-info/path-parameters.adoc[] + +.HTTP Response +include::{snippets}/get-sidebar-info/http-response.adoc[] + +.Response Body's Fields +include::{snippets}/get-sidebar-info/response-body.adoc[] diff --git a/src/main/java/doore/member/api/MemberController.java b/src/main/java/doore/member/api/MemberController.java index 51e109ef..1611084f 100644 --- a/src/main/java/doore/member/api/MemberController.java +++ b/src/main/java/doore/member/api/MemberController.java @@ -1,12 +1,15 @@ package doore.member.api; import doore.member.application.MemberCommandService; +import doore.member.application.MemberQueryService; +import doore.member.application.dto.response.MemberAndMyTeamsAndStudiesResponse; import doore.member.domain.Member; import doore.resolver.LoginMember; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +20,7 @@ public class MemberController { private final MemberCommandService memberCommandService; + private final MemberQueryService memberQueryService; @PatchMapping("/teams/{teamId}/mandate/{newTeamLeaderId}") // 팀장 public ResponseEntity transferTeamLeader(@PathVariable final Long teamId, @@ -40,4 +44,9 @@ public ResponseEntity deleteMember(@LoginMember final Member member) { return ResponseEntity.noContent().build(); } + @GetMapping("/members/{memberId}") + public ResponseEntity getSideBarInfo(@PathVariable final Long memberId, + @LoginMember final Member member) { + return ResponseEntity.ok(memberQueryService.getSideBarInfo(memberId, member.getId())); + } } diff --git a/src/main/java/doore/member/application/MemberQueryService.java b/src/main/java/doore/member/application/MemberQueryService.java new file mode 100644 index 00000000..4ccdb320 --- /dev/null +++ b/src/main/java/doore/member/application/MemberQueryService.java @@ -0,0 +1,39 @@ +package doore.member.application; + +import static doore.member.exception.MemberExceptionType.NOT_FOUND_MEMBER; +import static doore.member.exception.MemberExceptionType.UNAUTHORIZED; + +import doore.member.application.dto.response.MemberAndMyTeamsAndStudiesResponse; +import doore.member.domain.Member; +import doore.member.domain.repository.MemberRepository; +import doore.member.exception.MemberException; +import doore.team.application.TeamQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MemberQueryService { + private final MemberRepository memberRepository; + private final TeamQueryService teamQueryService; + + public MemberAndMyTeamsAndStudiesResponse getSideBarInfo(final Long memberId, final Long tokenMemberId) { + validateMember(memberId); + checkSameMemberIdAndTokenMemberId(memberId, tokenMemberId); + final Member member = memberRepository.findById(tokenMemberId) + .orElseThrow(() -> new MemberException(NOT_FOUND_MEMBER)); + return MemberAndMyTeamsAndStudiesResponse.of(member, teamQueryService.findMyTeamsAndStudies(memberId)); + } + + private void validateMember(final Long memberId) { + memberRepository.findById(memberId).orElseThrow(() -> new MemberException(NOT_FOUND_MEMBER)); + } + + private void checkSameMemberIdAndTokenMemberId(final Long memberId, final Long tokenMemberId) { + if (!memberId.equals(tokenMemberId)) { + throw new MemberException(UNAUTHORIZED); + } + } +} diff --git a/src/main/java/doore/member/application/dto/response/MemberAndMyTeamsAndStudiesResponse.java b/src/main/java/doore/member/application/dto/response/MemberAndMyTeamsAndStudiesResponse.java new file mode 100644 index 00000000..7542f421 --- /dev/null +++ b/src/main/java/doore/member/application/dto/response/MemberAndMyTeamsAndStudiesResponse.java @@ -0,0 +1,24 @@ +package doore.member.application.dto.response; + +import doore.member.domain.Member; +import doore.team.application.dto.response.MyTeamsAndStudiesResponse; +import java.util.List; +import lombok.Builder; + +@Builder +public record MemberAndMyTeamsAndStudiesResponse( + Long id, + String name, + String imageUrl, + List myTeamsAndStudies +) { + public static MemberAndMyTeamsAndStudiesResponse of(final Member member, + List myTeamsAndStudiesResponse) { + return MemberAndMyTeamsAndStudiesResponse.builder() + .id(member.getId()) + .name(member.getName()) + .imageUrl(member.getImageUrl()) + .myTeamsAndStudies(myTeamsAndStudiesResponse) + .build(); + } +} diff --git a/src/main/java/doore/team/api/TeamController.java b/src/main/java/doore/team/api/TeamController.java index e97ec76a..5a641153 100644 --- a/src/main/java/doore/team/api/TeamController.java +++ b/src/main/java/doore/team/api/TeamController.java @@ -7,7 +7,6 @@ import doore.team.application.dto.request.TeamCreateRequest; import doore.team.application.dto.request.TeamInviteCodeRequest; import doore.team.application.dto.request.TeamUpdateRequest; -import doore.team.application.dto.response.MyTeamsAndStudiesResponse; import doore.team.application.dto.response.TeamInviteCodeResponse; import doore.team.application.dto.response.TeamRankResponse; import doore.team.application.dto.response.TeamReferenceResponse; @@ -101,12 +100,6 @@ public ResponseEntity> getMyTeams(@PathVariable fina return ResponseEntity.ok(teamQueryService.findMyTeams(memberId, member.getId())); } - @GetMapping("/members/{memberId}/studies") - public ResponseEntity> getMyTeamsAndStudies(@PathVariable final Long memberId, - @LoginMember final Member member) { - return ResponseEntity.ok(teamQueryService.findMyTeamsAndStudies(memberId, member.getId())); - } - @GetMapping("/{teamId}") // 비회원 public ResponseEntity getTeam(@PathVariable final Long teamId) { return ResponseEntity.ok(teamQueryService.findTeamByTeamId(teamId)); diff --git a/src/main/java/doore/team/application/TeamQueryService.java b/src/main/java/doore/team/application/TeamQueryService.java index 901d3acb..d13097dc 100644 --- a/src/main/java/doore/team/application/TeamQueryService.java +++ b/src/main/java/doore/team/application/TeamQueryService.java @@ -22,7 +22,6 @@ import doore.team.domain.Team; import doore.team.domain.TeamRepository; import doore.team.exception.TeamException; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; @@ -49,22 +48,17 @@ public List findMyTeams(final Long memberId, final Long t .toList(); } - public List findMyTeamsAndStudies(final Long memberId, final Long tokenMemberId) { - validateMember(memberId); - checkSameMemberIdAndTokenMemberId(memberId, tokenMemberId); - final List myTeamsAndStudiesResponses = new ArrayList<>(); + public List findMyTeamsAndStudies(final Long memberId) { final List myTeams = teamRepository.findAllByMemberId(memberId); - for (final Team myTeam : myTeams) { - final List studyNameResponses = - studyRepository.findAllByTeamId(myTeam.getId()).stream() + return myTeams.stream() + .map(team -> { + List studyNameResponses = studyRepository.findAllByTeamId(team.getId()).stream() .map(StudyNameResponse::from) .toList(); - final MyTeamsAndStudiesResponse myTeamsAndStudiesResponse = - new MyTeamsAndStudiesResponse(myTeam.getId(), myTeam.getName(), studyNameResponses); - myTeamsAndStudiesResponses.add(myTeamsAndStudiesResponse); - } - return myTeamsAndStudiesResponses; + return MyTeamsAndStudiesResponse.of(team, studyNameResponses); + }) + .toList(); } public TeamResponse findTeamByTeamId(final Long teamId) { diff --git a/src/main/java/doore/team/application/dto/response/MyTeamsAndStudiesResponse.java b/src/main/java/doore/team/application/dto/response/MyTeamsAndStudiesResponse.java index 1d993ad9..934cc5c5 100644 --- a/src/main/java/doore/team/application/dto/response/MyTeamsAndStudiesResponse.java +++ b/src/main/java/doore/team/application/dto/response/MyTeamsAndStudiesResponse.java @@ -1,11 +1,21 @@ package doore.team.application.dto.response; import doore.study.application.dto.response.StudyNameResponse; +import doore.team.domain.Team; import java.util.List; +import lombok.Builder; +@Builder public record MyTeamsAndStudiesResponse( Long teamId, String teamName, List teamStudies ) { + public static MyTeamsAndStudiesResponse of(final Team team, final List teamStudies) { + return MyTeamsAndStudiesResponse.builder() + .teamId(team.getId()) + .teamName(team.getName()) + .teamStudies(teamStudies) + .build(); + } } diff --git a/src/test/java/doore/restdocs/RestDocsTest.java b/src/test/java/doore/restdocs/RestDocsTest.java index d02a8e21..797f0f0b 100644 --- a/src/test/java/doore/restdocs/RestDocsTest.java +++ b/src/test/java/doore/restdocs/RestDocsTest.java @@ -19,6 +19,7 @@ import doore.member.api.MemberController; import doore.member.api.MemberTeamController; import doore.member.application.MemberCommandService; +import doore.member.application.MemberQueryService; import doore.member.application.MemberTeamQueryService; import doore.member.domain.repository.MemberRepository; import doore.study.api.CurriculumItemController; @@ -109,6 +110,9 @@ public abstract class RestDocsTest extends ApiTestHelper { @MockBean protected GardenQueryService gardenQueryService; + @MockBean + protected MemberQueryService memberQueryService; + @MockBean protected JwtTokenGenerator jwtTokenGenerator; diff --git a/src/test/java/doore/restdocs/docs/MemberApiDocsTest.java b/src/test/java/doore/restdocs/docs/MemberApiDocsTest.java index 2619f388..fb472005 100644 --- a/src/test/java/doore/restdocs/docs/MemberApiDocsTest.java +++ b/src/test/java/doore/restdocs/docs/MemberApiDocsTest.java @@ -4,16 +4,25 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import doore.member.application.dto.response.MemberAndMyTeamsAndStudiesResponse; import doore.restdocs.RestDocsTest; +import doore.study.application.dto.response.StudyNameResponse; +import doore.team.application.dto.response.MyTeamsAndStudiesResponse; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.ResponseFieldsSnippet; +import org.springframework.restdocs.request.PathParametersSnippet; public class MemberApiDocsTest extends RestDocsTest { private String accessToken; @@ -61,4 +70,41 @@ void setUp() { .andDo(document("delete-member")); } + @Test + @DisplayName("[성공] 사이드바에 들어가는 정보를 조회한다.") + void getSideBarInfo_사이드바에_들어가는_정보를_조회한다_성공() throws Exception { + //given + final Long memberId = 1L; + final List studyResponses = List.of( + new StudyNameResponse(1L, "알고리즘 스터디"), + new StudyNameResponse(2L, "개발 스터디") + ); + final List response = List.of( + new MyTeamsAndStudiesResponse(1L, "BDD", studyResponses) + ); + final MemberAndMyTeamsAndStudiesResponse memberAndMyTeamsAndStudiesResponse = new MemberAndMyTeamsAndStudiesResponse( + 1L, "이름", "프로필사진", response); + final PathParametersSnippet pathParameters = pathParameters( + parameterWithName("memberId").description("사이드바 정보목록을 조회하는 회원 ID") + ); + final ResponseFieldsSnippet responseFieldsSnippet = responseFields( + numberFieldWithPath("id", "멤버 ID"), + stringFieldWithPath("name", "멤버 이름"), + stringFieldWithPath("imageUrl", "멤버 프로필 경로"), + numberFieldWithPath("myTeamsAndStudies[].teamId", "팀 ID"), + stringFieldWithPath("myTeamsAndStudies[].teamName", "팀 이름"), + numberFieldWithPath("myTeamsAndStudies[].teamStudies[].id", "팀에 포함되는 스터디 id"), + stringFieldWithPath("myTeamsAndStudies.[].teamStudies[].name", "팀에 포함되는 스터디 이름") + ); + + //when + when(memberQueryService.getSideBarInfo(any(), any())).thenReturn(memberAndMyTeamsAndStudiesResponse); + + //then + mockMvc.perform(get("/members/{memberId}", memberId) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(document("get-sidebar-info", pathParameters, responseFieldsSnippet)); + } } diff --git a/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java b/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java index 6d8b74e7..9a5f4f76 100644 --- a/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java +++ b/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java @@ -21,22 +21,16 @@ import doore.garden.application.dto.response.DayGardenResponse; import doore.restdocs.RestDocsTest; -import doore.study.application.dto.response.StudyNameResponse; import doore.team.application.dto.request.TeamCreateRequest; import doore.team.application.dto.request.TeamInviteCodeRequest; import doore.team.application.dto.request.TeamUpdateRequest; -import doore.team.application.dto.response.MyTeamsAndStudiesResponse; import doore.team.application.dto.response.TeamInviteCodeResponse; import doore.team.application.dto.response.TeamRankResponse; import doore.team.application.dto.response.TeamReferenceResponse; import doore.team.application.dto.response.TeamResponse; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -248,40 +242,6 @@ void setUp() { } - @Test - @DisplayName("나의 팀과 스터디 목록을 조회한다") - void 나의_팀과_스터디_목록을_조회한다() throws Exception { - //given - final Long memberId = 1L; - final List studyResponses = List.of( - new StudyNameResponse(1L, "알고리즘 스터디"), - new StudyNameResponse(2L, "개발 스터디") - ); - final List response = List.of( - new MyTeamsAndStudiesResponse(1L, "BDD", studyResponses) - ); - - final PathParametersSnippet pathParameters = pathParameters( - parameterWithName("memberId").description("팀 목록을 조회하고자 하는 회원 ID") - ); - final ResponseFieldsSnippet responseFieldsSnippet = responseFields( - numberFieldWithPath("[].teamId", "팀의 ID"), - stringFieldWithPath("[].teamName", "팀의 이름"), - numberFieldWithPath("[].teamStudies.[].id", "팀에 포함되는 스터디 id"), - stringFieldWithPath("[].teamStudies.[].name", "팀에 포함되는 스터디 이름") - ); - - //when - when(teamQueryService.findMyTeamsAndStudies(any(), any())).thenReturn(response); - - //then - mockMvc.perform(get("/teams/members/{memberId}/studies", memberId) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("my-teams-and-studies", pathParameters, responseFieldsSnippet)); - } - @Test @DisplayName("팀 상세목록을 조회한다.") void 팀_상세목록을_조회한다() throws Exception {