Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
@@ -1,7 +1,6 @@
package com.example.solidconnection.application.domain;

import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.university.domain.UniversityInfoForApply;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -54,14 +53,14 @@ public class Application {
@Column
private boolean isDelete = false;

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply firstChoiceUniversity;
@Column(nullable = false)
private Long firstChoiceUniversityApplyInfoId;

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply secondChoiceUniversity;
@Column
private Long secondChoiceUniversityApplyInfoId;

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply thirdChoiceUniversity;
@Column
private Long thirdChoiceUniversityApplyInfoId;
Comment thread
nayonsoso marked this conversation as resolved.
Outdated

@ManyToOne(fetch = FetchType.LAZY)
private SiteUser siteUser;
Expand All @@ -85,18 +84,18 @@ public Application(
LanguageTest languageTest,
String term,
Integer updateCount,
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
Long firstChoiceUniversityApplyInfoId,
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Long secondChoiceUniversityApplyInfoId,
Long thirdChoiceUniversityApplyInfoId,
String nicknameForApply) {
this.siteUser = siteUser;
this.gpa = gpa;
this.languageTest = languageTest;
this.term = term;
this.updateCount = updateCount;
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId;
this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId;
this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId;
this.nicknameForApply = nicknameForApply;
this.verifyStatus = PENDING;
}
Expand All @@ -106,37 +105,23 @@ public Application(
Gpa gpa,
LanguageTest languageTest,
String term,
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
Long firstChoiceUniversityApplyInfoId,
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Long secondChoiceUniversityApplyInfoId,
Long thirdChoiceUniversityApplyInfoId,
String nicknameForApply) {
this.siteUser = siteUser;
this.gpa = gpa;
this.languageTest = languageTest;
this.term = term;
this.updateCount = 1;
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId;
this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId;
this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId;
this.nicknameForApply = nicknameForApply;
this.verifyStatus = PENDING;
}

public void setIsDeleteTrue() {
this.isDelete = true;
}

public void updateUniversityChoice(
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
String nicknameForApply) {
if (this.firstChoiceUniversity != null) {
this.updateCount++;
}
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.nicknameForApply = nicknameForApply;
}
Comment thread
nayonsoso marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,26 @@ public interface ApplicationRepository extends JpaRepository<Application, Long>

boolean existsByNicknameForApply(String nicknameForApply);

List<Application> findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term);

List<Application> findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term);

List<Application> findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term);
@Query("""
SELECT a
FROM Application a
JOIN FETCH a.siteUser
WHERE (a.firstChoiceUniversityApplyInfoId IN :universityIds
OR a.secondChoiceUniversityApplyInfoId IN :universityIds
OR a.thirdChoiceUniversityApplyInfoId IN :universityIds)
AND a.verifyStatus = :status
AND a.term = :term
AND a.isDelete = false
""")
List<Application> findApplicationsByUniversityChoices(@Param("universityIds") List<Long> universityIds, @Param("status") VerifyStatus status, @Param("term") String term);

