Skip to content

Commit 91feb3d

Browse files
authored
feat: 매칭된 멘티/멘토 정보 조회 기능 구현 (#482)
* feat: 멘티의 매칭된 멘토 조회 관련 dto 추가 * feat: 멘티의 매칭된 멘토 조회 관련 repository 추가 * feat: 멘티의 매칭된 멘토 조회 관련 로직 추가 * test: 멘티의 매칭된 멘토 조회 관련 테스트 추구ㅏ * refactor: 멘토링을 먼저 조회하도록 수정
1 parent 1e5f2a4 commit 91feb3d

File tree

5 files changed

+217
-3
lines changed

5 files changed

+217
-3
lines changed

src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.example.solidconnection.common.resolver.AuthorizedUser;
66
import com.example.solidconnection.mentor.dto.CheckMentoringRequest;
77
import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse;
8+
import com.example.solidconnection.mentor.dto.MatchedMentorResponse;
89
import com.example.solidconnection.mentor.dto.MentoringApplyRequest;
910
import com.example.solidconnection.mentor.dto.MentoringApplyResponse;
1011
import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse;
@@ -38,6 +39,21 @@ public class MentoringForMenteeController {
3839
private final MentoringQueryService mentoringQueryService;
3940
private final MentoringCheckService mentoringCheckService;
4041

42+
@RequireRoleAccess(roles = Role.MENTEE)
43+
@GetMapping("/matched-mentors")
44+
public ResponseEntity<SliceResponse<MatchedMentorResponse>> getMatchedMentors(
45+
@AuthorizedUser long siteUserId,
46+
@PageableDefault
47+
@SortDefaults({
48+
@SortDefault(sort = "confirmedAt", direction = Sort.Direction.DESC),
49+
@SortDefault(sort = "id", direction = Sort.Direction.DESC)
50+
})
51+
Pageable pageable
52+
) {
53+
SliceResponse<MatchedMentorResponse> response = mentoringQueryService.getMatchedMentors(siteUserId, pageable);
54+
return ResponseEntity.ok(response);
55+
}
56+
4157
@RequireRoleAccess(roles = Role.MENTEE)
4258
@PostMapping
4359
public ResponseEntity<MentoringApplyResponse> applyMentoring(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.example.solidconnection.mentor.dto;
2+
3+
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
4+
5+
import com.example.solidconnection.mentor.domain.Mentor;
6+
import com.example.solidconnection.siteuser.domain.SiteUser;
7+
import com.example.solidconnection.university.domain.University;
8+
import com.fasterxml.jackson.annotation.JsonInclude;
9+
import java.util.List;
10+
11+
public record MatchedMentorResponse(
12+
long id,
13+
14+
@JsonInclude(NON_NULL)
15+
Long roomId,
16+
17+
String nickname,
18+
String profileImageUrl,
19+
String country,
20+
String universityName,
21+
String term,
22+
int menteeCount,
23+
boolean hasBadge,
24+
String introduction,
25+
List<ChannelResponse> channels,
26+
boolean isApplied
27+
) {
28+
29+
public static MatchedMentorResponse of(Mentor mentor, SiteUser mentorUser,
30+
University university, boolean isApplied, Long roomId) {
31+
return new MatchedMentorResponse(
32+
mentor.getId(),
33+
roomId,
34+
mentorUser.getNickname(),
35+
mentorUser.getProfileImageUrl(),
36+
university.getCountry().getKoreanName(),
37+
university.getKoreanName(),
38+
mentor.getTerm(),
39+
mentor.getMenteeCount(),
40+
mentor.isHasBadge(),
41+
mentor.getIntroduction(),
42+
mentor.getChannels().stream().map(ChannelResponse::from).toList(),
43+
isApplied
44+
);
45+
}
46+
}

src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,15 @@ public interface MentoringRepository extends JpaRepository<Mentoring, Long> {
2828
WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId
2929
""")
3030
List<Mentoring> findAllByMentorIdInAndMenteeId(@Param("mentorIds") List<Long> mentorIds, @Param("menteeId") long menteeId);
31+
32+
@Query("""
33+
SELECT m FROM Mentoring m
34+
WHERE m.menteeId = :menteeId
35+
AND m.mentorId IN :mentorIds
36+
AND m.verifyStatus = :verifyStatus
37+
""")
38+
List<Mentoring> findApprovedMentoringsByMenteeIdAndMentorIds(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, @Param("mentorIds") List<Long> mentorIds);
39+
40+
@Query("SELECT m FROM Mentoring m WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus")
41+
Slice<Mentoring> findApprovedMentoringsByMenteeId(long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable);
3142
}

src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
import com.example.solidconnection.common.exception.ErrorCode;
1111
import com.example.solidconnection.mentor.domain.Mentor;
1212
import com.example.solidconnection.mentor.domain.Mentoring;
13+
import com.example.solidconnection.mentor.dto.MatchedMentorResponse;
1314
import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse;
1415
import com.example.solidconnection.mentor.dto.MentoringForMentorResponse;
16+
import com.example.solidconnection.mentor.repository.MentorBatchQueryRepository;
1517
import com.example.solidconnection.mentor.repository.MentorRepository;
1618
import com.example.solidconnection.mentor.repository.MentoringRepository;
1719
import com.example.solidconnection.siteuser.domain.SiteUser;
1820
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
21+
import com.example.solidconnection.university.domain.University;
1922
import java.util.ArrayList;
2023
import java.util.List;
2124
import java.util.Map;
@@ -35,6 +38,59 @@ public class MentoringQueryService {
3538
private final MentorRepository mentorRepository;
3639
private final SiteUserRepository siteUserRepository;
3740
private final ChatRoomRepository chatRoomRepository;
41+
private final MentorBatchQueryRepository mentorBatchQueryRepository;
42+
43+
@Transactional(readOnly = true)
44+
public SliceResponse<MatchedMentorResponse> getMatchedMentors(long siteUserId, Pageable pageable) {
45+
Slice<Mentoring> mentoringSlice = mentoringRepository.findApprovedMentoringsByMenteeId(siteUserId, VerifyStatus.APPROVED, pageable);
46+
47+
List<Long> mentorIds = mentoringSlice.getContent().stream()
48+
.map(Mentoring::getMentorId)
49+
.distinct()
50+
.toList();
51+
52+
List<Mentor> mentors = mentorRepository.findAllById(mentorIds);
53+
54+
List<MatchedMentorResponse> content = buildMatchedMentorsWithBatchQuery(mentors, siteUserId);
55+
56+
return SliceResponse.of(content, mentoringSlice);
57+
}
58+
59+
private List<MatchedMentorResponse> buildMatchedMentorsWithBatchQuery(List<Mentor> mentors, long currentUserId) {
60+
Map<Long, SiteUser> mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors);
61+
Map<Long, University> mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors);
62+
Map<Long, Boolean> mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId);
63+
64+
Map<Long, Long> mentorIdToRoomId = getMentorIdToRoomIdMap(mentors, currentUserId);
65+
66+
List<MatchedMentorResponse> matchedMentors = new ArrayList<>();
67+
for (Mentor mentor : mentors) {
68+
SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId());
69+
University university = mentorIdToUniversity.get(mentor.getId());
70+
boolean isApplied = mentorIdToIsApplied.get(mentor.getId());
71+
Long roomId = mentorIdToRoomId.get(mentor.getId());
72+
MatchedMentorResponse response = MatchedMentorResponse.of(mentor, mentorUser, university, isApplied, roomId);
73+
matchedMentors.add(response);
74+
}
75+
return matchedMentors;
76+
}
77+
78+
private Map<Long, Long> getMentorIdToRoomIdMap(List<Mentor> mentors, long menteeUserId) {
79+
List<Long> mentorIds = mentors.stream().map(Mentor::getId).toList();
80+
List<Mentoring> approvedMentorings = mentoringRepository.findApprovedMentoringsByMenteeIdAndMentorIds(menteeUserId, VerifyStatus.APPROVED, mentorIds);
81+
82+
List<Long> mentoringIds = approvedMentorings.stream().map(Mentoring::getId).toList();
83+
List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
84+
85+
Map<Long, Long> mentoringIdToRoomId = chatRooms.stream()
86+
.collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId));
87+
88+
return approvedMentorings.stream()
89+
.collect(Collectors.toMap(
90+
Mentoring::getMentorId,
91+
mentoring -> mentoringIdToRoomId.get(mentoring.getId())
92+
));
93+
}
3894

3995
@Transactional(readOnly = true)
4096
public SliceResponse<MentoringForMenteeResponse> getMentoringsForMentee(

src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,33 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.assertj.core.api.Assertions.assertThatCode;
55
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
6+
import static org.junit.jupiter.api.Assertions.assertAll;
67

78
import com.example.solidconnection.chat.domain.ChatRoom;
89
import com.example.solidconnection.chat.fixture.ChatRoomFixture;
910
import com.example.solidconnection.common.VerifyStatus;
1011
import com.example.solidconnection.common.dto.SliceResponse;
1112
import com.example.solidconnection.common.exception.CustomException;
1213
import com.example.solidconnection.common.exception.ErrorCode;
14+
import com.example.solidconnection.mentor.domain.Channel;
1315
import com.example.solidconnection.mentor.domain.Mentor;
1416
import com.example.solidconnection.mentor.domain.Mentoring;
17+
import com.example.solidconnection.mentor.dto.ChannelResponse;
18+
import com.example.solidconnection.mentor.dto.MatchedMentorResponse;
1519
import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse;
1620
import com.example.solidconnection.mentor.dto.MentoringForMentorResponse;
21+
import com.example.solidconnection.mentor.fixture.ChannelFixture;
1722
import com.example.solidconnection.mentor.fixture.MentorFixture;
1823
import com.example.solidconnection.mentor.fixture.MentoringFixture;
1924
import com.example.solidconnection.mentor.repository.MentoringRepository;
2025
import com.example.solidconnection.siteuser.domain.SiteUser;
2126
import com.example.solidconnection.siteuser.fixture.SiteUserFixture;
2227
import com.example.solidconnection.support.TestContainerSpringBootTest;
28+
import com.example.solidconnection.university.domain.University;
29+
import com.example.solidconnection.university.fixture.UniversityFixture;
30+
import java.util.Map;
31+
import java.util.function.Function;
32+
import java.util.stream.Collectors;
2333
import org.junit.jupiter.api.BeforeEach;
2434
import org.junit.jupiter.api.DisplayName;
2535
import org.junit.jupiter.api.Nested;
@@ -44,6 +54,12 @@ class MentoringQueryServiceTest {
4454
@Autowired
4555
private MentoringFixture mentoringFixture;
4656

57+
@Autowired
58+
private UniversityFixture universityFixture;
59+
60+
@Autowired
61+
private ChannelFixture channelFixture;
62+
4763
@Autowired
4864
private MentoringRepository mentoringRepository;
4965

@@ -53,6 +69,7 @@ class MentoringQueryServiceTest {
5369
private SiteUser mentorUser1, mentorUser2;
5470
private SiteUser menteeUser1, menteeUser2, menteeUser3;
5571
private Mentor mentor1, mentor2, mentor3;
72+
private University university;
5673
private Pageable pageable;
5774

5875
@BeforeEach
@@ -63,9 +80,10 @@ void setUp() {
6380
menteeUser1 = siteUserFixture.사용자(1, "mentee1");
6481
menteeUser2 = siteUserFixture.사용자(2, "mentee2");
6582
menteeUser3 = siteUserFixture.사용자(3, "mentee3");
66-
mentor1 = mentorFixture.멘토(mentorUser1.getId(), 1L);
67-
mentor2 = mentorFixture.멘토(mentorUser2.getId(), 1L);
68-
mentor3 = mentorFixture.멘토(mentorUser3.getId(), 1L);
83+
university = universityFixture.괌_대학();
84+
mentor1 = mentorFixture.멘토(mentorUser1.getId(), university.getId());
85+
mentor2 = mentorFixture.멘토(mentorUser2.getId(), university.getId());
86+
mentor3 = mentorFixture.멘토(mentorUser3.getId(), university.getId());
6987
pageable = PageRequest.of(0, 3);
7088
}
7189

@@ -239,4 +257,71 @@ class 멘티의_멘토링_목록_조회_테스트 {
239257
assertThat(response.content()).isEmpty();
240258
}
241259
}
260+
261+
@Nested
262+
class 멘티의_멘토_목록_조회_테스트 {
263+
264+
private static final int NO_NEXT_PAGE_NUMBER = -1;
265+
266+
private Mentoring mentoring1, mentoring2;
267+
private ChatRoom chatRoom1, chatRoom2;
268+
269+
@BeforeEach
270+
void setUp() {
271+
mentoring1 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId());
272+
mentoring2 = mentoringFixture.승인된_멘토링(mentor2.getId(), menteeUser1.getId());
273+
274+
chatRoom1 = chatRoomFixture.멘토링_채팅방(mentoring1.getId());
275+
chatRoom2 = chatRoomFixture.멘토링_채팅방(mentoring2.getId());
276+
}
277+
278+
@Test
279+
void 매칭된_멘토의_목록_정보를_조회한다() {
280+
// given
281+
Channel channel1 = channelFixture.채널(1, mentor1);
282+
Channel channel2 = channelFixture.채널(2, mentor2);
283+
284+
// when
285+
SliceResponse<MatchedMentorResponse> response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 10));
286+
287+
// then
288+
Map<Long, MatchedMentorResponse> matchMentorMap = response.content().stream()
289+
.collect(Collectors.toMap(MatchedMentorResponse::id, Function.identity()));
290+
MatchedMentorResponse mentor1Response = matchMentorMap.get(mentor1.getId());
291+
MatchedMentorResponse mentor2Response = matchMentorMap.get(mentor2.getId());
292+
assertAll(
293+
() -> assertThat(mentor1Response.roomId()).isEqualTo(chatRoom1.getId()),
294+
() -> assertThat(mentor1Response.nickname()).isEqualTo(mentorUser1.getNickname()),
295+
() -> assertThat(mentor1Response.universityName()).isEqualTo(university.getKoreanName()),
296+
() -> assertThat(mentor1Response.country()).isEqualTo(university.getCountry().getKoreanName()),
297+
() -> assertThat(mentor1Response.channels()).extracting(ChannelResponse::url)
298+
.containsOnly(channel1.getUrl()),
299+
300+
() -> assertThat(mentor2Response.roomId()).isEqualTo(chatRoom2.getId()),
301+
() -> assertThat(mentor2Response.nickname()).isEqualTo(mentorUser2.getNickname()),
302+
() -> assertThat(mentor2Response.universityName()).isEqualTo(university.getKoreanName()),
303+
() -> assertThat(mentor2Response.country()).isEqualTo(university.getCountry().getKoreanName()),
304+
() -> assertThat(mentor2Response.channels()).extracting(ChannelResponse::url)
305+
.containsOnly(channel2.getUrl())
306+
);
307+
}
308+
309+
@Test
310+
void 다음_페이지_번호를_응답한다() {
311+
// given
312+
SliceResponse<MatchedMentorResponse> response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 1));
313+
314+
// then
315+
assertThat(response.nextPageNumber()).isEqualTo(2);
316+
}
317+
318+
@Test
319+
void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() {
320+
// given
321+
SliceResponse<MatchedMentorResponse> response = mentoringQueryService.getMatchedMentors(menteeUser1.getId(), PageRequest.of(0, 10));
322+
323+
// then
324+
assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER);
325+
}
326+
}
242327
}

0 commit comments

Comments
 (0)