From b460a5f540e54ab5bd9f938f506375428212396b Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 14:53:13 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[#31]=20feat:=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=ED=81=AC=EB=A3=A8=EC=9D=98=20=EB=9E=AD=ED=82=B9=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/crew/repository/CrewRepository.java | 10 ++++++++++ .../domain/crew/service/CrewRankingService.java | 15 ++++++++++++++- .../backend/domain/crew/service/CrewService.java | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/repository/CrewRepository.java b/src/main/java/run/backend/domain/crew/repository/CrewRepository.java index 50b3c93..0d3de9a 100644 --- a/src/main/java/run/backend/domain/crew/repository/CrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/CrewRepository.java @@ -7,6 +7,7 @@ import run.backend.domain.crew.dto.query.CrewProfileDto; import run.backend.domain.crew.entity.Crew; +import java.util.List; import java.util.Optional; public interface CrewRepository extends JpaRepository { @@ -15,6 +16,15 @@ public interface CrewRepository extends JpaRepository { Page findAllByDeletedAtIsNullOrderByMonthlyScoreTotalDesc(Pageable pageable); + + @Query(""" + SELECT c.id + FROM Crew c + WHERE c.deletedAt IS NULL + ORDER BY c.monthlyScoreTotal DESC + """) + List findAllActiveCrewIdsOrderByScoreDesc(); + @Query(""" SELECT new run.backend.domain.crew.dto.query.CrewProfileDto( c.image, diff --git a/src/main/java/run/backend/domain/crew/service/CrewRankingService.java b/src/main/java/run/backend/domain/crew/service/CrewRankingService.java index dd5571d..40b7cae 100644 --- a/src/main/java/run/backend/domain/crew/service/CrewRankingService.java +++ b/src/main/java/run/backend/domain/crew/service/CrewRankingService.java @@ -8,11 +8,13 @@ import run.backend.domain.crew.dto.response.CrewRankingResponse; import run.backend.domain.crew.dto.response.CrewRankingStatusResponse; import run.backend.domain.crew.entity.Crew; +import run.backend.domain.crew.exception.CrewException; import run.backend.domain.crew.mapper.CrewMapper; import run.backend.domain.crew.repository.CrewRepository; import run.backend.global.common.response.PageResponse; import java.util.List; +import java.util.stream.IntStream; @Service @RequiredArgsConstructor @@ -32,8 +34,19 @@ public PageResponse getCrewRanking(int page, int size) { public CrewRankingStatusResponse getCrewRankingStatus(Crew crew) { - int rank = 0; // [TODO] : 스케줄링 rank 계산 구현 수정 예정 + int rank = getSingleCrewRanking(crew.getId()); return crewMapper.toCrewRankingStatusResponse(rank, crew); } + + public int getSingleCrewRanking(Long crewId) { + + List crewIds = crewRepository.findAllActiveCrewIdsOrderByScoreDesc(); + + return IntStream.range(0, crewIds.size()) + .filter(i -> crewIds.get(i).equals(crewId)) + .findFirst() + .orElseThrow(CrewException.NotFoundCrew::new) + + 1; + } } diff --git a/src/main/java/run/backend/domain/crew/service/CrewService.java b/src/main/java/run/backend/domain/crew/service/CrewService.java index 31fcb10..1c03cb2 100644 --- a/src/main/java/run/backend/domain/crew/service/CrewService.java +++ b/src/main/java/run/backend/domain/crew/service/CrewService.java @@ -35,6 +35,7 @@ public class CrewService { private final CrewRepository crewRepository; private final MemberRepository memberRepository; private final JoinCrewRepository joinCrewRepository; + private final CrewRankingService crewRankingService; @Transactional public CrewInviteCodeDto createCrew(Member member, String imageStatus, MultipartFile image, CrewInfoRequest data) { @@ -95,7 +96,7 @@ public void joinCrew(Member member, Long crewId) { public CrewBaseInfoResponse getCrewBaseInfo(Crew crew) { - int rank = 0; // [TODO] : 스케줄링 rank 계산 구현 수정 예정 + int rank = crewRankingService.getSingleCrewRanking(crew.getId()); return crewMapper.toCrewBaseInfo(rank, crew); } From 7e01e880e3b85fc7058a8f2dcee27a0c1d567e80 Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 15:11:18 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[#31]=20fix:=20API=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EB=8D=94=20=EC=9E=90=EC=84=B8=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../run/backend/domain/crew/controller/CrewController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/controller/CrewController.java b/src/main/java/run/backend/domain/crew/controller/CrewController.java index bccd8fc..3142f25 100644 --- a/src/main/java/run/backend/domain/crew/controller/CrewController.java +++ b/src/main/java/run/backend/domain/crew/controller/CrewController.java @@ -76,13 +76,13 @@ public CommonResponse getCrewByInviteCode(@PathVariable Str } @PostMapping("/{crewId}/join") - @Operation(summary = "크루 가입", description = "크루 가입하는 API 입니다.") + @Operation(summary = "크루 가입 요청", description = "크루 가입 요청하는 API 입니다.") public CommonResponse joinCrew( @Login Member member, @PathVariable Long crewId) { crewService.joinCrew(member, crewId); - return new CommonResponse<>("크루 가입 성공"); + return new CommonResponse<>("크루 가입 요청 성공"); } @GetMapping From 1a8ba94ca34fd05513ea98b4b47b8bd179d70be4 Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 15:11:31 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[#31]=20delete:=20JoinCrew=EC=97=90?= =?UTF-8?q?=EC=84=9C=20role=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/run/backend/domain/crew/entity/JoinCrew.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/entity/JoinCrew.java b/src/main/java/run/backend/domain/crew/entity/JoinCrew.java index b23cf62..3902e38 100644 --- a/src/main/java/run/backend/domain/crew/entity/JoinCrew.java +++ b/src/main/java/run/backend/domain/crew/entity/JoinCrew.java @@ -8,7 +8,6 @@ import org.hibernate.annotations.SQLDelete; import run.backend.domain.crew.enums.JoinStatus; import run.backend.domain.member.entity.Member; -import run.backend.domain.member.enums.Role; import run.backend.global.common.BaseEntity; import java.time.LocalDate; @@ -29,10 +28,6 @@ public class JoinCrew extends BaseEntity { @Column(name = "join_status") private JoinStatus joinStatus; - @Enumerated(EnumType.STRING) - @Column(name = "crew_role") - private Role role; - @Column(name = "joined_date") private LocalDate joinedDate; @@ -45,7 +40,6 @@ public class JoinCrew extends BaseEntity { private Crew crew; public void approveJoin() { - this.role = Role.MEMBER; this.joinedDate = LocalDate.now(); this.joinStatus = JoinStatus.APPROVED; } @@ -54,7 +48,6 @@ public static JoinCrew createLeaderJoin(Member member, Crew crew) { return JoinCrew.builder() .crew(crew) .member(member) - .role(Role.LEADER) .joinStatus(JoinStatus.APPROVED) .joinedDate(LocalDate.now()) .build(); @@ -64,7 +57,6 @@ public static JoinCrew createAppliedJoin(Member member, Crew crew) { return JoinCrew.builder() .crew(crew) .member(member) - .role(Role.MEMBER) .joinStatus(JoinStatus.APPLIED) .build(); } @@ -73,14 +65,12 @@ public static JoinCrew createAppliedJoin(Member member, Crew crew) { private JoinCrew( Member member, Crew crew, - Role role, JoinStatus joinStatus, LocalDate joinedDate ) { this.crew = crew; this.member = member; - this.role = role; this.joinStatus = joinStatus; this.joinedDate = joinedDate; } From 98fa28a922a06da1b05e3a749c0548c5261f58b1 Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 15:24:06 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[#31]=20fix:=20=ED=81=AC=EB=A3=A8=EC=9E=A5?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/crew/repository/JoinCrewRepository.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java index ec0a6a7..b7aadc9 100644 --- a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java @@ -24,10 +24,9 @@ public interface JoinCrewRepository extends JpaRepository { @Query(""" SELECT jc.member FROM JoinCrew jc - JOIN Crew c ON jc.crew = c - WHERE jc.role = :role + WHERE jc.crew = :crew AND jc.member.role = :role """) - Member findCrewLeader(@Param("role") Role role, Crew crew); + Member findCrewLeader(@Param("role") Role role, @Param("crew") Crew crew); @Query("SELECT jc FROM JoinCrew jc WHERE jc.member.id = :memberId AND jc.joinStatus = :status") Optional findByMemberIdAndJoinStatus(@Param("memberId") Long memberId, From 1b0553bfabdf9495632d16f3af3bd36f951ad43c Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 15:38:23 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[#31]=20fix:=20CrewMapper=20=EC=A0=84?= =?UTF-8?q?=EB=B6=80=20map-struct=20=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/crew/mapper/CrewMapper.java | 47 +++++++------------ .../domain/crew/service/CrewService.java | 2 +- .../domain/crew/mapper/CrewMapperTest.java | 2 +- .../domain/crew/service/CrewServiceTest.java | 6 +-- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java b/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java index 2ee9bb9..e4b1171 100644 --- a/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java +++ b/src/main/java/run/backend/domain/crew/mapper/CrewMapper.java @@ -20,6 +20,7 @@ public interface CrewMapper { @Mapping(target = "rank", source = "rank") CrewBaseInfoResponse toCrewBaseInfo(int rank, Crew crew); + @Mapping(target = "crewId", source = "crew.id") @Mapping(target = "monthlyScoreTotal", expression = "java(crew.getMonthlyScoreTotal().intValue())") CrewRankingResponse toCrewRankingResponse(Crew crew); @@ -33,33 +34,21 @@ public interface CrewMapper { List toCrewSearchResponseList(List dtos); - default Crew toEntity(String imageName, String name, String description) { - return Crew.builder() - .image(imageName) - .name(name) - .description(description) - .build(); - } - - default CrewProfileResponse toCrewProfile(Crew crew, Member leader) { - return CrewProfileResponse.builder() - .crewImage(crew.getImage()) - .crewName(crew.getName()) - .crewDescription(crew.getDescription()) - .memberCount(crew.getMemberCount()) - .leaderImage(leader.getProfileImage()) - .leaderName(leader.getNickname()) - .build(); - } - - default CrewRankingStatusResponse toCrewRankingStatusResponse( - int rank, - Crew crew - ) { - return new CrewRankingStatusResponse( - rank, - crew.getMonthlyDistanceTotal().intValue(), - crew.getCapturedDistanceTotal().intValue() - ); - } + @Mapping(target = "image", source = "imageName") + @Mapping(target = "name", source = "name") + @Mapping(target = "description", source = "description") + Crew toCrewEntity(String imageName, String name, String description); + + @Mapping(target = "crewImage", source = "crew.image") + @Mapping(target = "crewName", source = "crew.name") + @Mapping(target = "crewDescription", source = "crew.description") + @Mapping(target = "memberCount", source = "crew.memberCount") + @Mapping(target = "leaderImage", source = "leader.profileImage") + @Mapping(target = "leaderName", source = "leader.nickname") + CrewProfileResponse toCrewProfile(Crew crew, Member leader); + + @Mapping(target = "ranking", source = "rank") + @Mapping(target = "totalDistanceKm", expression = "java(crew.getMonthlyDistanceTotal().intValue())") + @Mapping(target = "capturedDistanceKm", expression = "java(crew.getCapturedDistanceTotal().intValue())") + CrewRankingStatusResponse toCrewRankingStatusResponse(int rank, Crew crew); } diff --git a/src/main/java/run/backend/domain/crew/service/CrewService.java b/src/main/java/run/backend/domain/crew/service/CrewService.java index 1c03cb2..c3f7e29 100644 --- a/src/main/java/run/backend/domain/crew/service/CrewService.java +++ b/src/main/java/run/backend/domain/crew/service/CrewService.java @@ -44,7 +44,7 @@ public CrewInviteCodeDto createCrew(Member member, String imageStatus, Multipart throw new CrewException.AlreadyJoinedCrew(); String imageName = fileService.handleImageUpdate(imageStatus, "default-profile-image.png", image); - Crew crew = crewMapper.toEntity(imageName, data.name(), data.description()); + Crew crew = crewMapper.toCrewEntity(imageName, data.name(), data.description()); crewRepository.save(crew); JoinCrew joinCrew = JoinCrew.createLeaderJoin(member, crew); diff --git a/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java b/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java index 6a10698..12fbff8 100644 --- a/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java +++ b/src/test/java/run/backend/domain/crew/mapper/CrewMapperTest.java @@ -48,7 +48,7 @@ void toCrewEntity_mapsCorrectly() { String description = "crew_description"; // when - Crew result = crewMapper.toEntity(imageName, name, description); + Crew result = crewMapper.toCrewEntity(imageName, name, description); // then assertThat(result.getImage()).isEqualTo(imageName); diff --git a/src/test/java/run/backend/domain/crew/service/CrewServiceTest.java b/src/test/java/run/backend/domain/crew/service/CrewServiceTest.java index 84e1f13..f44401b 100644 --- a/src/test/java/run/backend/domain/crew/service/CrewServiceTest.java +++ b/src/test/java/run/backend/domain/crew/service/CrewServiceTest.java @@ -95,7 +95,7 @@ void respondsWithInviteCode_whenCreatingCrew() { .thenReturn(false); when(fileService.handleImageUpdate(eq("unchanged"), eq("default-profile-image.png"), isNull())) .thenReturn("default-profile-image.png"); - when(crewMapper.toEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) + when(crewMapper.toCrewEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) .thenReturn(crew); when(crewRepository.save(any(Crew.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(joinCrewRepository.save(any(JoinCrew.class))).thenReturn(null); @@ -116,7 +116,7 @@ void saveJoinCrew_whenCreatingCrew() { when(joinCrewRepository.existsByMemberAndJoinStatus(member, JoinStatus.APPROVED)).thenReturn(false); when(fileService.handleImageUpdate(eq("unchanged"), eq("default-profile-image.png"), isNull())) .thenReturn("default-profile-image.png"); - when(crewMapper.toEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) + when(crewMapper.toCrewEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) .thenReturn(crew); when(crewRepository.save(any(Crew.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(joinCrewRepository.save(any(JoinCrew.class))).thenReturn(null); @@ -137,7 +137,7 @@ void updatesMemberRoleToLeader_whenCreatingCrew() { when(joinCrewRepository.existsByMemberAndJoinStatus(member, JoinStatus.APPROVED)).thenReturn(false); when(fileService.handleImageUpdate(eq("unchanged"), eq("default-profile-image.png"), isNull())) .thenReturn("default-profile-image.png"); - when(crewMapper.toEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) + when(crewMapper.toCrewEntity(eq("default-profile-image.png"), eq(request.name()), eq(request.description()))) .thenReturn(crew); when(crewRepository.save(any(Crew.class))).thenAnswer(invocation -> invocation.getArgument(0)); when(joinCrewRepository.save(any(JoinCrew.class))).thenReturn(null); From f800bdb1d8c2748eec164e679bff6003422e131e Mon Sep 17 00:00:00 2001 From: choiseoji Date: Mon, 4 Aug 2025 16:14:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[#31]=20test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/run/backend/domain/crew/entity/JoinCrewTest.java | 2 -- .../backend/domain/crew/service/CrewRankingServiceTest.java | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/java/run/backend/domain/crew/entity/JoinCrewTest.java b/src/test/java/run/backend/domain/crew/entity/JoinCrewTest.java index 1cb3dd0..8f5b6ef 100644 --- a/src/test/java/run/backend/domain/crew/entity/JoinCrewTest.java +++ b/src/test/java/run/backend/domain/crew/entity/JoinCrewTest.java @@ -27,7 +27,6 @@ void createLeaderJoin_setsCorrectValues() { // then assertThat(joinCrew.getMember()).isEqualTo(member); assertThat(joinCrew.getCrew()).isEqualTo(crew); - assertThat(joinCrew.getRole()).isEqualTo(Role.LEADER); assertThat(joinCrew.getJoinStatus()).isEqualTo(JoinStatus.APPROVED); assertThat(joinCrew.getJoinedDate()).isEqualTo(LocalDate.now()); } @@ -42,7 +41,6 @@ void createAppliedJoin_setsCorrectValues() { // then assertThat(joinCrew.getMember()).isEqualTo(member); assertThat(joinCrew.getCrew()).isEqualTo(crew); - assertThat(joinCrew.getRole()).isEqualTo(Role.MEMBER); assertThat(joinCrew.getJoinStatus()).isEqualTo(JoinStatus.APPLIED); assertThat(joinCrew.getJoinedDate()).isNull(); } diff --git a/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java b/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java index bb0d574..f7a45fb 100644 --- a/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java +++ b/src/test/java/run/backend/domain/crew/service/CrewRankingServiceTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -21,12 +22,14 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @DisplayName("Crew Ranking 서비스 테스트") @ExtendWith(MockitoExtension.class) public class CrewRankingServiceTest { + @Spy @InjectMocks private CrewRankingService crewRankingService; @@ -110,9 +113,10 @@ class getCrewRankingStatusTest { void getCrewRankingStatus_whenValidCrewGiven_thenReturnsStatusResponse() { // given - int rank = 0; + int rank = 1; CrewRankingStatusResponse expectedResponse = new CrewRankingStatusResponse(rank, 0, 0); + doReturn(rank).when(crewRankingService).getSingleCrewRanking(crew1.getId()); when(crewMapper.toCrewRankingStatusResponse(rank, crew1)) .thenReturn(expectedResponse);