diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/application/UniversityController.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/application/UniversityController.kt index 889d615..7066fdf 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/application/UniversityController.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/application/UniversityController.kt @@ -2,6 +2,9 @@ package org.example.beyondubackend.domain.university.application import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.Parameters +import io.swagger.v3.oas.annotations.enums.ParameterIn +import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.tags.Tag import org.example.beyondubackend.common.annotation.ExamScoreParams import org.example.beyondubackend.common.dto.ApiResponse @@ -9,6 +12,7 @@ import org.example.beyondubackend.domain.university.application.dto.UniversitySe import org.example.beyondubackend.domain.university.business.UniversityService import org.example.beyondubackend.domain.university.business.dto.UniversityDetailResponse import org.example.beyondubackend.domain.university.business.dto.UniversityListResponse +import org.springdoc.core.annotations.ParameterObject import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Sort import org.springframework.http.ResponseEntity @@ -25,18 +29,61 @@ class UniversityController( summary = "대학교 목록 조회", description = """ 대학교 목록을 조회합니다. - - 필터링: nation, isExchange, isVisit, gpa, nations, major, hasReview - - 언어 점수: TOEFL, IELTS, TOEIC, JLPT 등 - - 페이지네이션: page + - 필터링: nation, isExchange, isVisit, gpa, major, hasReview + - 어학 점수 필터: TOEFL_IBT, TOEFL_ITP, IELTS, TOEIC, TOEIC_Speaking, HSK, JLPT, JPT, DELF, ZD + - 페이지네이션: page (기본값 0), size (기본값 12) + - 모든 파라미터는 optional이며, null이면 전체 조회 """ ) + @Parameters(value = [ + Parameter( + name = "TOEFL_IBT", description = "TOEFL iBT 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "80") + ), + Parameter( + name = "TOEFL_ITP", description = "TOEFL ITP 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "550") + ), + Parameter( + name = "IELTS", description = "IELTS 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "6.5") + ), + Parameter( + name = "TOEIC", description = "TOEIC 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "800") + ), + Parameter( + name = "TOEIC_Speaking", description = "TOEIC Speaking 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "160") + ), + Parameter( + name = "HSK", description = "HSK 급수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "4") + ), + Parameter( + name = "JLPT", description = "JLPT 레벨 (1=N1 ~ 5=N5, 숫자가 낮을수록 높은 레벨)", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "2") + ), + Parameter( + name = "JPT", description = "JPT 점수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "700") + ), + Parameter( + name = "DELF", description = "DELF 급수", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "4") + ), + Parameter( + name = "ZD", description = "ZD 급수 (독어 자격증)", + required = false, `in` = ParameterIn.QUERY, schema = Schema(type = "number", example = "4") + ) + ]) @GetMapping fun getUniversities( - @ModelAttribute request: UniversitySearchRequest, - @Parameter(description = "언어 시험 점수 (예: TOEFL=80, IELTS=6.5)", required = false) - @ExamScoreParams examScores: Map, + @ParameterObject @ModelAttribute request: UniversitySearchRequest, + @Parameter(hidden = true) @ExamScoreParams examScores: Map, @Parameter(description = "페이지 번호", example = "0") @RequestParam(defaultValue = "0") page: Int, + @Parameter(description = "페이지 크기", example = "12") @RequestParam(defaultValue = "12") size: Int ): ResponseEntity> { val pageable = PageRequest.of(page, size, Sort.by(Sort.Order.asc("nameEng"), Sort.Order.asc("nameKor"))) diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/application/dto/UniversitySearchRequest.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/application/dto/UniversitySearchRequest.kt index 69073b8..fdb46c6 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/application/dto/UniversitySearchRequest.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/application/dto/UniversitySearchRequest.kt @@ -1,15 +1,23 @@ package org.example.beyondubackend.domain.university.application.dto +import io.swagger.v3.oas.annotations.media.Schema import org.example.beyondubackend.domain.university.business.query.UniversityQuery data class UniversitySearchRequest( + @Schema(description = "국가 필터 (예: 미국, 일본)") val nation: String? = null, + @Schema(description = "교환학생 가능 여부") val isExchange: Boolean? = null, + @Schema(description = "방문학생 가능 여부") val isVisit: Boolean? = null, + //TO DO: 대학 이름 검색 사용시 추가 + @Schema(hidden = true) val search: String? = null, + @Schema(description = "최소 GPA (입력한 GPA 이상 지원 가능한 학교 조회)", example = "3.5") val gpa: Double? = null, - val nations: String? = null, + @Schema(description = "전공 필터") val major: String? = null, + @Schema(description = "후기 보유 여부") val hasReview: Boolean? = null ) { fun toQuery(examScores: Map): UniversityQuery { @@ -19,7 +27,6 @@ data class UniversitySearchRequest( isVisit = isVisit, search = search, gpa = gpa, - nations = nations, major = major, hasReview = hasReview, examScores = examScores diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/business/UniversityServiceImpl.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/business/UniversityServiceImpl.kt index 0d9cc52..6ac40f4 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/business/UniversityServiceImpl.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/business/UniversityServiceImpl.kt @@ -25,7 +25,6 @@ class UniversityServiceImpl( isVisit = query.isVisit, search = query.search, gpa = query.gpa, - nations = query.nations, major = query.major, hasReview = query.hasReview, examScores = query.examScores, diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/business/dto/UniversityDetailResponse.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/business/dto/UniversityDetailResponse.kt index cee5332..e63e589 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/business/dto/UniversityDetailResponse.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/business/dto/UniversityDetailResponse.kt @@ -10,6 +10,7 @@ data class UniversityDetailResponse( val region: String, val isExchange: Boolean, val isVisit: Boolean, + val programType: String, val badge: String, val hasReview: Boolean, val minGpa: Double, @@ -20,12 +21,19 @@ data class UniversityDetailResponse( val availableMajors: List?, val courseListUrl: String?, val studentCount: String?, + val location: String?, ) { companion object { fun from( university: University, languageRequirements: List ): UniversityDetailResponse { + val programType = when { + university.isExchange -> "일반교환" + university.isVisit -> "방문교환" + else -> "" + } + return UniversityDetailResponse( id = university.id!!, nameKor = university.nameKor, @@ -34,6 +42,7 @@ data class UniversityDetailResponse( region = university.region, isExchange = university.isExchange, isVisit = university.isVisit, + programType = programType, badge = university.badge, hasReview = university.hasReview, minGpa = university.minGpa, @@ -44,6 +53,7 @@ data class UniversityDetailResponse( availableMajors = parseAvailableMajors(university.availableMajors), courseListUrl = parseCourseListUrl(university.availableMajors), studentCount = parseStudentCount(university.remark), + location = parseLocation(university.remark), ) } @@ -74,6 +84,18 @@ data class UniversityDetailResponse( return regex.find(availableMajors)?.groupValues?.get(1)?.trim() } + /** + * remark에서 위치 파싱 + * 예: "* 위치: Vechta\n* 특징: ..." -> "Vechta" + * 예: "* 위치: Brühl (쾰른에서 기차로 10분) * 특징: ..." -> "Brühl (쾰른에서 기차로 10분)" + * 없으면 null 반환 + */ + private fun parseLocation(remark: String?): String? { + if (remark == null) return null + val regex = """\*\s*위치\s*:\s*([^*\n]+)""".toRegex() + return regex.find(remark)?.groupValues?.get(1)?.trim()?.takeIf { it.isNotBlank() } + } + /** * remark에서 학생 수 파싱 * 예: "... 학생 수 약 28,600명 ..." -> "약 28,600명" @@ -83,7 +105,7 @@ data class UniversityDetailResponse( if (remark == null) return "학생 수 정보 없음" val regex = """학생\s*수\s*약?\s*([\d,]+)\s*명""".toRegex() val count = regex.find(remark)?.groupValues?.get(1)?.trim() - return if (count != null) "약 ${count} 명" else "학생 수 정보 없음" + return if (count != null) "약 $count 명" else "학생 수 정보 없음" } } } diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/business/query/UniversityQuery.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/business/query/UniversityQuery.kt index 7fd2a52..8e1914b 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/business/query/UniversityQuery.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/business/query/UniversityQuery.kt @@ -6,7 +6,6 @@ data class UniversityQuery( val isVisit: Boolean? = null, val search: String? = null, val gpa: Double? = null, - val nations: String? = null, val major: String? = null, val hasReview: Boolean? = null, val examScores: Map = emptyMap() diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityReader.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityReader.kt index 8c75792..66ff868 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityReader.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityReader.kt @@ -20,14 +20,13 @@ class UniversityReader( isVisit: Boolean?, search: String?, gpa: Double?, - nations: String?, major: String?, hasReview: Boolean?, examScores: Map, pageable: Pageable ): Page { return universityRepository.findAllWithFilters( - nation, isExchange, isVisit, search, gpa, nations, major, hasReview, examScores, pageable + nation, isExchange, isVisit, search, gpa, major, hasReview, examScores, pageable ) } diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityRepository.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityRepository.kt index 9db37da..2680c3b 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityRepository.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/implement/UniversityRepository.kt @@ -10,7 +10,6 @@ interface UniversityRepository { isVisit: Boolean?, search: String?, gpa: Double?, - nations: String?, major: String?, hasReview: Boolean?, examScores: Map, diff --git a/src/main/kotlin/org/example/beyondubackend/domain/university/storage/UniversityRepositoryImpl.kt b/src/main/kotlin/org/example/beyondubackend/domain/university/storage/UniversityRepositoryImpl.kt index fbdc8eb..9f23bdd 100644 --- a/src/main/kotlin/org/example/beyondubackend/domain/university/storage/UniversityRepositoryImpl.kt +++ b/src/main/kotlin/org/example/beyondubackend/domain/university/storage/UniversityRepositoryImpl.kt @@ -24,7 +24,6 @@ class UniversityRepositoryImpl( isVisit: Boolean?, search: String?, gpa: Double?, - nations: String?, major: String?, hasReview: Boolean?, examScores: Map, @@ -39,7 +38,6 @@ class UniversityRepositoryImpl( isVisitEq(isVisit), searchKeyword(search), gpaLoe(gpa), - nationsIn(nations), majorContains(major), hasReviewEq(hasReview), examScoresMatch(examScores) @@ -66,7 +64,6 @@ class UniversityRepositoryImpl( isVisitEq(isVisit), searchKeyword(search), gpaLoe(gpa), - nationsIn(nations), majorContains(major), hasReviewEq(hasReview), examScoresMatch(examScores)