From da5d31e06c7df8bca0577f8cb207bb59ba1ee17a Mon Sep 17 00:00:00 2001 From: JIMIN LEE <107912763+JJimini@users.noreply.github.com> Date: Sun, 30 Jun 2024 03:29:21 +0900 Subject: [PATCH] =?UTF-8?q?Feature/#171=20=ED=8C=80=20=EC=8A=A4=ED=84=B0?= =?UTF-8?q?=EB=94=94=20=EC=83=81=EC=84=B8=EC=A1=B0=ED=9A=8C=EC=97=90=20lea?= =?UTF-8?q?der=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#176)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 팀 상세조회에 teamLeaderId 정보 추가 * test: 팀 상세조회 테스트에 teamLeaderId 정보 추가 * feat: study상세조회에 studyLeaderId 추가 * test: study상세조회 테스트에 studyLeaderId 추가 --- .../repository/StudyRoleRepository.java | 3 ++ .../domain/repository/TeamRoleRepository.java | 3 ++ .../java/doore/study/api/StudyController.java | 5 ++-- .../study/application/StudyQueryService.java | 12 ++++---- .../dto/response/StudyReferenceResponse.java | 27 +++++++++++++++++ .../dto/response/StudyResponse.java | 7 +++-- .../team/application/TeamQueryService.java | 5 +++- .../dto/response/TeamResponse.java | 6 ++-- .../doore/restdocs/docs/StudyApiDocsTest.java | 29 ++++++++++++------- .../doore/restdocs/docs/TeamApiDocsTest.java | 11 +++---- .../application/StudyQueryServiceTest.java | 20 +++++-------- .../application/TeamQueryServiceTest.java | 17 +++++++++-- 12 files changed, 100 insertions(+), 45 deletions(-) create mode 100644 src/main/java/doore/study/application/dto/response/StudyReferenceResponse.java diff --git a/src/main/java/doore/member/domain/repository/StudyRoleRepository.java b/src/main/java/doore/member/domain/repository/StudyRoleRepository.java index 07356e69..07095930 100644 --- a/src/main/java/doore/member/domain/repository/StudyRoleRepository.java +++ b/src/main/java/doore/member/domain/repository/StudyRoleRepository.java @@ -4,8 +4,11 @@ import doore.member.domain.StudyRoleType; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface StudyRoleRepository extends JpaRepository { Optional findStudyRoleByStudyIdAndMemberId(Long studyId, Long memberId); Optional findStudyRoleByStudyIdAndStudyRoleType(Long studyId, StudyRoleType studyRoleType); + @Query("SELECT sr.memberId FROM StudyRole sr WHERE sr.studyId = :studyId AND sr.studyRoleType = 'ROLE_스터디장'") + Long findLeaderIdByStudyId(Long studyId); } diff --git a/src/main/java/doore/member/domain/repository/TeamRoleRepository.java b/src/main/java/doore/member/domain/repository/TeamRoleRepository.java index c96b1bc3..81f29b4d 100644 --- a/src/main/java/doore/member/domain/repository/TeamRoleRepository.java +++ b/src/main/java/doore/member/domain/repository/TeamRoleRepository.java @@ -4,9 +4,12 @@ import doore.member.domain.TeamRoleType; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface TeamRoleRepository extends JpaRepository { Optional findTeamRoleByTeamIdAndMemberId(Long teamId, Long memberId); Optional findTeamRoleByTeamIdAndTeamRoleType(Long teamId, TeamRoleType teamRoleType); Optional findTeamRoleByMemberId(Long memberId); + @Query("SELECT tr.memberId FROM TeamRole tr WHERE tr.teamId = :teamId AND tr.teamRoleType = 'ROLE_팀장'") + Long findLeaderIdByTeamId(Long teamId); } diff --git a/src/main/java/doore/study/api/StudyController.java b/src/main/java/doore/study/api/StudyController.java index 674baf01..39ca0ec4 100644 --- a/src/main/java/doore/study/api/StudyController.java +++ b/src/main/java/doore/study/api/StudyController.java @@ -6,6 +6,7 @@ import doore.study.application.StudyQueryService; import doore.study.application.dto.request.StudyCreateRequest; import doore.study.application.dto.request.StudyUpdateRequest; +import doore.study.application.dto.response.StudyReferenceResponse; import doore.study.application.dto.response.StudyResponse; import jakarta.validation.Valid; import java.util.List; @@ -70,8 +71,8 @@ public ResponseEntity terminateStudy(@PathVariable final Long studyId, @Lo } @GetMapping("/studies/members/{memberId}") // 회원 - public ResponseEntity> getMyStudies(@PathVariable final Long memberId, - @LoginMember final Member member) { + public ResponseEntity> getMyStudies(@PathVariable final Long memberId, + @LoginMember final Member member) { // TODO: 3/22/24 토큰의 주인과 회원아이디가 같은지 검증 (2024/5/15 완료) return ResponseEntity.ok(studyQueryService.findMyStudies(memberId, member.getId())); } diff --git a/src/main/java/doore/study/application/StudyQueryService.java b/src/main/java/doore/study/application/StudyQueryService.java index a08426c9..0f2c5f89 100644 --- a/src/main/java/doore/study/application/StudyQueryService.java +++ b/src/main/java/doore/study/application/StudyQueryService.java @@ -18,6 +18,7 @@ import doore.member.domain.repository.ParticipantRepository; import doore.member.domain.repository.StudyRoleRepository; import doore.member.exception.MemberException; +import doore.study.application.dto.response.StudyReferenceResponse; import doore.study.application.dto.response.StudyResponse; import doore.study.domain.Study; import doore.study.domain.repository.CurriculumItemRepository; @@ -47,6 +48,7 @@ public class StudyQueryService { public StudyResponse findStudyById(final Long studyId) { final Study study = studyRepository.findById(studyId).orElseThrow(() -> new StudyException(NOT_FOUND_STUDY)); + final Long studyLeaderId = studyRoleRepository.findLeaderIdByStudyId(study.getId()); final Team team = teamRepository.findById(study.getTeamId()) .orElseThrow(() -> new TeamException(NOT_FOUND_TEAM)); @@ -54,10 +56,10 @@ public StudyResponse findStudyById(final Long studyId) { .orElseThrow(() -> new CropException(NOT_FOUND_CROP)); final long studyProgressRatio = checkStudyProgressRatio(studyId); - return StudyResponse.of(study, team, crop, studyProgressRatio); + return StudyResponse.of(study, team, crop, studyProgressRatio, studyLeaderId); } - public List findMyStudies(final Long memberId, final Long tokenMemberId) { + public List findMyStudies(final Long memberId, final Long tokenMemberId) { checkSameMemberIdAndTokenMemberId(memberId, tokenMemberId); final List participants = participantRepository.findByMemberId(memberId); @@ -67,11 +69,7 @@ public List findMyStudies(final Long memberId, final Long tokenMe final List studies = studyRepository.findAllById(studyIds); return studies.stream() - .map(study -> StudyResponse.of(study, - teamRepository.findById(study.getTeamId()).orElseThrow(() -> new TeamException(NOT_FOUND_TEAM)), - cropRepository.findById(study.getCropId()) - .orElseThrow(() -> new CropException(NOT_FOUND_CROP)), - checkStudyProgressRatio(study.getId()))) + .map(study -> StudyReferenceResponse.of(study, checkStudyProgressRatio(study.getId()))) .toList(); } diff --git a/src/main/java/doore/study/application/dto/response/StudyReferenceResponse.java b/src/main/java/doore/study/application/dto/response/StudyReferenceResponse.java new file mode 100644 index 00000000..806e6320 --- /dev/null +++ b/src/main/java/doore/study/application/dto/response/StudyReferenceResponse.java @@ -0,0 +1,27 @@ +package doore.study.application.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import doore.study.domain.Study; +import doore.study.domain.StudyStatus; +import java.time.LocalDate; +import lombok.Builder; + +@Builder +public record StudyReferenceResponse( + Long id, + String name, + String description, + @JsonFormat(pattern = "yyyy-MM-dd") + LocalDate startDate, + @JsonFormat(pattern = "yyyy-MM-dd") + LocalDate endDate, + StudyStatus status, + Long cropId, + long studyProgressRatio +) { + + public static StudyReferenceResponse of(final Study study, final long studyProgressRatio) { + return new StudyReferenceResponse(study.getId(), study.getName(), study.getDescription(), study.getStartDate(), + study.getEndDate(), study.getStatus(), study.getCropId(), studyProgressRatio); + } +} diff --git a/src/main/java/doore/study/application/dto/response/StudyResponse.java b/src/main/java/doore/study/application/dto/response/StudyResponse.java index a907381d..c7ec5a18 100644 --- a/src/main/java/doore/study/application/dto/response/StudyResponse.java +++ b/src/main/java/doore/study/application/dto/response/StudyResponse.java @@ -22,10 +22,12 @@ public record StudyResponse( StudyStatus status, TeamReferenceResponse teamReference, CropReferenceResponse cropReference, - long studyProgressRatio + long studyProgressRatio, + Long studyLeaderId ) { - public static StudyResponse of(final Study study, final Team team, final Crop crop, final long studyProgressRatio) { + public static StudyResponse of(final Study study, final Team team, final Crop crop, final long studyProgressRatio, + final Long studyLeaderId) { return StudyResponse.builder() .id(study.getId()) .name(study.getName()) @@ -36,6 +38,7 @@ public static StudyResponse of(final Study study, final Team team, final Crop cr .teamReference(TeamReferenceResponse.from(team)) .cropReference(CropReferenceResponse.from(crop)) .studyProgressRatio(studyProgressRatio) + .studyLeaderId(studyLeaderId) .build(); } } diff --git a/src/main/java/doore/team/application/TeamQueryService.java b/src/main/java/doore/team/application/TeamQueryService.java index d13097dc..f6174c81 100644 --- a/src/main/java/doore/team/application/TeamQueryService.java +++ b/src/main/java/doore/team/application/TeamQueryService.java @@ -12,6 +12,7 @@ import doore.member.domain.MemberTeam; import doore.member.domain.repository.MemberRepository; import doore.member.domain.repository.MemberTeamRepository; +import doore.member.domain.repository.TeamRoleRepository; import doore.member.exception.MemberException; import doore.study.application.dto.response.StudyNameResponse; import doore.study.domain.repository.StudyRepository; @@ -37,6 +38,7 @@ public class TeamQueryService { private final StudyRepository studyRepository; private final AttendanceRepository attendanceRepository; private final MemberTeamRepository memberTeamRepository; + private final TeamRoleRepository teamRoleRepository; private final GardenQueryService gardenQueryService; public List findMyTeams(final Long memberId, final Long tokenMemberId) { @@ -75,8 +77,9 @@ public TeamResponse findTeamByTeamId(final Long teamId) { final long countAttendanceMemberTeam = attendances.size(); final long attendanceRatio = countMemberTeam > 0 ? (long) ((countAttendanceMemberTeam * 100.0) / countMemberTeam) : 0; + final Long teamLeaderId = teamRoleRepository.findLeaderIdByTeamId(teamId); - return TeamResponse.of(team, attendanceRatio); + return TeamResponse.of(team, attendanceRatio, teamLeaderId); } private void validateMember(final Long memberId) { diff --git a/src/main/java/doore/team/application/dto/response/TeamResponse.java b/src/main/java/doore/team/application/dto/response/TeamResponse.java index 4eeceb1c..d458c9ac 100644 --- a/src/main/java/doore/team/application/dto/response/TeamResponse.java +++ b/src/main/java/doore/team/application/dto/response/TeamResponse.java @@ -9,15 +9,17 @@ public record TeamResponse( String name, String description, String imageUrl, - long attendanceRatio + long attendanceRatio, + Long teamLeaderId ) { - public static TeamResponse of(final Team team, final long attendanceRatio) { + public static TeamResponse of(final Team team, final long attendanceRatio, final Long teamLeaderId) { return TeamResponse.builder() .id(team.getId()) .name(team.getName()) .description(team.getDescription()) .imageUrl(team.getImageUrl()) .attendanceRatio(attendanceRatio) + .teamLeaderId(teamLeaderId) .build(); } } diff --git a/src/test/java/doore/restdocs/docs/StudyApiDocsTest.java b/src/test/java/doore/restdocs/docs/StudyApiDocsTest.java index 0654bab7..2c81524a 100644 --- a/src/test/java/doore/restdocs/docs/StudyApiDocsTest.java +++ b/src/test/java/doore/restdocs/docs/StudyApiDocsTest.java @@ -14,6 +14,7 @@ import doore.restdocs.RestDocsTest; import doore.study.application.dto.request.StudyCreateRequest; import doore.study.application.dto.request.StudyUpdateRequest; +import doore.study.application.dto.response.StudyReferenceResponse; import doore.study.application.dto.response.StudyResponse; import doore.study.domain.StudyStatus; import doore.team.application.dto.response.TeamReferenceResponse; @@ -95,6 +96,20 @@ private StudyResponse getStudyResponse() { .teamReference(teamReferenceResponse) .cropReference(cropReferenceResponse) .studyProgressRatio(50) + .studyLeaderId(1L) + .build(); + } + + private StudyReferenceResponse getStudyReferenceResponse() { + return StudyReferenceResponse.builder() + .id(1L) + .name("알고리즘") + .description("알고리즘 스터디입니다.") + .startDate(LocalDate.parse("2020-01-01")) + .endDate(LocalDate.parse("2020-01-02")) + .status(StudyStatus.IN_PROGRESS) + .cropId(1L) + .studyProgressRatio(50) .build(); } @@ -170,9 +185,9 @@ private StudyResponse getStudyResponse() { @DisplayName("나의 스터디 목록을 조회한다.") public void 나의_스터디_목록을_조회한다() throws Exception { final Long memberId = 1L; - final List response = List.of( - getStudyResponse(), - getStudyResponse() + final List response = List.of( + getStudyReferenceResponse(), + getStudyReferenceResponse() ); final ResponseFieldsSnippet responseFieldsSnippet = responseFields( @@ -182,13 +197,7 @@ private StudyResponse getStudyResponse() { stringFieldWithPath("[].startDate", "스터디의 시작일"), stringFieldWithPath("[].endDate", "스터디의 종료일"), stringFieldWithPath("[].status", "스터디의 진행 상태"), - numberFieldWithPath("[].teamReference.id", "스터디가 속한 팀의 ID"), - stringFieldWithPath("[].teamReference.name", "스터디가 속한 팀의 이름"), - stringFieldWithPath("[].teamReference.description", "스터디가 속한 팀의 설명"), - stringFieldWithPath("[].teamReference.imageUrl", "스터디가 속한 팀의 이미지 url"), - numberFieldWithPath("[].cropReference.id", "스터디의 작물의 ID"), - stringFieldWithPath("[].cropReference.name", "스터디의 작물의 이름"), - stringFieldWithPath("[].cropReference.imageUrl", "스터디의 작물의 이미지 url"), + numberFieldWithPath("[].cropId", "스터디의 작물 ID"), numberFieldWithPath("[].studyProgressRatio", "스터디 진행률") ); diff --git a/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java b/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java index 9a5f4f76..23d0050c 100644 --- a/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java +++ b/src/test/java/doore/restdocs/docs/TeamApiDocsTest.java @@ -247,7 +247,7 @@ void setUp() { void 팀_상세목록을_조회한다() throws Exception { final Long teamId = 1L; - final TeamResponse teamResponse = new TeamResponse(1L, "팀 이름", "팀 설명", "1234", 50); + final TeamResponse teamResponse = new TeamResponse(1L, "팀 이름", "팀 설명", "1234", 50, 1L); final PathParametersSnippet pathParameters = pathParameters( parameterWithName("teamId").description("조회하고자 하는 팀 ID") ); @@ -257,7 +257,8 @@ void setUp() { stringFieldWithPath("name", "팀 이름"), stringFieldWithPath("description", "팀 설명"), stringFieldWithPath("imageUrl", "이미지 url"), - numberFieldWithPath("attendanceRatio", "출석률") + numberFieldWithPath("attendanceRatio", "출석률"), + numberFieldWithPath("teamLeaderId", "팀장 ID") ); when(teamQueryService.findTeamByTeamId(teamId)).thenReturn(teamResponse); @@ -275,15 +276,15 @@ void setUp() { final List teamRankResponses = new ArrayList<>(); final List gardenResponse = List.of( DayGardenResponse.builder() - .contributeDate(LocalDate.of(2024,1,1)) + .contributeDate(LocalDate.of(2024, 1, 1)) .contributeCount(2) .build(), DayGardenResponse.builder() - .contributeDate(LocalDate.of(2024,1,2)) + .contributeDate(LocalDate.of(2024, 1, 2)) .contributeCount(1) .build(), DayGardenResponse.builder() - .contributeDate(LocalDate.of(2024,1,7)) + .contributeDate(LocalDate.of(2024, 1, 7)) .contributeCount(5) .build() ); diff --git a/src/test/java/doore/study/application/StudyQueryServiceTest.java b/src/test/java/doore/study/application/StudyQueryServiceTest.java index 12cb58dd..d9d5f330 100644 --- a/src/test/java/doore/study/application/StudyQueryServiceTest.java +++ b/src/test/java/doore/study/application/StudyQueryServiceTest.java @@ -5,13 +5,13 @@ import static doore.member.exception.MemberExceptionType.UNAUTHORIZED; import static doore.study.CurriculumItemFixture.curriculumItem; import static doore.study.ParticipantCurriculumItemFixture.participantCurriculumItem; +import static doore.study.StudyFixture.algorithmStudy; import static doore.study.StudyFixture.createStudy; import static doore.study.exception.StudyExceptionType.NOT_FOUND_STUDY; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; -import doore.crop.domain.Crop; import doore.crop.domain.repository.CropRepository; import doore.helper.IntegrationTest; import doore.member.domain.Member; @@ -21,7 +21,7 @@ import doore.member.domain.repository.ParticipantRepository; import doore.member.domain.repository.StudyRoleRepository; import doore.member.exception.MemberException; -import doore.study.application.dto.response.StudyResponse; +import doore.study.application.dto.response.StudyReferenceResponse; import doore.study.domain.CurriculumItem; import doore.study.domain.ParticipantCurriculumItem; import doore.study.domain.Study; @@ -102,22 +102,16 @@ class studyTest { } @Test - @Disabled @DisplayName("[성공] 내가 속한 스터디 목록을 조회할 수 있다.") void findMyStudies_내가_속한_스터디_목록을_조회할_수_있다_성공() { // given final Long tokenMemberId = member.getId(); - final Study anotherStudy = studyRepository.save(createStudy()); + final Study anotherStudy = studyRepository.save(algorithmStudy()); final Participant participantForStudy = participantRepository.save( Participant.builder().member(member).studyId(study.getId()).build()); final Participant participantForAnotherStudy = participantRepository.save( Participant.builder().member(member).studyId(anotherStudy.getId()).build()); - final Team teamOfStudy = teamRepository.findById(study.getTeamId()).orElseThrow(); - final Team teamOfAnotherStudy = teamRepository.findById(anotherStudy.getTeamId()).orElseThrow(); - final Crop cropOfTeam = cropRepository.findById(study.getCropId()).orElseThrow(); - final Crop cropOfAnotherTeam = cropRepository.findById(anotherStudy.getCropId()).orElseThrow(); - final CurriculumItem curriculumItemForStudy1 = curriculumItemRepository.save(curriculumItem(study)); final CurriculumItem curriculumItemForStudy2 = curriculumItemRepository.save(curriculumItem(study)); final CurriculumItem curriculumItemForAnotherStudy = curriculumItemRepository.save(curriculumItem(anotherStudy)); @@ -133,11 +127,11 @@ class studyTest { member.getId()); // when - final List expectedResponses = List.of( - StudyResponse.of(study, teamOfStudy, cropOfTeam, 50), - StudyResponse.of(anotherStudy, teamOfAnotherStudy, cropOfAnotherTeam, 0) + final List expectedResponses = List.of( + StudyReferenceResponse.of(study, 50), + StudyReferenceResponse.of(anotherStudy, 0) ); - final List actualResponses = studyQueryService.findMyStudies(member.getId(), + final List actualResponses = studyQueryService.findMyStudies(member.getId(), tokenMemberId); // then diff --git a/src/test/java/doore/team/application/TeamQueryServiceTest.java b/src/test/java/doore/team/application/TeamQueryServiceTest.java index 9c9e0f4f..daa98cdc 100644 --- a/src/test/java/doore/team/application/TeamQueryServiceTest.java +++ b/src/test/java/doore/team/application/TeamQueryServiceTest.java @@ -3,8 +3,8 @@ import static doore.member.MemberFixture.createMember; import static doore.member.MemberFixture.미나; import static doore.member.MemberFixture.아마란스; +import static doore.member.domain.TeamRoleType.ROLE_팀장; import static doore.member.exception.MemberExceptionType.UNAUTHORIZED; -import static doore.team.TeamFixture.createTeam; import static doore.team.TeamFixture.team; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -14,8 +14,10 @@ import doore.helper.IntegrationTest; import doore.member.domain.Member; import doore.member.domain.MemberTeam; +import doore.member.domain.TeamRole; import doore.member.domain.repository.MemberRepository; import doore.member.domain.repository.MemberTeamRepository; +import doore.member.domain.repository.TeamRoleRepository; import doore.member.exception.MemberException; import doore.team.application.dto.response.TeamReferenceResponse; import doore.team.application.dto.response.TeamResponse; @@ -39,12 +41,21 @@ class TeamQueryServiceTest extends IntegrationTest { private MemberRepository memberRepository; @Autowired private AttendanceRepository attendanceRepository; + @Autowired + private TeamRoleRepository teamRoleRepository; private Member member; + private Team team; @BeforeEach void setUp() { member = memberRepository.save(미나()); + team = teamRepository.save(team()); + teamRoleRepository.save(TeamRole.builder() + .teamId(team.getId()) + .teamRoleType(ROLE_팀장) + .memberId(member.getId()) + .build()); } @Test @@ -85,7 +96,6 @@ void setUp() { @Test @DisplayName("[성공] 팀 상세 조회를 할 수 있다.") void findTeamByTeamId_팀_상세_조회를_할_수_있다_성공() { - final Team team = createTeam(); final Member anotherMember = createMember(); memberTeamRepository.save(MemberTeam.builder().teamId(team.getId()).member(member).isDeleted(false).build()); memberTeamRepository.save( @@ -94,9 +104,10 @@ void setUp() { final long attendanceRatio = 50L; - final TeamResponse expectTeamResponse = TeamResponse.of(team, attendanceRatio); + final TeamResponse expectTeamResponse = TeamResponse.of(team, attendanceRatio, member.getId()); final TeamResponse actualTeamResponse = teamQueryService.findTeamByTeamId(team.getId()); assertThat(actualTeamResponse).isEqualTo(expectTeamResponse); + assertThat(actualTeamResponse.teamLeaderId()).isEqualTo(member.getId()); } }