diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java index 28683cd37..06d77b073 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java @@ -5,6 +5,7 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.mentor.dto.CheckMentoringRequest; import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse; +import com.example.solidconnection.mentor.dto.MatchedMentorResponse; import com.example.solidconnection.mentor.dto.MentoringApplyRequest; import com.example.solidconnection.mentor.dto.MentoringApplyResponse; import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; @@ -38,6 +39,21 @@ public class MentoringForMenteeController { private final MentoringQueryService mentoringQueryService; private final MentoringCheckService mentoringCheckService; + @RequireRoleAccess(roles = Role.MENTEE) + @GetMapping("/matched-mentors") + public ResponseEntity> getMatchedMentors( + @AuthorizedUser long siteUserId, + @PageableDefault + @SortDefaults({ + @SortDefault(sort = "confirmedAt", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.DESC) + }) + Pageable pageable + ) { + SliceResponse response = mentoringQueryService.getMatchedMentors(siteUserId, pageable); + return ResponseEntity.ok(response); + } + @RequireRoleAccess(roles = Role.MENTEE) @PostMapping public ResponseEntity applyMentoring( diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java new file mode 100644 index 000000000..c959ea083 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MatchedMentorResponse.java @@ -0,0 +1,46 @@ +package com.example.solidconnection.mentor.dto; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.University; +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +public record MatchedMentorResponse( + long id, + + @JsonInclude(NON_NULL) + Long roomId, + + String nickname, + String profileImageUrl, + String country, + String universityName, + String term, + int menteeCount, + boolean hasBadge, + String introduction, + List channels, + boolean isApplied +) { + + public static MatchedMentorResponse of(Mentor mentor, SiteUser mentorUser, + University university, boolean isApplied, Long roomId) { + return new MatchedMentorResponse( + mentor.getId(), + roomId, + mentorUser.getNickname(), + mentorUser.getProfileImageUrl(), + university.getCountry().getKoreanName(), + university.getKoreanName(), + mentor.getTerm(), + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), + isApplied + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java index 16caf3318..16230b74b 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -28,4 +28,15 @@ public interface MentoringRepository extends JpaRepository { WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId """) List findAllByMentorIdInAndMenteeId(@Param("mentorIds") List mentorIds, @Param("menteeId") long menteeId); + + @Query(""" + SELECT m FROM Mentoring m + WHERE m.menteeId = :menteeId + AND m.mentorId IN :mentorIds + AND m.verifyStatus = :verifyStatus + """) + List findApprovedMentoringsByMenteeIdAndMentorIds(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, @Param("mentorIds") List mentorIds); + + @Query("SELECT m FROM Mentoring m WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus") + Slice findApprovedMentoringsByMenteeId(long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index 1ef037706..57753f983 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -10,12 +10,15 @@ import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.MatchedMentorResponse; import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; import com.example.solidconnection.mentor.dto.MentoringForMentorResponse; +import com.example.solidconnection.mentor.repository.MentorBatchQueryRepository; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.University; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -35,6 +38,59 @@ public class MentoringQueryService { private final MentorRepository mentorRepository; private final SiteUserRepository siteUserRepository; private final ChatRoomRepository chatRoomRepository; + private final MentorBatchQueryRepository mentorBatchQueryRepository; + + @Transactional(readOnly = true) + public SliceResponse getMatchedMentors(long siteUserId, Pageable pageable) { + Slice mentoringSlice = mentoringRepository.findApprovedMentoringsByMenteeId(siteUserId, VerifyStatus.APPROVED, pageable); + + List mentorIds = mentoringSlice.getContent().stream() + .map(Mentoring::getMentorId) + .distinct() + .toList(); + + List mentors = mentorRepository.findAllById(mentorIds); + + List content = buildMatchedMentorsWithBatchQuery(mentors, siteUserId); + + return SliceResponse.of(content, mentoringSlice); + } + + private List buildMatchedMentorsWithBatchQuery(List mentors, long currentUserId) { + Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + Map mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors); + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId); + + Map mentorIdToRoomId = getMentorIdToRoomIdMap(mentors, currentUserId); + + List matchedMentors = new ArrayList<>(); + for (Mentor mentor : mentors) { + SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId()); + University university = mentorIdToUniversity.get(mentor.getId()); + boolean isApplied = mentorIdToIsApplied.get(mentor.getId()); + Long roomId = mentorIdToRoomId.get(mentor.getId()); + MatchedMentorResponse response = MatchedMentorResponse.of(mentor, mentorUser, university, isApplied, roomId); + matchedMentors.add(response); + } + return matchedMentors; + } + + private Map getMentorIdToRoomIdMap(List mentors, long menteeUserId) { + List mentorIds = mentors.stream().map(Mentor::getId).toList(); + List approvedMentorings = mentoringRepository.findApprovedMentoringsByMenteeIdAndMentorIds(menteeUserId, VerifyStatus.APPROVED, mentorIds); + + List mentoringIds = approvedMentorings.stream().map(Mentoring::getId).toList(); + List chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds); + + Map mentoringIdToRoomId = chatRooms.stream() + .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId)); + + return approvedMentorings.stream() + .collect(Collectors.toMap( + Mentoring::getMentorId, + mentoring -> mentoringIdToRoomId.get(mentoring.getId()) + )); + } @Transactional(readOnly = true) public SliceResponse getMentoringsForMentee( diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index 867af6e56..cb680b986 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.chat.domain.ChatRoom; import com.example.solidconnection.chat.fixture.ChatRoomFixture; @@ -10,16 +11,25 @@ import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.mentor.domain.Channel; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.ChannelResponse; +import com.example.solidconnection.mentor.dto.MatchedMentorResponse; import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; import com.example.solidconnection.mentor.dto.MentoringForMentorResponse; +import com.example.solidconnection.mentor.fixture.ChannelFixture; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.mentor.fixture.MentoringFixture; import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.fixture.UniversityFixture; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -44,6 +54,12 @@ class MentoringQueryServiceTest { @Autowired private MentoringFixture mentoringFixture; + @Autowired + private UniversityFixture universityFixture; + + @Autowired + private ChannelFixture channelFixture; + @Autowired private MentoringRepository mentoringRepository; @@ -53,6 +69,7 @@ class MentoringQueryServiceTest { private SiteUser mentorUser1, mentorUser2; private SiteUser menteeUser1, menteeUser2, menteeUser3; private Mentor mentor1, mentor2, mentor3; + private University university; private Pageable pageable; @BeforeEach @@ -63,9 +80,10 @@ void setUp() { menteeUser1 = siteUserFixture.사용자(1, "mentee1"); menteeUser2 = siteUserFixture.사용자(2, "mentee2"); menteeUser3 = siteUserFixture.사용자(3, "mentee3"); - mentor1 = mentorFixture.멘토(mentorUser1.getId(), 1L); - mentor2 = mentorFixture.멘토(mentorUser2.getId(), 1L); - mentor3 = mentorFixture.멘토(mentorUser3.getId(), 1L); + university = universityFixture.괌_대학(); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), university.getId()); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), university.getId()); + mentor3 = mentorFixture.멘토(mentorUser3.getId(), university.getId()); pageable = PageRequest.of(0, 3); } @@ -239,4 +257,71 @@ class 멘티의_멘토링_목록_조회_테스트 { assertThat(response.content()).isEmpty(); } } + + @Nested + class 멘티의_멘토_목록_조회_테스트 { + + private static final int NO_NEXT_PAGE_NUMBER = -1; + + private Mentoring mentoring1, mentoring2; + private ChatRoom chatRoom1, chatRoom2; + + @BeforeEach + void setUp() { + mentoring1 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId()); + mentoring2 = mentoringFixture.승인된_멘토링(mentor2.getId(), menteeUser1.getId()); + + chatRoom1 = chatRoomFixture.멘토링_채팅방(mentoring1.getId()); + chatRoom2 = chatRoomFixture.멘토링_채팅방(mentoring2.getId()); + } + + @Test + void 매칭된_멘토의_목록_정보를_조회한다() { + // given + Channel channel1 = channelFixture.채널(1, mentor1); + Channel channel2 = channelFixture.채널(2, mentor2); + + // when + SliceResponse response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 10)); + + // then + Map matchMentorMap = response.content().stream() + .collect(Collectors.toMap(MatchedMentorResponse::id, Function.identity())); + MatchedMentorResponse mentor1Response = matchMentorMap.get(mentor1.getId()); + MatchedMentorResponse mentor2Response = matchMentorMap.get(mentor2.getId()); + assertAll( + () -> assertThat(mentor1Response.roomId()).isEqualTo(chatRoom1.getId()), + () -> assertThat(mentor1Response.nickname()).isEqualTo(mentorUser1.getNickname()), + () -> assertThat(mentor1Response.universityName()).isEqualTo(university.getKoreanName()), + () -> assertThat(mentor1Response.country()).isEqualTo(university.getCountry().getKoreanName()), + () -> assertThat(mentor1Response.channels()).extracting(ChannelResponse::url) + .containsOnly(channel1.getUrl()), + + () -> assertThat(mentor2Response.roomId()).isEqualTo(chatRoom2.getId()), + () -> assertThat(mentor2Response.nickname()).isEqualTo(mentorUser2.getNickname()), + () -> assertThat(mentor2Response.universityName()).isEqualTo(university.getKoreanName()), + () -> assertThat(mentor2Response.country()).isEqualTo(university.getCountry().getKoreanName()), + () -> assertThat(mentor2Response.channels()).extracting(ChannelResponse::url) + .containsOnly(channel2.getUrl()) + ); + } + + @Test + void 다음_페이지_번호를_응답한다() { + // given + SliceResponse response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 1)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(2); + } + + @Test + void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { + // given + SliceResponse response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 10)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); + } + } }