From f3f704d7492025983a90e7a7e4ae237ff060801c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 02:37:21 +0900 Subject: [PATCH 01/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20=EC=9D=91=EB=8B=B5=20dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/dto/ChannelResponse.java | 17 ++++++++ .../mentor/dto/MentorDetailResponse.java | 40 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java diff --git a/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java new file mode 100644 index 000000000..cc6de7c71 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; + +public record ChannelResponse( + ChannelType type, + String url +) { + + public static ChannelResponse from(Channel channel) { + return new ChannelResponse( + channel.getType(), + channel.getUrl() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java new file mode 100644 index 000000000..cf0fbc98a --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java @@ -0,0 +1,40 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.util.List; + +public record MentorDetailResponse( + long id, + String nickname, + String profileImageUrl, + ExchangeStatus exchangeStatus, + String country, + String universityName, + int menteeCount, + boolean hasBadge, + String introduction, + List channels, + String passTip, + boolean isApplied +) { + + public static MentorDetailResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + return new MentorDetailResponse( + mentor.getId(), + mentorUser.getNickname(), + mentorUser.getProfileImageUrl(), + mentorUser.getExchangeStatus(), + "국가", // todo: 교환학생 기록이 인증되면 추가 + "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), + mentor.getPassTip(), + isApplied + ); + } +} From 21e9a4e292676ee42dd7a76492485737b891e552 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 02:37:47 +0900 Subject: [PATCH 02/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0,=20=EB=A9=98?= =?UTF-8?q?=ED=86=A0=EB=A7=81,=20=EC=B1=84=EB=84=90=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/repository/ChannelRepository.java | 7 +++++++ .../mentor/repository/MentorRepository.java | 7 +++++++ .../mentor/repository/MentoringRepository.java | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java diff --git a/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java new file mode 100644 index 000000000..571e4fb21 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Channel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChannelRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java new file mode 100644 index 000000000..d024c12c9 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentor; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MentorRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java new file mode 100644 index 000000000..c524bda9a --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentoring; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MentoringRepository extends JpaRepository { +} From b6107e8a36de05a1680f6d526e47875573cce467 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 02:38:23 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/exception/ErrorCode.java | 1 + .../repository/MentoringRepository.java | 2 ++ .../mentor/service/MentorQueryService.java | 34 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 90a53dad3..a0a3877ea 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -43,6 +43,7 @@ public enum ErrorCode { GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."), LANGUAGE_TEST_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 어학성적입니다."), NEWS_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 소식지입니다."), + MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 멘토입니다."), // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), 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 c524bda9a..499485c9d 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface MentoringRepository extends JpaRepository { + + boolean existsByMentorIdAndMenteeId(long mentorId, long menteeId); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java new file mode 100644 index 000000000..19fb504a4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -0,0 +1,34 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +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 lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + +@RequiredArgsConstructor +@Service +public class MentorQueryService { + + private final MentorRepository mentorRepository; + private final MentoringRepository mentoringRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional(readOnly = true) + public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser) { + Mentor mentor = mentorRepository.findById(mentorId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + SiteUser mentorUser = siteUserRepository.findById(mentor.getSiteUserId()) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + boolean isApplied = mentoringRepository.existsByMentorIdAndMenteeId(mentorId, currentUser.getId()); + + return MentorDetailResponse.of(mentor, mentorUser, isApplied); + } +} From 73ada9c6ff54d59090582166298b31d9ebb913da Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 02:38:45 +0900 Subject: [PATCH 04/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/controller/MentorController.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/controller/MentorController.java diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java new file mode 100644 index 000000000..fe5284c0d --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.mentor.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.service.MentorQueryService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/mentors") +@RestController +public class MentorController { + + private final MentorQueryService mentorQueryService; + + @GetMapping("/{mentor-id}") + public ResponseEntity getMentorDetails( + @AuthorizedUser SiteUser siteUser, + @PathVariable("mentor-id") Long mentorId + ) { + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentorId, siteUser); + return ResponseEntity.ok(response); + } +} From 3f987087354616b40c3d0deb55753fc704442a1e Mon Sep 17 00:00:00 2001 From: seonghyeok Date: Wed, 2 Jul 2025 13:19:22 +0900 Subject: [PATCH 05/25] =?UTF-8?q?test:=20=EB=A9=98=ED=86=A0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=94=BD=EC=8A=A4=EC=B2=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/fixture/MentorFixture.java | 21 +++++ .../mentor/fixture/MentorFixtureBuilder.java | 68 ++++++++++++++++ .../mentor/fixture/MentoringFixture.java | 77 +++++++++++++++++++ .../fixture/MentoringFixtureBuilder.java | 77 +++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java new file mode 100644 index 000000000..b612a9417 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Mentor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class MentorFixture { + + private final MentorFixtureBuilder mentorFixtureBuilder; + + public Mentor 멘토(long siteUserId, long universityId) { + return mentorFixtureBuilder.mentor() + .siteUserId(siteUserId) + .universityId(universityId) + .introduction("멘토 소개") + .passTip("합격 팁") + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java new file mode 100644 index 000000000..d499ecc2a --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java @@ -0,0 +1,68 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.repository.MentorRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class MentorFixtureBuilder { + + private final MentorRepository mentorRepository; + + private int menteeCount = 0; + private boolean hasBadge = false; + private String introduction; + private String passTip; + private long siteUserId; + private long universityId; + + public MentorFixtureBuilder mentor() { + return new MentorFixtureBuilder(mentorRepository); + } + + public MentorFixtureBuilder menteeCount(int menteeCount) { + this.menteeCount = menteeCount; + return this; + } + + public MentorFixtureBuilder hasBadge(boolean hasBadge) { + this.hasBadge = hasBadge; + return this; + } + + public MentorFixtureBuilder introduction(String introduction) { + this.introduction = introduction; + return this; + } + + public MentorFixtureBuilder passTip(String passTip) { + this.passTip = passTip; + return this; + } + + public MentorFixtureBuilder siteUserId(Long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public MentorFixtureBuilder universityId(Long universityId) { + this.universityId = universityId; + return this; + } + + public Mentor create() { + Mentor mentor = new Mentor( + null, + menteeCount, + hasBadge, + introduction, + passTip, + siteUserId, + universityId, + null + ); + return mentorRepository.save(mentor); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java new file mode 100644 index 000000000..818798953 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java @@ -0,0 +1,77 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.mentor.domain.Mentoring; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import java.time.ZonedDateTime; + +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + +@TestComponent +@RequiredArgsConstructor +public class MentoringFixture { + + private final MentoringFixtureBuilder mentoringFixtureBuilder; + + public Mentoring 대기중_멘토링(long mentorId, long menteeId) { + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .create(); + } + + public Mentoring 승인된_멘토링(long mentorId, long menteeId) { + ZonedDateTime now = getCurrentTime(); + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .verifyStatus(VerifyStatus.APPROVED) + .confirmedAt(now) + .checkedAt(now) + .create(); + } + + public Mentoring 거절된_멘토링(long mentorId, long menteeId, String rejectedReason) { + ZonedDateTime now = getCurrentTime(); + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .verifyStatus(VerifyStatus.REJECTED) + .rejectedReason(rejectedReason) + .confirmedAt(now) + .checkedAt(now) + .create(); + } + + public Mentoring 확인되지_않은_멘토링(long mentorId, long menteeId) { + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .checkedAt(null) + .create(); + } + + public Mentoring 멘토링( + long mentorId, + long menteeId, + VerifyStatus status, + String rejectedReason, + ZonedDateTime confirmedAt, + ZonedDateTime checkedAt) { + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .verifyStatus(status) + .rejectedReason(rejectedReason) + .confirmedAt(confirmedAt) + .checkedAt(checkedAt) + .create(); + } + + private ZonedDateTime getCurrentTime() { + return ZonedDateTime.now(UTC).truncatedTo(MICROS); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java new file mode 100644 index 000000000..0c579de5b --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java @@ -0,0 +1,77 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import java.time.ZonedDateTime; + +@TestComponent +@RequiredArgsConstructor +public class MentoringFixtureBuilder { + + private final MentoringRepository mentoringRepository; + + private ZonedDateTime createdAt; + private ZonedDateTime confirmedAt; + private ZonedDateTime checkedAt; + private VerifyStatus verifyStatus = VerifyStatus.PENDING; + private String rejectedReason; + private long mentorId; + private long menteeId; + + public MentoringFixtureBuilder mentoring() { + return new MentoringFixtureBuilder(mentoringRepository); + } + + public MentoringFixtureBuilder createdAt(ZonedDateTime createdAt) { + this.createdAt = createdAt; + return this; + } + + public MentoringFixtureBuilder confirmedAt(ZonedDateTime confirmedAt) { + this.confirmedAt = confirmedAt; + return this; + } + + public MentoringFixtureBuilder checkedAt(ZonedDateTime checkedAt) { + this.checkedAt = checkedAt; + return this; + } + + public MentoringFixtureBuilder verifyStatus(VerifyStatus verifyStatus) { + this.verifyStatus = verifyStatus; + return this; + } + + public MentoringFixtureBuilder rejectedReason(String rejectedReason) { + this.rejectedReason = rejectedReason; + return this; + } + + public MentoringFixtureBuilder mentorId(long mentorId) { + this.mentorId = mentorId; + return this; + } + + public MentoringFixtureBuilder menteeId(long menteeId) { + this.menteeId = menteeId; + return this; + } + + public Mentoring create() { + Mentoring mentoring = new Mentoring( + null, + createdAt, + confirmedAt, + checkedAt, + verifyStatus, + rejectedReason, + mentorId, + menteeId + ); + return mentoringRepository.save(mentoring); + } +} From 60553b3a5bc204c74bb7dc855b80098a5fd77eff Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 15:30:12 +0900 Subject: [PATCH 06/25] =?UTF-8?q?refactor:=20channel=20=EC=97=B0=EA=B4=80?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=ED=8E=B8=EC=9D=98=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/mentor/domain/Channel.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java index 33c4f72c3..846b8e625 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java @@ -44,4 +44,8 @@ public class Channel { @ManyToOne(fetch = FetchType.LAZY) private Mentor mentor; + + public void updateMentor(Mentor mentor) { + this.mentor = mentor; + } } From b2f6e355acc9d5f6800f5d664958fa02e0102b8c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 15:30:21 +0900 Subject: [PATCH 07/25] =?UTF-8?q?test:=20channel=20=ED=94=BD=EC=8A=A4?= =?UTF-8?q?=EC=B3=90=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/fixture/ChannelFixture.java | 23 ++++++++ .../mentor/fixture/ChannelFixtureBuilder.java | 56 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java new file mode 100644 index 000000000..d3c27c114 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; +import com.example.solidconnection.mentor.domain.Mentor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChannelFixture { + + private final ChannelFixtureBuilder channelFixtureBuilder; + + public Channel 채널(int sequence, Mentor mentor) { + return channelFixtureBuilder.channel() + .sequence(sequence) + .type(ChannelType.YOUTUBE) + .url("https://www.youtube.com/channel" + sequence) + .mentor(mentor) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java new file mode 100644 index 000000000..670b0e8b4 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java @@ -0,0 +1,56 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.repository.ChannelRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChannelFixtureBuilder { + + private final ChannelRepository channelRepository; + + private int sequence; + private ChannelType type; + private String url; + private Mentor mentor; + + public ChannelFixtureBuilder channel() { + return new ChannelFixtureBuilder(channelRepository); + } + + public ChannelFixtureBuilder sequence(int sequence) { + this.sequence = sequence; + return this; + } + + public ChannelFixtureBuilder type(ChannelType type) { + this.type = type; + return this; + } + + public ChannelFixtureBuilder url(String url) { + this.url = url; + return this; + } + + public ChannelFixtureBuilder mentor(Mentor mentor) { + this.mentor = mentor; + return this; + } + + public Channel create() { + Channel channel = new Channel( + null, + sequence, + type, + url, + null + ); + channel.updateMentor(mentor); + return channelRepository.save(channel); + } +} From 1864b0ab19acff08bb66014ba4ded6b2787bba91 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 03:07:44 +0900 Subject: [PATCH 08/25] =?UTF-8?q?test:=20=EB=A9=98=ED=86=A0=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MentorQueryServiceTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java new file mode 100644 index 000000000..a0aa6cdca --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -0,0 +1,106 @@ +package com.example.solidconnection.mentor.service; + +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.dto.ChannelResponse; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +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.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("멘토 조회 서비스 테스트") +@TestContainerSpringBootTest +class MentorQueryServiceTest { + + @Autowired + private MentorQueryService mentorQueryService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + @Autowired + private ChannelFixture channelFixture; + + private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + + @Nested + class 멘토_단일_조회_성공 { + + @Test + void 멘토_정보를_조회한다() { + // given + SiteUser siteUser = siteUserFixture.사용자(); + SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + Channel channel1 = channelFixture.채널(1, mentor); + Channel channel2 = channelFixture.채널(2, mentor); + + // when + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentor.getId(), siteUser); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(mentor.getId()), + () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.channels()).extracting(ChannelResponse::url) + .containsOnly(channel1.getUrl(), channel2.getUrl()) + ); + } + + @Test + void 멘토에_대한_나의_멘토링_신청_여부를_조회한다() { + // given + SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + + SiteUser notAppliedUser = siteUserFixture.사용자(2, "멘토링 지원 안한 사용자"); + SiteUser appliedUser = siteUserFixture.사용자(3, "멘토링 지원한 사용자"); + mentoringFixture.대기중_멘토링(mentor.getId(), appliedUser.getId()); + + + // when + MentorDetailResponse notAppliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), notAppliedUser); + MentorDetailResponse appliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), appliedUser); + + // then + assertAll( + () -> assertThat(notAppliedResponse.isApplied()).isFalse(), + () -> assertThat(appliedResponse.isApplied()).isTrue() + ); + } + } + + @Nested + class 멘토_단일_조회_실패 { + + @Test + void 존재하지_않는_멘토를_조회하면_예외_응답을_반환한다() { + // given + long notExistingMentorId = 999L; + + // when & then + assertThatCode(() -> mentorQueryService.getMentorDetails(notExistingMentorId, siteUserFixture.사용자())) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.MENTOR_NOT_FOUND.getMessage()); + } + } +} From 236c66685e3d96e740c18d18f3953bd1ecab9812 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 03:09:11 +0900 Subject: [PATCH 09/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/dto/MentorPreviewResponse.java | 38 +++++++++++++++++++ .../mentor/dto/MentorPreviewsResponse.java | 9 +++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java new file mode 100644 index 000000000..bba3010f4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.util.List; + +public record MentorPreviewResponse( + long id, + String nickname, + String profileImageUrl, + ExchangeStatus exchangeStatus, + String country, + String universityName, + int menteeCount, + boolean hasBadge, + String introduction, + List channels, + boolean isApplied +) { + + public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + return new MentorPreviewResponse( + mentor.getId(), + mentorUser.getNickname(), + mentorUser.getProfileImageUrl(), + mentorUser.getExchangeStatus(), + "국가", // todo: 교환학생 기록이 인증되면 추가 + "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), + isApplied + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java new file mode 100644 index 000000000..cb49ba602 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.mentor.dto; + +import java.util.List; + +public record MentorPreviewsResponse( + List content, + int nextPageNumber +) { +} From 4717e7adf179de7fabe8010bfd3902a01775b410 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 03:14:26 +0900 Subject: [PATCH 10/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20Batch=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mentor와 SiteUser는 id 만 참조하는 관계이다. - Mentor와 Mentoring도 id 만 참조하는 관계이다. - "멘토 목록"에 대해서 매번 siteUser, mentoring과 join 하면 N+1 이 발생한다. - 이를 해결하기 위해, 한번에 조회하고 매핑하여 1번의 쿼리로 해결한다. --- .../MentorBatchQueryRepository.java | 56 +++++++++++++++++++ .../repository/MentoringRepository.java | 10 ++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java new file mode 100644 index 000000000..026c7aef5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java @@ -0,0 +1,56 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; + +@Repository +@RequiredArgsConstructor +public class MentorBatchQueryRepository { // 연관관계가 설정되지 않은 엔티티들을 N+1 없이 하나의 쿼리로 조회 + + private final SiteUserRepository siteUserRepository; + private final MentoringRepository mentoringRepository; + + public Map getMentorIdToSiteUserMap(List mentors) { + List mentorUserIds = mentors.stream().map(Mentor::getSiteUserId).toList(); + List mentorUsers = siteUserRepository.findAllById(mentorUserIds); + Map mentorUserIdToSiteUserMap = mentorUsers.stream() + .collect(Collectors.toMap(SiteUser::getId, Function.identity())); + + return mentors.stream().collect(Collectors.toMap( + Mentor::getId, + mentor -> { + SiteUser mentorUser = mentorUserIdToSiteUserMap.get(mentor.getSiteUserId()); + if (mentorUser == null) { // site_user.id == mentor.site_user_id 에 해당하는게 없으면 정합성 문제가 발생한 것 + throw new CustomException(DATA_INTEGRITY_VIOLATION, "mentor에 해당하는 siteUser 존재하지 않음"); + } + return mentorUser; + } + )); + } + + public Map getMentorIdToIsApplied(List mentors, long currentUserId) { + List mentorIds = mentors.stream().map(Mentor::getId).toList(); + List appliedMentorings = mentoringRepository.findAllByMentorIdInAndMenteeId(mentorIds, currentUserId); + Set appliedMentorIds = appliedMentorings.stream() + .map(Mentoring::getMentorId) + .collect(Collectors.toSet()); + + return mentors.stream().collect(Collectors.toMap( + Mentor::getId, + mentor -> appliedMentorIds.contains(mentor.getId()) + )); + } +} 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 499485c9d..081f320d5 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -2,8 +2,18 @@ import com.example.solidconnection.mentor.domain.Mentoring; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface MentoringRepository extends JpaRepository { boolean existsByMentorIdAndMenteeId(long mentorId, long menteeId); + + @Query(""" + SELECT m FROM Mentoring m + WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId + """) + List findAllByMentorIdInAndMenteeId(@Param("mentorIds") List mentorIds, @Param("menteeId") long menteeId); } From 7c8419b0f116dee755b31a16e7b814fbc8e1f674 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 03:16:31 +0900 Subject: [PATCH 11/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EB=AA=A9=EB=A1=9D=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/service/MentorQueryService.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index 19fb504a4..72e2b3429 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -3,23 +3,35 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; +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 lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; @RequiredArgsConstructor @Service public class MentorQueryService { + private static final int NO_NEXT_PAGE_NUMBER = -1; + private final MentorRepository mentorRepository; private final MentoringRepository mentoringRepository; private final SiteUserRepository siteUserRepository; + private final MentorBatchQueryRepository mentorBatchQueryRepository; @Transactional(readOnly = true) public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser) { @@ -31,4 +43,33 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser return MentorDetailResponse.of(mentor, mentorUser, isApplied); } + + @Transactional(readOnly = true) + public MentorPreviewsResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 + Page mentorPage = mentorRepository.findAll(pageable); + List mentors = mentorPage.toList(); + + List content = getContent(mentors, siteUser); + int pageNumber = getPageNumber(mentorPage); + + return new MentorPreviewsResponse(content, pageNumber); + } + + private List getContent(List mentors, SiteUser siteUser) { + Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, siteUser.getId()); + + List mentorPreviews = new ArrayList<>(); + for (Mentor mentor : mentors) { + SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId()); + boolean isApplied = mentorIdToIsApplied.get(mentor.getId()); + MentorPreviewResponse response = MentorPreviewResponse.of(mentor, mentorUser, isApplied); + mentorPreviews.add(response); + } + return mentorPreviews; + } + + private int getPageNumber(Page page) { + return page.hasNext() ? page.nextPageable().getPageNumber() + 1 : NO_NEXT_PAGE_NUMBER; // one-based index + } } From 3d83350377ee6834d916bf6cea081e92dea2540d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 03:30:54 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EB=AA=A9=EB=A1=9D=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/controller/MentorController.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index fe5284c0d..452f4a35e 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -2,15 +2,21 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; import com.example.solidconnection.mentor.service.MentorQueryService; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import static org.springframework.data.domain.Sort.Direction.DESC; + @RequiredArgsConstructor @RequestMapping("/mentors") @RestController @@ -19,11 +25,21 @@ public class MentorController { private final MentorQueryService mentorQueryService; @GetMapping("/{mentor-id}") - public ResponseEntity getMentorDetails( + public ResponseEntity getMentorDetails( @AuthorizedUser SiteUser siteUser, @PathVariable("mentor-id") Long mentorId ) { MentorDetailResponse response = mentorQueryService.getMentorDetails(mentorId, siteUser); return ResponseEntity.ok(response); } + + @GetMapping + public ResponseEntity getMentorPreviews( + @AuthorizedUser SiteUser siteUser, + @RequestParam("region") String region, + @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) Pageable pageable + ) { + MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); + return ResponseEntity.ok(response); + } } From c5c3761cde0f3172dba3f2ab8ba11984ed78f30d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 15:53:07 +0900 Subject: [PATCH 13/25] =?UTF-8?q?test:=20=EB=A9=98=ED=86=A0=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MentorQueryServiceTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index a0aa6cdca..ad06a9366 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -6,16 +6,24 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.dto.ChannelResponse; import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; 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.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -41,6 +49,7 @@ class MentorQueryServiceTest { private ChannelFixture channelFixture; private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + private String region = "아시아"; @Nested class 멘토_단일_조회_성공 { @@ -103,4 +112,82 @@ class 멘토_단일_조회_실패 { .hasMessageContaining(ErrorCode.MENTOR_NOT_FOUND.getMessage()); } } + + @Nested + class 멘토_미리보기_목록_조회 { + + private static final int NO_NEXT_PAGE_NUMBER = -1; + + private Mentor mentor1, mentor2; + private SiteUser mentorUser1, mentorUser2, currentUser; + + @BeforeEach + void setUp() { + currentUser = siteUserFixture.사용자(1, "사용자1"); + mentorUser1 = siteUserFixture.사용자(2, "멘토1"); + mentorUser2 = siteUserFixture.사용자(3, "멘토2"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + } + + @Test + void 멘토_미리보기_목록의_정보를_조회한다() { + // given + Channel channel1 = channelFixture.채널(1, mentor1); + Channel channel2 = channelFixture.채널(2, mentor2); + + // when + MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + Map mentorPreviewMap = response.content().stream() + .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); + + assertAll( + () -> assertThat(mentorPreviewMap.get(mentor1.getId())).extracting(MentorPreviewResponse::nickname) + .isEqualTo(mentorUser1.getNickname()), + () -> assertThat(mentorPreviewMap.get(mentor1.getId()).channels()).extracting(ChannelResponse::url) + .containsOnly(channel1.getUrl()), + () -> assertThat(mentorPreviewMap.get(mentor2.getId())).extracting(MentorPreviewResponse::nickname) + .isEqualTo(mentorUser2.getNickname()), + () -> assertThat(mentorPreviewMap.get(mentor2.getId()).channels()).extracting(ChannelResponse::url) + .containsOnly(channel2.getUrl()) + ); + } + + @Test + void 멘토들에_대한_나의_멘토링_지원_여부를_조회한다() { + // given + mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); + + // when + MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + Map mentorPreviewMap = response.content().stream() + .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); + assertAll( + () -> assertThat(mentorPreviewMap.get(mentor1.getId()).isApplied()).isTrue(), + () -> assertThat(mentorPreviewMap.get(mentor2.getId()).isApplied()).isFalse() + ); + } + + @Test + void 다음_페이지_번호를_응답한다() { + // given + MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 1)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(2); + } + + @Test + void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { + // given + MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); + } + } } From dec6e03c0915f6605316e9178c5e47a1f98e8be0 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Thu, 3 Jul 2025 16:45:23 +0900 Subject: [PATCH 14/25] =?UTF-8?q?test:=20=EB=A9=98=ED=86=A0=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=20=EC=A1=B0=ED=9A=8C=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MentorBatchQueryRepositoryTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java diff --git a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java new file mode 100644 index 000000000..0ef517c34 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java @@ -0,0 +1,80 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("멘토 배치 조회 레포지토리 테스트") +@TestContainerSpringBootTest +class MentorBatchQueryRepositoryTest { + + @Autowired + private MentorBatchQueryRepository mentorBatchQueryRepository; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + private Mentor mentor1, mentor2; + private SiteUser mentorUser1, mentorUser2, currentUser; + + @BeforeEach + void setUp() { + currentUser = siteUserFixture.사용자(1, "사용자"); + mentorUser1 = siteUserFixture.사용자(2, "멘토1"); + mentorUser2 = siteUserFixture.사용자(3, "멘토2"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + } + + @Test + void 멘토_ID_와_멘토_사용자를_매핑한다() { + // given + List mentors = List.of(mentor1, mentor2); + + // when + Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + + // then + assertAll( + () -> assertThat(mentorIdToSiteUser.get(mentor1.getId()).getId()).isEqualTo(mentorUser1.getId()), + () -> assertThat(mentorIdToSiteUser.get(mentor2.getId()).getId()).isEqualTo(mentorUser2.getId()) + ); + } + + @Test + void 멘토_ID_와_현재_사용자의_지원_여부를_매핑한다() { + // given + Mentoring 대기중_멘토링 = mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); + List mentors = List.of(mentor1, mentor2); + + // when + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUser.getId()); + + // then + assertAll( + () -> assertThat(mentorIdToIsApplied.get(mentor1.getId())).isTrue(), + () -> assertThat(mentorIdToIsApplied.get(mentor2.getId())).isFalse() + ); + } +} From 4487fbfb59513350c42214df144137a3b662d6e1 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:25:39 +0900 Subject: [PATCH 15/25] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/mentor/service/MentorQueryServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index ad06a9366..5dd47aa76 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -85,7 +85,6 @@ class 멘토_단일_조회_성공 { SiteUser appliedUser = siteUserFixture.사용자(3, "멘토링 지원한 사용자"); mentoringFixture.대기중_멘토링(mentor.getId(), appliedUser.getId()); - // when MentorDetailResponse notAppliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), notAppliedUser); MentorDetailResponse appliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), appliedUser); From a3f5950636bfe946aec0cd4d3756094e65cd1bf5 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:28:17 +0900 Subject: [PATCH 16/25] =?UTF-8?q?refactor:=20Page=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=88=EB=9D=BC=20Slice=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=EB=B0=9B=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Page를 사용할 시, 추가로 발생하는 count 쿼리 방지 --- .../solidconnection/mentor/repository/MentorRepository.java | 4 ++++ .../solidconnection/mentor/service/MentorQueryService.java | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index d024c12c9..8b896a9d2 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -1,7 +1,11 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Mentor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; public interface MentorRepository extends JpaRepository { + + Slice findBy(Pageable pageable); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index 72e2b3429..e57501cab 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -11,8 +11,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,7 +46,7 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser @Transactional(readOnly = true) public MentorPreviewsResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 - Page mentorPage = mentorRepository.findAll(pageable); + Slice mentorPage = mentorRepository.findBy(pageable); List mentors = mentorPage.toList(); List content = getContent(mentors, siteUser); @@ -69,7 +69,7 @@ private List getContent(List mentors, SiteUser si return mentorPreviews; } - private int getPageNumber(Page page) { + private int getPageNumber(Slice page) { return page.hasNext() ? page.nextPageable().getPageNumber() + 1 : NO_NEXT_PAGE_NUMBER; // one-based index } } From fcc924470555fee38c3d618d1856b994020743b9 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:28:32 +0900 Subject: [PATCH 17/25] =?UTF-8?q?refactor:=20Channel=20N+1=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/solidconnection/mentor/domain/Mentor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 81f9d6177..2d0176411 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -11,6 +11,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; import java.util.ArrayList; import java.util.List; @@ -43,6 +44,7 @@ public class Mentor { @Column private long universityId; + @BatchSize(size = 10) @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) private List channels = new ArrayList<>(); } From e40c9433ba502a0becd5684fb44cd15d90d03ff5 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:42:05 +0900 Subject: [PATCH 18/25] =?UTF-8?q?refactor:=20SliceResponse=EB=A1=9C=20?= =?UTF-8?q?=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/SliceResponse.java | 21 +++++++++++++++++++ .../mentor/controller/MentorController.java | 7 ++++--- .../mentor/dto/MentorPreviewsResponse.java | 9 -------- .../mentor/service/MentorQueryService.java | 6 +++--- .../service/MentorQueryServiceTest.java | 10 ++++----- 5 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/common/dto/SliceResponse.java delete mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java diff --git a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java new file mode 100644 index 000000000..d99a35849 --- /dev/null +++ b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.common.dto; + +import org.springframework.data.domain.Slice; + +import java.util.List; + +public record SliceResponse( + List content, + int nextPageNumber +) { + + private static final int NO_NEXT_PAGE = -1; + private static final int BASE_NUMBER = 1; // 1-based + + public static SliceResponse of(Slice slice, List content) { + int nextPageNumber = slice.hasNext() + ? slice.getNumber() + BASE_NUMBER + 1 + : NO_NEXT_PAGE; + return new SliceResponse<>(content, nextPageNumber); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index 452f4a35e..eed9ba010 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -1,8 +1,9 @@ package com.example.solidconnection.mentor.controller; +import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.mentor.dto.MentorDetailResponse; -import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; import com.example.solidconnection.mentor.service.MentorQueryService; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; @@ -34,12 +35,12 @@ public ResponseEntity getMentorDetails( } @GetMapping - public ResponseEntity getMentorPreviews( + public ResponseEntity> getMentorPreviews( @AuthorizedUser SiteUser siteUser, @RequestParam("region") String region, @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) Pageable pageable ) { - MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); + SliceResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java deleted file mode 100644 index cb49ba602..000000000 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.solidconnection.mentor.dto; - -import java.util.List; - -public record MentorPreviewsResponse( - List content, - int nextPageNumber -) { -} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index e57501cab..823e3ef1e 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -1,10 +1,10 @@ package com.example.solidconnection.mentor.service; +import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.dto.MentorDetailResponse; import com.example.solidconnection.mentor.dto.MentorPreviewResponse; -import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; import com.example.solidconnection.mentor.repository.MentorBatchQueryRepository; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.mentor.repository.MentoringRepository; @@ -45,14 +45,14 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser } @Transactional(readOnly = true) - public MentorPreviewsResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 + public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 Slice mentorPage = mentorRepository.findBy(pageable); List mentors = mentorPage.toList(); List content = getContent(mentors, siteUser); int pageNumber = getPageNumber(mentorPage); - return new MentorPreviewsResponse(content, pageNumber); + return new SliceResponse<>(content, pageNumber); } private List getContent(List mentors, SiteUser siteUser) { diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index 5dd47aa76..536244a6b 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.mentor.service; +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; @@ -7,7 +8,6 @@ import com.example.solidconnection.mentor.dto.ChannelResponse; import com.example.solidconnection.mentor.dto.MentorDetailResponse; import com.example.solidconnection.mentor.dto.MentorPreviewResponse; -import com.example.solidconnection.mentor.dto.MentorPreviewsResponse; import com.example.solidconnection.mentor.fixture.ChannelFixture; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.mentor.fixture.MentoringFixture; @@ -136,7 +136,7 @@ void setUp() { Channel channel2 = channelFixture.채널(2, mentor2); // when - MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); // then Map mentorPreviewMap = response.content().stream() @@ -160,7 +160,7 @@ void setUp() { mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); // when - MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); // then Map mentorPreviewMap = response.content().stream() @@ -174,7 +174,7 @@ void setUp() { @Test void 다음_페이지_번호를_응답한다() { // given - MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 1)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 1)); // then assertThat(response.nextPageNumber()).isEqualTo(2); @@ -183,7 +183,7 @@ void setUp() { @Test void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { // given - MentorPreviewsResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); // then assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); From 55411881e6968ab0b0f7989340b4222e35e795ea Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:50:02 +0900 Subject: [PATCH 19/25] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EA=B5=AC=EC=B2=B4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기획팀께 답변 받은 내용 적용 --- .../mentor/controller/MentorController.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index eed9ba010..ea60b180c 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -8,7 +8,10 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; +import org.springframework.data.web.SortDefault.SortDefaults; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -38,7 +41,13 @@ public ResponseEntity getMentorDetails( public ResponseEntity> getMentorPreviews( @AuthorizedUser SiteUser siteUser, @RequestParam("region") String region, - @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) Pageable pageable + + @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) + @SortDefaults({ + @SortDefault(sort = "menteeCount", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.ASC) + }) + Pageable pageable ) { SliceResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); return ResponseEntity.ok(response); From 91cef417fa8f5b5a6e6b49d01c218662c95e335d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Mon, 7 Jul 2025 18:59:46 +0900 Subject: [PATCH 20/25] =?UTF-8?q?feat:=20=EC=B1=84=EB=84=90=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=9D=84=20sequence=20=EC=98=A4=EB=A6=84=EC=B0=A8?= =?UTF-8?q?=EC=88=9C=EC=9C=BC=EB=A1=9C=20=EC=A0=95=EB=A0=AC=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/dto/MentorPreviewResponse.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java index bba3010f4..25e3c0542 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -1,9 +1,11 @@ package com.example.solidconnection.mentor.dto; +import com.example.solidconnection.mentor.domain.Channel; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Comparator; import java.util.List; public record MentorPreviewResponse( @@ -31,8 +33,15 @@ public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boole mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), - mentor.getChannels().stream().map(ChannelResponse::from).toList(), + toChannelResponses(mentor.getChannels()), isApplied ); } + + private static List toChannelResponses(List channels) { + return channels.stream() + .sorted(Comparator.comparingInt(Channel::getSequence)) + .map(ChannelResponse::from) + .toList(); + } } From 8095d364ff74d2749170eef5bfcc4e2d50f4a9bb Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 8 Jul 2025 20:43:05 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor:=20SliceResponse=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=ED=8C=A9=ED=84=B0=EB=A6=AC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/service/MentorQueryService.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index 823e3ef1e..f22c7beb7 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -26,8 +26,6 @@ @Service public class MentorQueryService { - private static final int NO_NEXT_PAGE_NUMBER = -1; - private final MentorRepository mentorRepository; private final MentoringRepository mentoringRepository; private final SiteUserRepository siteUserRepository; @@ -46,13 +44,11 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser @Transactional(readOnly = true) public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 - Slice mentorPage = mentorRepository.findBy(pageable); - List mentors = mentorPage.toList(); - + Slice mentorSlice = mentorRepository.findBy(pageable); + List mentors = mentorSlice.toList(); List content = getContent(mentors, siteUser); - int pageNumber = getPageNumber(mentorPage); - return new SliceResponse<>(content, pageNumber); + return SliceResponse.of(content, mentorSlice); } private List getContent(List mentors, SiteUser siteUser) { @@ -68,8 +64,4 @@ private List getContent(List mentors, SiteUser si } return mentorPreviews; } - - private int getPageNumber(Slice page) { - return page.hasNext() ? page.nextPageable().getPageNumber() + 1 : NO_NEXT_PAGE_NUMBER; // one-based index - } } From 2836715b9bd3f7e1a74ed3f442622d62fda6af24 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 8 Jul 2025 20:43:36 +0900 Subject: [PATCH 22/25] =?UTF-8?q?refactor:=20=EC=9E=90=EC=97=B0=EC=8A=A4?= =?UTF-8?q?=EB=9F=BD=EA=B2=8C=20=EC=9D=BD=ED=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/solidconnection/common/dto/SliceResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java index d99a35849..3f91dc7c4 100644 --- a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java +++ b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java @@ -12,7 +12,7 @@ public record SliceResponse( private static final int NO_NEXT_PAGE = -1; private static final int BASE_NUMBER = 1; // 1-based - public static SliceResponse of(Slice slice, List content) { + public static SliceResponse of(List content, Slice slice) { int nextPageNumber = slice.hasNext() ? slice.getNumber() + BASE_NUMBER + 1 : NO_NEXT_PAGE; From f917a0be85dfd08c6be3bdd274e326cb62747872 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 8 Jul 2025 20:43:59 +0900 Subject: [PATCH 23/25] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=9D=B4=20=EC=9D=98=EB=AF=B8=EB=A5=BC=20?= =?UTF-8?q?=EB=93=9C=EB=9F=AC=EB=82=B4=EB=8F=84=EB=A1=9D=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/mentor/service/MentorQueryService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index f22c7beb7..5c9346b3c 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -46,12 +46,12 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 Slice mentorSlice = mentorRepository.findBy(pageable); List mentors = mentorSlice.toList(); - List content = getContent(mentors, siteUser); + List content = getMentorPreviewResponses(mentors, siteUser); return SliceResponse.of(content, mentorSlice); } - private List getContent(List mentors, SiteUser siteUser) { + private List getMentorPreviewResponses(List mentors, SiteUser siteUser) { Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, siteUser.getId()); From d906c7d89f57f05b77c07e830b126a01487518e3 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 8 Jul 2025 20:45:59 +0900 Subject: [PATCH 24/25] =?UTF-8?q?refactor:=20JPA=20=ED=91=9C=EC=A4=80=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B4=EB=8F=84=EB=A1=9D=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findBy -> findAllBy --- .../solidconnection/mentor/repository/MentorRepository.java | 2 +- .../solidconnection/mentor/service/MentorQueryService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index 8b896a9d2..49324c724 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -7,5 +7,5 @@ public interface MentorRepository extends JpaRepository { - Slice findBy(Pageable pageable); + Slice findAllBy(Pageable pageable); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index 5c9346b3c..a7c46e285 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -44,7 +44,7 @@ public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser @Transactional(readOnly = true) public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 - Slice mentorSlice = mentorRepository.findBy(pageable); + Slice mentorSlice = mentorRepository.findAllBy(pageable); List mentors = mentorSlice.toList(); List content = getMentorPreviewResponses(mentors, siteUser); From 58fd475f500003d23bc318ed4d85fbcc84998433 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Tue, 8 Jul 2025 21:10:37 +0900 Subject: [PATCH 25/25] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=EC=97=90=EC=84=9C=20=EC=B1=84=EB=84=90?= =?UTF-8?q?=EC=9D=98=20=EC=88=9C=EC=84=9C=EB=A5=BC=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=ED=95=B4=20=EA=B0=80=EC=A7=80=EA=B3=A0=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test도 더 정확하게 수정 --- .../example/solidconnection/mentor/domain/Mentor.java | 2 ++ .../mentor/dto/MentorPreviewResponse.java | 11 +---------- .../mentor/service/MentorQueryServiceTest.java | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 2d0176411..b1b34da9c 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -7,6 +7,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -45,6 +46,7 @@ public class Mentor { private long universityId; @BatchSize(size = 10) + @OrderBy("sequence ASC") @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) private List channels = new ArrayList<>(); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java index 25e3c0542..bba3010f4 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -1,11 +1,9 @@ package com.example.solidconnection.mentor.dto; -import com.example.solidconnection.mentor.domain.Channel; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; -import java.util.Comparator; import java.util.List; public record MentorPreviewResponse( @@ -33,15 +31,8 @@ public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boole mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), - toChannelResponses(mentor.getChannels()), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), isApplied ); } - - private static List toChannelResponses(List channels) { - return channels.stream() - .sorted(Comparator.comparingInt(Channel::getSequence)) - .map(ChannelResponse::from) - .toList(); - } } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index 536244a6b..421d24a12 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -71,7 +71,7 @@ class 멘토_단일_조회_성공 { () -> assertThat(response.id()).isEqualTo(mentor.getId()), () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), () -> assertThat(response.channels()).extracting(ChannelResponse::url) - .containsOnly(channel1.getUrl(), channel2.getUrl()) + .containsExactly(channel1.getUrl(), channel2.getUrl()) ); }