Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,29 @@ public ApplicationPreviewResponse getApplicantUniversityPreviews(long siteUserId
applicationRepository.findApplicantUniversityPreviews(
VerifyStatus.APPROVED,
term.getId(),
siteUser.getHomeUniversityId())
siteUser.getHomeUniversityId()
)
);
}

@Transactional(readOnly = true)
public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) {
SiteUser siteUser = siteUserRepository.findById(siteUserId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
Long homeUniversityId = siteUser.getHomeUniversityId();

if (homeUniversityId == null) {
throw new CustomException(SCHOOL_EMAIL_NOT_VERIFIED);
}

List<String> keywords = StringUtils.isNotBlank(keyword) ? List.of(keyword) : List.of();

Term term = termRepository.findByIsCurrentTrue()
.orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND));

List<UnivApplyInfo> univApplyInfos = universityFilterRepository
.findAllByRegionCodeAndKeywordsAndTermId(regionCode, keywords, term.getId());
.findAllByRegionCodeAndKeywordsAndTermIdAndHomeUniversityId(
regionCode, keywords, term.getId(), siteUser.getHomeUniversityId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

1. API 예외 순서를 보장하십시오.

ApplicationController.getApplicantsgetApplicants 전에 validateSiteUserCanViewApplicants를 호출합니다. 모교가 없는 사용자는 Line 74의 검사 전에 다른 지원서 검증 처리 또는 예외를 받을 수 있습니다.

validateSiteUserCanViewApplicants에서 homeUniversityId를 먼저 검사하십시오. 컨트롤러 경로를 호출하는 테스트도 추가하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java`
around lines 68 - 85, Update validateSiteUserCanViewApplicants to check the
requesting SiteUser’s homeUniversityId before performing any other
applicant-validation logic, throwing SCHOOL_EMAIL_NOT_VERIFIED when it is
absent. Ensure the ApplicationController.getApplicants path invokes this
validation first, and add coverage for a controller request from a user without
a home university.

Comment on lines +83 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter applicants by the viewer's home as well

When a user from another home university has an approved application whose choice points at one of this viewer's UnivApplyInfo IDs, this path still includes that applicant because only the university list is scoped by homeUniversityId; ApplicationSubmissionService.getValidUnivApplyInfos currently accepts any existing choice ID, and the later findAllByUnivApplyInfoIds query does not join/filter the applicant's SiteUser.homeUniversityId. Please also enforce the home-university match at submission or filter the returned applications by the applicant's home so /applications cannot still expose other-home applicants for in-scope university IDs.

Useful? React with 👍 / 👎.

if (univApplyInfos.isEmpty()) {
return new ApplicationsResponse(List.of());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

public interface UnivApplyInfoFilterRepository {

List<UnivApplyInfo> findAllByRegionCodeAndKeywordsAndTermId(String regionCode, List<String> keywords, Long term);
List<UnivApplyInfo> findAllByRegionCodeAndKeywordsAndTermIdAndHomeUniversityId(
String regionCode,
List<String> keywords,
Long term,
long homeUniversityId);

List<UnivApplyInfo> findAllByText(String text, Long termId, Long homeUniversityId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public UnivApplyInfoFilterRepositoryImpl(EntityManager em) {
}

@Override
public List<UnivApplyInfo> findAllByRegionCodeAndKeywordsAndTermId(String regionCode, List<String> keywords, Long termId) {
public List<UnivApplyInfo> findAllByRegionCodeAndKeywordsAndTermIdAndHomeUniversityId(
String regionCode,
List<String> keywords,
Long termId,
long homeUniversityId
) {
QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo;
QHostUniversity university = QHostUniversity.hostUniversity;
QHomeUniversity homeUniversity = QHomeUniversity.homeUniversity;
Expand All @@ -42,12 +47,13 @@ public List<UnivApplyInfo> findAllByRegionCodeAndKeywordsAndTermId(String region
.selectFrom(univApplyInfo)
.join(univApplyInfo.university, university).fetchJoin()
.join(university.country, country).fetchJoin()
.leftJoin(univApplyInfo.homeUniversity, homeUniversity).fetchJoin()
.join(univApplyInfo.homeUniversity, homeUniversity).fetchJoin()
.leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin()
.where(
regionCodeEq(country, regionCode)
.and(countryOrUniversityContainsKeyword(country, university, keywords))
.and(univApplyInfo.termId.eq(termId))
.and(homeUniversity.id.eq(homeUniversityId))
)
.distinct()
.fetch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.example.solidconnection.support.TestContainerSpringBootTest;
import com.example.solidconnection.term.domain.Term;
import com.example.solidconnection.term.fixture.TermFixture;
import com.example.solidconnection.university.domain.HomeUniversity;
import com.example.solidconnection.university.domain.UnivApplyInfo;
import com.example.solidconnection.university.fixture.HomeUniversityFixture;
import com.example.solidconnection.university.fixture.UnivApplyInfoFixture;
Expand Down Expand Up @@ -93,16 +94,17 @@ class ApplicationQueryServiceTest {
@BeforeEach
void setUp() {
term = termFixture.현재_학기("2025-2");
HomeUniversity 인하대학교 = homeUniversityFixture.인하대학교();

user1 = siteUserFixture.사용자(1, "test1");
user1 = siteUserFixture.국내_대학_정보_소지_사용자(1, "test1", 인하대학교.getId());
gpaScore1 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user1);
languageTestScore1 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user1);

user2 = siteUserFixture.사용자(2, "test2");
user2 = siteUserFixture.국내_대학_정보_소지_사용자(2, "test2", 인하대학교.getId());
gpaScore2 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user2);
languageTestScore2 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user2);

user3 = siteUserFixture.사용자(3, "test3");
user3 = siteUserFixture.국내_대학_정보_소지_사용자(3, "test3", 인하대학교.getId());
gpaScore3 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user3);
languageTestScore3 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user3);

Expand Down Expand Up @@ -211,9 +213,13 @@ class 지원_현황_미리보기_조회_테스트 {

@Test
void 모교가_등록되지_않은_사용자는_미리보기를_조회할_수_없다() {
// given
SiteUser userWithoutHomeUniversity = siteUserFixture.사용자(4, "test4");

// when
// then
assertThatThrownBy(() -> applicationQueryService.getApplicantUniversityPreviews(user1.getId()))
assertThatThrownBy(() -> applicationQueryService.getApplicantUniversityPreviews(
userWithoutHomeUniversity.getId()))
.isInstanceOf(CustomException.class)
.hasMessage(SCHOOL_EMAIL_NOT_VERIFIED.getMessage());
}
Expand Down Expand Up @@ -362,6 +368,50 @@ class 지원자_목록_조회_테스트 {
.filter(ApplicantResponse::isMine))
.containsExactly(ApplicantResponse.of(secondApplication, true));
}

@Test
void 다른_모교의_지원_대학과_지원자는_조회되지_않는다() {
// given
UnivApplyInfo 인천대학교_전용_지원_정보 = univApplyInfoFixtureBuilder.univApplyInfo()
.termId(term.getId())
.koreanName("인천대학교 전용 교환 대학")
.university(서던덴마크대학교_지원_정보.getUniversity())
.homeUniversity(homeUniversityFixture.인천대학교())
.create();
Application application1 = applicationFixture.지원서(
user1, "nickname1", term.getId(),
gpaScore1.getGpa(), languageTestScore1.getLanguageTest(),
List.of(괌대학_A_지원_정보.getId())
);
applicationFixture.지원서(
user2, "nickname2", term.getId(),
gpaScore2.getGpa(), languageTestScore2.getLanguageTest(),
List.of(인천대학교_전용_지원_정보.getId())
);

// when
ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", "");

// then
assertThat(response.choices().get(0)).containsExactlyInAnyOrder(
ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1),
ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(), user1),
ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(), user1)
);
}

@Test
void 모교가_등록되지_않은_사용자는_지원자_목록을_조회할_수_없다() {
// given
SiteUser userWithoutHomeUniversity = siteUserFixture.사용자(4, "test4");

// when
// then
assertThatThrownBy(() -> applicationQueryService.getApplicants(
userWithoutHomeUniversity.getId(), "", ""))
.isInstanceOf(CustomException.class)
.hasMessage(SCHOOL_EMAIL_NOT_VERIFIED.getMessage());
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ public class SiteUserFixture {
.create();
}

public SiteUser 국내_대학_정보_소지_사용자(int index, String nickname, Long homeUniversityId) {
return siteUserFixtureBuilder.siteUser()
.email("university" + index + "@example.com")
.authType(AuthType.EMAIL)
.nickname(nickname)
.homeUniversityId(homeUniversityId)
.profileImageUrl("profileImageUrl")
.role(Role.MENTEE)
.password("password123")
.userStatus(UserStatus.ACTIVE)
.create();
}

public SiteUser 멘토(int index, String nickname) {
return siteUserFixtureBuilder.siteUser()
.email("mentor" + index + "@example.com")
Expand Down
Loading