@Query("""
SELECT a FROM Application a
WHERE a.siteUser = :siteUser
AND a.term = :term
AND a.isDelete = false
""")
SELECT a
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
FROM Application a
WHERE a.siteUser = :siteUser
AND a.term = :term
AND a.isDelete = false
""")
Optional<Application> findBySiteUserAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term);

default Application getApplicationBySiteUserAndTerm(SiteUser siteUser, String term) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import com.example.solidconnection.application.dto.ApplicationsResponse;
import com.example.solidconnection.application.dto.UniversityApplicantsResponse;
import com.example.solidconnection.application.repository.ApplicationRepository;
import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.university.domain.University;
import com.example.solidconnection.university.domain.UniversityInfoForApply;
import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository;
import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl;
Expand All @@ -18,12 +16,14 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;

import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED;

Expand All @@ -38,97 +38,98 @@ public class ApplicationQueryService {
@Value("${university.term}")
public String term;

/*
* 다른 지원자들의 성적을 조회한다.
* - 유저가 다른 지원자들을 볼 수 있는지 검증한다.
* - 지역과 키워드를 통해 대학을 필터링한다.
* - 지역은 영어 대문자로 받는다 e.g. ASIA
* - 1지망, 2지망 지원자들을 조회한다.
* */
// todo: 캐싱 정책 변경 시 수정 필요
@Transactional(readOnly = true)
// todo: 임시로 단일 키로 캐시 적용. 추후 캐싱 전략 재검토 필요.
@ThunderingHerdCaching(key = "applications:all", cacheManager = "customCacheManager", ttlSec = 86400)
public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) {
// 국가와 키워드와 지역을 통해 대학을 필터링한다.
List<University> universities
= universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));

// 1지망, 2지망, 3지망 지원자들을 조회한다.
List<UniversityApplicantsResponse> firstChoiceApplicants = getFirstChoiceApplicants(universities, siteUser, term);
List<UniversityApplicantsResponse> secondChoiceApplicants = getSecondChoiceApplicants(universities, siteUser, term);
List<UniversityApplicantsResponse> thirdChoiceApplicants = getThirdChoiceApplicants(universities, siteUser, term);
return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants);
// 1. 대학 ID 필터링 (regionCode, keyword)
List<Long> universityIds = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));
if (universityIds.isEmpty()) {
return new ApplicationsResponse(List.of(), List.of(), List.of());
}

// 2. 조건에 맞는 모든 Application 한 번에 조회
List<Application> applications = applicationRepository.findApplicationsByUniversityChoices(universityIds, VerifyStatus.APPROVED, term);

// 3. 대학정보 조회
List<UniversityInfoForApply> universityInfosForApply = universityInfoForApplyRepository.findByUniversityIdsWithUniversityAndLocation(universityIds);

// 4. 지원서 분류 및 DTO 변환
return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser);
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
}

@Transactional(readOnly = true)
public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) {
Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term);
List<University> userAppliedUniversities = Arrays.asList(
Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null),
Optional.ofNullable(userLatestApplication.getSecondChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null),
Optional.ofNullable(userLatestApplication.getThirdChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null)
).stream()

List<Long> universityInfoForApplyIds = Stream.of(
userLatestApplication.getFirstChoiceUniversityApplyInfoId(),
userLatestApplication.getSecondChoiceUniversityApplyInfoId(),
userLatestApplication.getThirdChoiceUniversityApplyInfoId()
)
.filter(Objects::nonNull)
.collect(Collectors.toList());

List<UniversityApplicantsResponse> firstChoiceApplicants = getFirstChoiceApplicants(userAppliedUniversities, siteUser, term);
List<UniversityApplicantsResponse> secondChoiceApplicants = getSecondChoiceApplicants(userAppliedUniversities, siteUser, term);
List<UniversityApplicantsResponse> thirdChoiceApplicants = getThirdChoiceApplicants(userAppliedUniversities, siteUser, term);
return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants);
}

// 학기별로 상태가 관리된다.
// 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다.
@Transactional(readOnly = true)
public void validateSiteUserCanViewApplicants(SiteUser siteUser) {
VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus();
if (verifyStatus != VerifyStatus.APPROVED) {
throw new CustomException(APPLICATION_NOT_APPROVED);
if (universityInfoForApplyIds.isEmpty()) {
return new ApplicationsResponse(List.of(), List.of(), List.of());
}
}

private List<UniversityApplicantsResponse> getFirstChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
List<Application> applications = applicationRepository.findApplicationsByUniversityChoices(universityInfoForApplyIds, VerifyStatus.APPROVED, term);
List<UniversityInfoForApply> universityInfosForApply = universityInfoForApplyRepository.findByIdsWithUniversityAndLocation(universityInfoForApplyIds);

return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser);
}

private List<UniversityApplicantsResponse> getSecondChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
private ApplicationsResponse classifyApplicationsByChoice(
List<UniversityInfoForApply> universityInfosForApply,
List<Application> applications,
SiteUser siteUser) {
Map<Long, List<Application>> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUniversityApplyInfoId);
Map<Long, List<Application>> secondChoiceMap = createChoiceMap(applications, Application::getSecondChoiceUniversityApplyInfoId);
Map<Long, List<Application>> thirdChoiceMap = createChoiceMap(applications, Application::getThirdChoiceUniversityApplyInfoId);

List<UniversityApplicantsResponse> firstChoiceApplicants =
createUniversityApplicantsResponses(universityInfosForApply, firstChoiceMap, siteUser);
List<UniversityApplicantsResponse> secondChoiceApplicants =
createUniversityApplicantsResponses(universityInfosForApply, secondChoiceMap, siteUser);
List<UniversityApplicantsResponse> thirdChoiceApplicants =
createUniversityApplicantsResponses(universityInfosForApply, thirdChoiceMap, siteUser);

return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants);
}

private List<UniversityApplicantsResponse> getThirdChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
private Map<Long, List<Application>> createChoiceMap(
List<Application> applications,
Function<Application, Long> choiceIdExtractor) {
Map<Long, List<Application>> choiceMap = new HashMap<>();

for (Application application : applications) {
Long choiceId = choiceIdExtractor.apply(application);
if (choiceId != null) {
choiceMap.computeIfAbsent(choiceId, k -> new ArrayList<>()).add(application);
}
}

return choiceMap;
}

private List<UniversityApplicantsResponse> getApplicantsByChoice(
List<University> searchedUniversities,
SiteUser siteUser,
Function<UniversityInfoForApply, List<Application>> findApplicationsByChoice) {
return universityInfoForApplyRepository.findByUniversitiesAndTerm(searchedUniversities, term).stream()
.map(universityInfoForApply -> UniversityApplicantsResponse.of(
universityInfoForApply,
findApplicationsByChoice.apply(universityInfoForApply).stream()
.map(ap -> ApplicantResponse.of(
ap,
Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
private List<UniversityApplicantsResponse> createUniversityApplicantsResponses(
List<UniversityInfoForApply> universityInfosForApply,
Map<Long, List<Application>> choiceMap,
SiteUser siteUser) {
return universityInfosForApply.stream()
.map(uia -> UniversityApplicantsResponse.of(
uia,
choiceMap.getOrDefault(uia.getId(), List.of()).stream()
.map(ap -> ApplicantResponse.of(ap, Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
.toList()))
.toList();
}

@Transactional(readOnly = true)
public void validateSiteUserCanViewApplicants(SiteUser siteUser) {
VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus();
if (verifyStatus != VerifyStatus.APPROVED) {
throw new CustomException(APPLICATION_NOT_APPROVED);
}
}
}
Loading
Loading