Skip to content

Commit

Permalink
Feature/#166 get my teams and studies에 개인정보 추가 (#169)
Browse files Browse the repository at this point in the history
* feat: getMyTeamsAndStudies에 정보 추가

* docs: feat: getMyTeamsAndStudies 테스트에 정보 추가

* docs: adoc 파일 정보 추가

* refactor : 불변list를 없애는 방법으로 코드 리팩토링

* fix : 사이드바 정보 api 도메인 위치 변경

* fix : 사이드바 정보 api 테스트 도메인 위치 변경
  • Loading branch information
JJimini authored Jun 29, 2024
1 parent 26f1a04 commit 4b76e51
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 60 deletions.
14 changes: 14 additions & 0 deletions src/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
9 changes: 9 additions & 0 deletions src/main/java/doore/member/api/MemberController.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,6 +20,7 @@
public class MemberController {

private final MemberCommandService memberCommandService;
private final MemberQueryService memberQueryService;

@PatchMapping("/teams/{teamId}/mandate/{newTeamLeaderId}") // 팀장
public ResponseEntity<Void> transferTeamLeader(@PathVariable final Long teamId,
Expand All @@ -40,4 +44,9 @@ public ResponseEntity<Void> deleteMember(@LoginMember final Member member) {
return ResponseEntity.noContent().build();
}

@GetMapping("/members/{memberId}")
public ResponseEntity<MemberAndMyTeamsAndStudiesResponse> getSideBarInfo(@PathVariable final Long memberId,
@LoginMember final Member member) {
return ResponseEntity.ok(memberQueryService.getSideBarInfo(memberId, member.getId()));
}
}
39 changes: 39 additions & 0 deletions src/main/java/doore/member/application/MemberQueryService.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MyTeamsAndStudiesResponse> myTeamsAndStudies
) {
public static MemberAndMyTeamsAndStudiesResponse of(final Member member,
List<MyTeamsAndStudiesResponse> myTeamsAndStudiesResponse) {
return MemberAndMyTeamsAndStudiesResponse.builder()
.id(member.getId())
.name(member.getName())
.imageUrl(member.getImageUrl())
.myTeamsAndStudies(myTeamsAndStudiesResponse)
.build();
}
}
7 changes: 0 additions & 7 deletions src/main/java/doore/team/api/TeamController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -101,12 +100,6 @@ public ResponseEntity<List<TeamReferenceResponse>> getMyTeams(@PathVariable fina
return ResponseEntity.ok(teamQueryService.findMyTeams(memberId, member.getId()));
}

@GetMapping("/members/{memberId}/studies")
public ResponseEntity<List<MyTeamsAndStudiesResponse>> getMyTeamsAndStudies(@PathVariable final Long memberId,
@LoginMember final Member member) {
return ResponseEntity.ok(teamQueryService.findMyTeamsAndStudies(memberId, member.getId()));
}

@GetMapping("/{teamId}") // 비회원
public ResponseEntity<TeamResponse> getTeam(@PathVariable final Long teamId) {
return ResponseEntity.ok(teamQueryService.findTeamByTeamId(teamId));
Expand Down
20 changes: 7 additions & 13 deletions src/main/java/doore/team/application/TeamQueryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,22 +48,17 @@ public List<TeamReferenceResponse> findMyTeams(final Long memberId, final Long t
.toList();
}

public List<MyTeamsAndStudiesResponse> findMyTeamsAndStudies(final Long memberId, final Long tokenMemberId) {
validateMember(memberId);
checkSameMemberIdAndTokenMemberId(memberId, tokenMemberId);
final List<MyTeamsAndStudiesResponse> myTeamsAndStudiesResponses = new ArrayList<>();
public List<MyTeamsAndStudiesResponse> findMyTeamsAndStudies(final Long memberId) {
final List<Team> myTeams = teamRepository.findAllByMemberId(memberId);

for (final Team myTeam : myTeams) {
final List<StudyNameResponse> studyNameResponses =
studyRepository.findAllByTeamId(myTeam.getId()).stream()
return myTeams.stream()
.map(team -> {
List<StudyNameResponse> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<StudyNameResponse> teamStudies
) {
public static MyTeamsAndStudiesResponse of(final Team team, final List<StudyNameResponse> teamStudies) {
return MyTeamsAndStudiesResponse.builder()
.teamId(team.getId())
.teamName(team.getName())
.teamStudies(teamStudies)
.build();
}
}
4 changes: 4 additions & 0 deletions src/test/java/doore/restdocs/RestDocsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -109,6 +110,9 @@ public abstract class RestDocsTest extends ApiTestHelper {
@MockBean
protected GardenQueryService gardenQueryService;

@MockBean
protected MemberQueryService memberQueryService;

@MockBean
protected JwtTokenGenerator jwtTokenGenerator;

Expand Down
46 changes: 46 additions & 0 deletions src/test/java/doore/restdocs/docs/MemberApiDocsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,4 +70,41 @@ void setUp() {
.andDo(document("delete-member"));
}

@Test
@DisplayName("[성공] 사이드바에 들어가는 정보를 조회한다.")
void getSideBarInfo_사이드바에_들어가는_정보를_조회한다_성공() throws Exception {
//given
final Long memberId = 1L;
final List<StudyNameResponse> studyResponses = List.of(
new StudyNameResponse(1L, "알고리즘 스터디"),
new StudyNameResponse(2L, "개발 스터디")
);
final List<MyTeamsAndStudiesResponse> 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));
}
}
40 changes: 0 additions & 40 deletions src/test/java/doore/restdocs/docs/TeamApiDocsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -248,40 +242,6 @@ void setUp() {

}

@Test
@DisplayName("나의 팀과 스터디 목록을 조회한다")
void 나의_팀과_스터디_목록을_조회한다() throws Exception {
//given
final Long memberId = 1L;
final List<StudyNameResponse> studyResponses = List.of(
new StudyNameResponse(1L, "알고리즘 스터디"),
new StudyNameResponse(2L, "개발 스터디")
);
final List<MyTeamsAndStudiesResponse> 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 {
Expand Down

0 comments on commit 4b76e51

Please sign in to comment.