diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt index 61043e39..ac46e33e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt @@ -1,11 +1,14 @@ package com.wafflestudio.csereal.core.about.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.about.dto.AboutRequest import com.wafflestudio.csereal.core.about.dto.FutureCareersRequest import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -68,6 +71,34 @@ class AboutController( return ResponseEntity.ok(aboutService.readFutureCareers()) } + @GetMapping("/search/top") + fun searchTopAbout( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ): AboutSearchResBody = aboutService.searchTopAbout( + keyword, + LanguageType.makeStringToLanguageType(language), + number, + amount + ) + + @GetMapping("/search") + fun searchPageAbout( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ): AboutSearchResBody = aboutService.searchPageAbout( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount + ) + @PostMapping("/migrate") fun migrateAbout( @RequestBody requestList: List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt new file mode 100644 index 00000000..0e6a09ff --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchElementDto.kt @@ -0,0 +1,37 @@ +package com.wafflestudio.csereal.core.about.api.res + +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.about.database.AboutPostType + +data class AboutSearchElementDto private constructor( + val id: Long, + val language: String, + val aboutPostType: AboutPostType, + val name: String?, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int +) { + companion object { + fun of(about: AboutEntity, keyword: String, amount: Int) = about.run { + val (boldStartIdx, partialDescription) = substringAroundKeyword( + keyword, + cleanTextFromHtml(description), + amount + ) + + AboutSearchElementDto( + id = id, + language = LanguageType.makeLowercase(language), + aboutPostType = postType, + name = name, + partialDescription = partialDescription.replace('\n', ' '), + boldStartIndex = boldStartIdx ?: 0, + boldEndIndex = boldStartIdx?.plus(keyword.length) ?: 0 + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt new file mode 100644 index 00000000..5c48e99b --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/res/AboutSearchResBody.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.about.api.res + +data class AboutSearchResBody( + val total: Long, + val results: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 2b97fd06..4a334658 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -5,6 +5,7 @@ import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.StringListConverter +import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.about.dto.AboutDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -31,22 +32,66 @@ class AboutEntity( var attachments: MutableList = mutableListOf(), @OneToOne - var mainImage: MainImageEntity? = null + var mainImage: MainImageEntity? = null, + + @Column(columnDefinition = "TEXT") + var searchContent: String ) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { override fun bringMainImage(): MainImageEntity? = mainImage override fun bringAttachments(): List = attachments companion object { - fun of(postType: AboutPostType, languageType: LanguageType, aboutDto: AboutDto): AboutEntity { + fun of( + postType: AboutPostType, + languageType: LanguageType, + aboutDto: AboutDto + ): AboutEntity { return AboutEntity( postType = postType, language = languageType, name = aboutDto.name, description = aboutDto.description, year = aboutDto.year, - locations = aboutDto.locations?.toMutableList() ?: mutableListOf() + locations = aboutDto.locations?.toMutableList() ?: mutableListOf(), + searchContent = "" ) } + + fun createContent(name: String?, description: String, locations: List) = StringBuilder().apply { + name?.let { appendLine(it) } + appendLine(cleanTextFromHtml(description)) + locations.forEach { + appendLine(it) + } + }.toString() + + fun createContent( + name: String?, + description: String, + statNames: List, + companyNames: List + ): String { + return StringBuilder().apply { + name?.let { appendLine(it) } + appendLine(cleanTextFromHtml(description)) + statNames.forEach { + appendLine(it) + } + companyNames.forEach { + appendLine(it) + } + }.toString() + } + } + + fun syncSearchContent() { + assert(postType != AboutPostType.FUTURE_CAREERS) + searchContent = createContent(name, description, locations) + } + + fun syncSearchContent(statNames: List, companyNames: List) { + assert(postType == AboutPostType.FUTURE_CAREERS) + searchContent = createContent(name, description, statNames, companyNames) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt index b98a26fd..727cbb09 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutRepository.kt @@ -1,9 +1,15 @@ package com.wafflestudio.csereal.core.about.database +import com.querydsl.jpa.impl.JPAQuery +import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.core.about.database.QAboutEntity.aboutEntity import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository -interface AboutRepository : JpaRepository { +interface AboutRepository : JpaRepository, AboutCustomRepository { fun findAllByLanguageAndPostTypeOrderByName( languageType: LanguageType, postType: AboutPostType @@ -13,3 +19,58 @@ interface AboutRepository : JpaRepository { postType: AboutPostType ): AboutEntity } + +interface AboutCustomRepository { + fun searchAbouts( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> +} + +@Repository +class AboutCustomRepositoryImpl( + private val queryFactory: JPAQueryFactory, + private val commonRepository: CommonRepository +) : AboutCustomRepository { + override fun searchAbouts( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val total = searchCount(keyword, language) + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = ( + if (validPageNum >= 1) validPageNum - 1 else 0 + ) * pageSize.toLong() + + val queryResult = searchQueryExpr(keyword, language) + .offset(validOffset) + .limit(pageSize.toLong()) + .fetch() + + return queryResult to total + } + + fun searchCount(keyword: String, language: LanguageType): Long { + return searchQueryExpr(keyword, language) + .select(aboutEntity.countDistinct()) + .fetchOne()!! + } + + fun searchQueryExpr(keyword: String, language: LanguageType): JPAQuery { + val matchExpression = commonRepository.searchFullSingleTextTemplate( + keyword, + aboutEntity.searchContent + ) + + return queryFactory.select(aboutEntity) + .from(aboutEntity) + .where( + matchExpression.gt(0.0), + aboutEntity.language.eq(language) + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt index c63b6c3a..e85732e7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -2,6 +2,8 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.about.api.res.AboutSearchElementDto +import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.database.* import com.wafflestudio.csereal.core.about.dto.* import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService @@ -24,6 +26,22 @@ interface AboutService { fun readAllFacilities(language: String): List fun readAllDirections(language: String): List fun readFutureCareers(): FutureCareersPage + + fun searchTopAbout( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AboutSearchResBody + + fun searchPageAbout( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AboutSearchResBody + fun migrateAbout(requestList: List): List fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage fun migrateStudentClubs(requestList: List): List @@ -53,7 +71,7 @@ class AboutServiceImpl( ): AboutDto { val enumPostType = makeStringToEnum(postType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) + var newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) if (mainImage != null) { mainImageService.uploadMainImage(newAbout, mainImage) @@ -62,10 +80,14 @@ class AboutServiceImpl( if (attachments != null) { attachmentService.uploadAllAttachments(newAbout, attachments) } - aboutRepository.save(newAbout) + + syncSearchOfAbout(newAbout) + + newAbout = aboutRepository.save(newAbout) val imageURL = mainImageService.createImageURL(newAbout.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(newAbout.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newAbout.attachments) return AboutDto.of(newAbout, imageURL, attachmentResponses) } @@ -85,9 +107,13 @@ class AboutServiceImpl( override fun readAllClubs(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val clubs = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.STUDENT_CLUBS).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.STUDENT_CLUBS + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachmentResponses) } @@ -98,9 +124,13 @@ class AboutServiceImpl( override fun readAllFacilities(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val facilities = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.FACILITIES).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.FACILITIES + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachmentResponses) } @@ -111,7 +141,10 @@ class AboutServiceImpl( override fun readAllDirections(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val directions = - aboutRepository.findAllByLanguageAndPostTypeOrderByName(languageType, AboutPostType.DIRECTIONS).map { + aboutRepository.findAllByLanguageAndPostTypeOrderByName( + languageType, + AboutPostType.DIRECTIONS + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) val attachments = attachmentService.createAttachmentResponses(it.attachments) AboutDto.of(it, imageURL, attachments) @@ -154,8 +187,60 @@ class AboutServiceImpl( return FutureCareersPage(description, statList, companyList) } + @Transactional + fun syncSearchOfAbout(about: AboutEntity) { + if (about.postType == AboutPostType.FUTURE_CAREERS) { + about.syncSearchContent( + statRepository.findAll().map { it.name }, + companyRepository.findAll().map { it.name } + ) + } else { + about.syncSearchContent() + } + } + + @Transactional(readOnly = true) + override fun searchTopAbout( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AboutSearchResBody { + val (searchEntities, searchCnt) = + aboutRepository.searchAbouts(keyword, language, number, 1) + return AboutSearchResBody( + searchCnt, + searchEntities.map { + AboutSearchElementDto.of(it, keyword, amount) + } + ) + } + + @Transactional(readOnly = true) + override fun searchPageAbout( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AboutSearchResBody { + val (searchEntities, searchCnt) = aboutRepository.searchAbouts( + keyword, + language, + pageSize, + pageNum + ) + return AboutSearchResBody( + searchCnt, + searchEntities.map { + AboutSearchElementDto.of(it, keyword, amount) + } + ) + } + @Transactional override fun migrateAbout(requestList: List): List { + // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { @@ -177,9 +262,10 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) + var newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) + syncSearchOfAbout(newAbout) - aboutRepository.save(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(AboutDto.of(newAbout, null, listOf())) } @@ -188,6 +274,7 @@ class AboutServiceImpl( @Transactional override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage { + // Todo: add about migrate search val description = request.description val language = request.language val statList = mutableListOf() @@ -207,8 +294,8 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) - aboutRepository.save(newAbout) + + var newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) for (stat in request.stat) { val year = stat.year @@ -243,6 +330,9 @@ class AboutServiceImpl( companyList.add(company) } + syncSearchOfAbout(newAbout) + newAbout = aboutRepository.save(newAbout) + return FutureCareersPage(description, statList.toList(), companyList.toList()) } @@ -267,9 +357,11 @@ class AboutServiceImpl( attachments = listOf() ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) - aboutRepository.save(newAbout) + var newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) + + syncSearchOfAbout(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(StudentClubDto.of(newAbout)) } @@ -278,6 +370,7 @@ class AboutServiceImpl( @Transactional override fun migrateFacilities(requestList: List): List = + // Todo: add about migrate search requestList.map { AboutDto( id = null, @@ -296,6 +389,8 @@ class AboutServiceImpl( LanguageType.makeStringToLanguageType(it.language), dto ) + }.also { + syncSearchOfAbout(it) } }.let { aboutRepository.saveAll(it) @@ -305,6 +400,7 @@ class AboutServiceImpl( @Transactional override fun migrateDirections(requestList: List): List { + // Todo: add about migrate search val list = mutableListOf() for (request in requestList) { @@ -326,9 +422,10 @@ class AboutServiceImpl( ) val languageType = LanguageType.makeStringToLanguageType(language) - val newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) + var newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) + syncSearchOfAbout(newAbout) - aboutRepository.save(newAbout) + newAbout = aboutRepository.save(newAbout) list.add(DirectionDto.of(newAbout)) } @@ -349,7 +446,8 @@ class AboutServiceImpl( } val imageURL = mainImageService.createImageURL(about.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(about.attachments) return AboutDto.of(about, imageURL, attachmentResponses) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt index 55df6ce2..06d8a5cc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/AcademicsController.kt @@ -1,11 +1,13 @@ package com.wafflestudio.csereal.core.academics.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.dto.* import com.wafflestudio.csereal.core.academics.service.AcademicsService import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import com.wafflestudio.csereal.core.academics.service.AcademicsSearchService import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -26,22 +28,28 @@ class AcademicsController( request: AcademicsDto, @RequestPart("attachments") attachments: List? ): ResponseEntity { - return ResponseEntity.ok(academicsService.createAcademics(studentType, postType, request, attachments)) + return ResponseEntity.ok( + academicsService.createAcademics(studentType, postType, request, attachments) + ) } @GetMapping("/{studentType}/guide") fun readGuide( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readGuide(studentType)) + return ResponseEntity.ok(academicsService.readGuide(language, studentType)) } @GetMapping("/{studentType}/{postType}") fun readAcademicsYearResponses( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String, @PathVariable postType: String ): ResponseEntity> { - return ResponseEntity.ok(academicsService.readAcademicsYearResponses(studentType, postType)) + return ResponseEntity.ok( + academicsService.readAcademicsYearResponses(language, studentType, postType) + ) } //교과목 정보 @@ -73,9 +81,11 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readCourse(language, name)) } - @GetMapping("/undergraduate/general-studies-requirements") - fun readGeneralStudiesRequirements(): ResponseEntity { - return ResponseEntity.ok(academicsService.readGeneralStudies()) + @GetMapping("/undergraduate/degree-requirements") + fun readDegreeRequirements( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readDegreeRequirements(language)) } @AuthenticatedStaff @@ -90,33 +100,86 @@ class AcademicsController( @GetMapping("/{studentType}/scholarship") fun readAllScholarship( + @RequestParam(required = false, defaultValue = "ko") language: String, @PathVariable studentType: String ): ResponseEntity { - return ResponseEntity.ok(academicsService.readAllScholarship(studentType)) + return ResponseEntity.ok(academicsService.readAllScholarship(language, studentType)) } @GetMapping("/scholarship/{scholarshipId}") - fun getScholarship(@PathVariable scholarshipId: Long): ResponseEntity { + fun getScholarship( + @PathVariable scholarshipId: Long + ): ResponseEntity { return ResponseEntity.ok(academicsService.readScholarship(scholarshipId)) } + @PostMapping("/{studentType}/{postType}/migrate") + fun migrateAcademicsDetail( + @PathVariable studentType: String, + @PathVariable postType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok( + academicsService.migrateAcademicsDetail(studentType, postType, requestList) + ) + } + + @PostMapping("/course/migrate/{studentType}") + fun migrateCourses( + @PathVariable studentType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok(academicsService.migrateCourses(studentType, requestList)) + } + + @PostMapping("/{studentType}/scholarshipDetail/migrate") + fun migrateScholarshipDetail( + @PathVariable studentType: String, + @RequestBody requestList: List + ): ResponseEntity> { + return ResponseEntity.ok( + academicsService.migrateScholarshipDetail(studentType, requestList) + ) + } + + @PatchMapping("/migrateAttachment/{academicsId}") + fun migrateAcademicsDetailAttachments( + @PathVariable academicsId: Long, + @RequestPart("attachments") attachments: List? + ): ResponseEntity { + return ResponseEntity.ok( + academicsService.migrateAcademicsDetailAttachments( + academicsId, + attachments + ) + ) + } + @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") amount: Int ) = academicsSearchService.searchTopAcademics( keyword = keyword, - number = number + language = LanguageType.makeStringToLanguageType(language), + number = number, + amount = amount ) @GetMapping("/search") fun searchAcademics( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") amount: Int ) = academicsSearchService.searchAcademics( keyword = keyword, + language = LanguageType.makeStringToLanguageType(language), pageSize = pageSize, - pageNum = pageNum + pageNum = pageNum, + amount = amount ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt new file mode 100644 index 00000000..6cf2e59d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResBody.kt @@ -0,0 +1,26 @@ +package com.wafflestudio.csereal.core.academics.api.res + +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity + +data class AcademicsSearchResBody( + val results: List, + val total: Long +) { + companion object { + fun of( + total: Long, + academics: List, + keyword: String, + amount: Int + ) = AcademicsSearchResBody( + results = academics.map { + AcademicsSearchResElement.of( + it, + keyword, + amount + ) + }, + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt new file mode 100644 index 00000000..8958fdcf --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/api/res/AcademicsSearchResElement.kt @@ -0,0 +1,92 @@ +package com.wafflestudio.csereal.core.academics.api.res + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity +import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType + +data class AcademicsSearchResElement( + val id: Long, + val language: String, + val name: String, + val academicsType: AcademicsSearchType, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int +) { + companion object { + fun of( + academicsSearch: AcademicsSearchEntity, + keyword: String, + amount: Int + ): AcademicsSearchResElement { + return when { + academicsSearch.academics != null && + academicsSearch.course == null && + academicsSearch.scholarship == null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.academics!!.id, + name = academicsSearch.academics!!.name, + language = academicsSearch.academics!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.ACADEMICS, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + academicsSearch.academics == null && + academicsSearch.course != null && + academicsSearch.scholarship == null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.course!!.id, + name = academicsSearch.course!!.name, + language = academicsSearch.course!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.COURSE, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + academicsSearch.academics == null && + academicsSearch.course == null && + academicsSearch.scholarship != null -> { + val (startIdx, partialDescription) = substringAroundKeyword( + keyword, + academicsSearch.content, + amount + ) + AcademicsSearchResElement( + id = academicsSearch.scholarship!!.id, + name = academicsSearch.scholarship!!.name, + language = academicsSearch.scholarship!!.language.let { + LanguageType.makeLowercase(it) + }, + academicsType = AcademicsSearchType.SCHOLARSHIP, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } + + else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") + } + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt index e5e55b91..a3b87160 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsEntity.kt @@ -18,9 +18,10 @@ class AcademicsEntity( var language: LanguageType, var name: String, + + @Column(columnDefinition = "mediumText") var description: String, var year: Int?, - var time: String?, @OneToMany(mappedBy = "academics", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), @@ -44,8 +45,7 @@ class AcademicsEntity( language = languageType, name = academicsDto.name, description = academicsDto.description, - year = academicsDto.year, - time = academicsDto.time + year = academicsDto.year ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt index 432ea6c6..fb77471f 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsPostType.kt @@ -2,5 +2,5 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsPostType { GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, - CURRICULUM, DEGREE_REQUIREMENTS, COURSE_CHANGES, SCHOLARSHIP; + CURRICULUM, DEGREE_REQUIREMENTS, DEGREE_REQUIREMENTS_YEAR_LIST, COURSE_CHANGES, SCHOLARSHIP; } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt index e5eacb97..2aeeb80e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsRepository.kt @@ -1,17 +1,16 @@ package com.wafflestudio.csereal.core.academics.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface AcademicsRepository : JpaRepository { - fun findByStudentTypeAndPostType( + fun findByLanguageAndStudentTypeAndPostType( + languageType: LanguageType, studentType: AcademicsStudentType, postType: AcademicsPostType ): AcademicsEntity - fun findAllByStudentTypeAndPostTypeOrderByYearDesc( - studentType: AcademicsStudentType, - postType: AcademicsPostType - ): List - fun findAllByStudentTypeAndPostTypeOrderByTimeDesc( + fun findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + languageType: LanguageType, studentType: AcademicsStudentType, postType: AcademicsPostType ): List diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt index ce50ce3b..1cd8ea7b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import jakarta.persistence.* @@ -9,6 +10,9 @@ class AcademicsSearchEntity( @Column(columnDefinition = "TEXT", nullable = false) var content: String, + @Enumerated(value = EnumType.STRING) + val language: LanguageType, + @OneToOne @JoinColumn(name = "academics_id") val academics: AcademicsEntity? = null, @@ -26,6 +30,7 @@ class AcademicsSearchEntity( fun create(academics: AcademicsEntity): AcademicsSearchEntity { return AcademicsSearchEntity( academics = academics, + language = academics.language, content = createContent(academics) ) } @@ -33,6 +38,7 @@ class AcademicsSearchEntity( fun create(course: CourseEntity): AcademicsSearchEntity { return AcademicsSearchEntity( course = course, + language = course.language, content = createContent(course) ) } @@ -40,6 +46,7 @@ class AcademicsSearchEntity( fun create(scholarship: ScholarshipEntity): AcademicsSearchEntity { return AcademicsSearchEntity( scholarship = scholarship, + language = scholarship.language, content = createContent(scholarship) ) } @@ -47,7 +54,6 @@ class AcademicsSearchEntity( fun createContent(academics: AcademicsEntity): String { val sb = StringBuilder() academics.name.let { sb.appendLine(it) } - academics.time?.let { sb.appendLine(it) } academics.year?.let { sb.appendLine(it) } sb.appendLine(academics.studentType.value) sb.appendLine( diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt index cd16fb10..df24e29a 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/AcademicsSearchRepository.kt @@ -2,20 +2,25 @@ package com.wafflestudio.csereal.core.academics.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.academics.database.QAcademicsEntity.academicsEntity import com.wafflestudio.csereal.core.academics.database.QAcademicsSearchEntity.academicsSearchEntity import com.wafflestudio.csereal.core.academics.database.QCourseEntity.courseEntity import com.wafflestudio.csereal.core.academics.database.QScholarshipEntity.scholarshipEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository -import com.wafflestudio.csereal.common.utils.exchangePageNum interface AcademicsSearchRepository : JpaRepository, AcademicsSearchCustomRepository interface AcademicsSearchCustomRepository { - fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> - fun searchTopAcademics(keyword: String, number: Int): List + fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,22 +28,18 @@ class AcademicsSearchCustomRepositoryImpl( private val queryFactory: JPAQueryFactory, private val commonRepository: CommonRepository ) : AcademicsSearchCustomRepository { - override fun searchTopAcademics(keyword: String, number: Int): List { - return searchQuery(keyword) - .limit(number.toLong()) - .fetch() - } - override fun searchAcademics( keyword: String, + language: LanguageType, pageSize: Int, pageNum: Int ): Pair, Long> { - val query = searchQuery(keyword) - val total = getSearchCount(keyword) + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() + val queryResult = query.offset(validOffset) .limit(pageSize.toLong()) .fetch() @@ -46,7 +47,7 @@ class AcademicsSearchCustomRepositoryImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, academicsSearchEntity.content @@ -67,11 +68,12 @@ class AcademicsSearchCustomRepositoryImpl( scholarshipEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + academicsSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, academicsSearchEntity.content @@ -81,7 +83,8 @@ class AcademicsSearchCustomRepositoryImpl( academicsSearchEntity.countDistinct() ).from(academicsSearchEntity) .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + academicsSearchEntity.language.eq(language) ).fetchOne()!! } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt index 792c0507..1200c790 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/CourseEntity.kt @@ -21,6 +21,8 @@ class CourseEntity( var name: String, var credit: Int, var grade: String, + + @Column(columnDefinition = "mediumText") var description: String?, @OneToMany(mappedBy = "course", cascade = [CascadeType.ALL], orphanRemoval = true) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt index 6aee54fd..64ab6098 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/database/ScholarshipEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.academics.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.dto.ScholarshipDto import jakarta.persistence.* @@ -9,6 +10,9 @@ class ScholarshipEntity( @Enumerated(EnumType.STRING) var studentType: AcademicsStudentType, + @Enumerated(EnumType.STRING) + var language: LanguageType, + val name: String, @Column(columnDefinition = "text") @@ -20,8 +24,13 @@ class ScholarshipEntity( ) : BaseTimeEntity() { companion object { - fun of(studentType: AcademicsStudentType, scholarshipDto: ScholarshipDto): ScholarshipEntity { + fun of( + languageType: LanguageType, + studentType: AcademicsStudentType, + scholarshipDto: ScholarshipDto + ): ScholarshipEntity { return ScholarshipEntity( + language = languageType, studentType = studentType, name = scholarshipDto.name, description = scholarshipDto.description diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt index 1530c621..38091e05 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsDto.kt @@ -11,7 +11,6 @@ data class AcademicsDto( val name: String, val description: String, val year: Int? = null, - val time: String? = null, val createdAt: LocalDateTime? = null, val modifiedAt: LocalDateTime? = null, val attachments: List? = null @@ -24,7 +23,6 @@ data class AcademicsDto( name = this.name, description = this.description, year = this.year, - time = this.time, createdAt = this.createdAt, modifiedAt = this.modifiedAt, attachments = attachmentResponses diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt deleted file mode 100644 index 36b6b79f..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchPageResponse.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity - -data class AcademicsSearchPageResponse( - val academics: List, - val total: Long -) { - companion object { - fun of( - academics: List, - total: Long - ) = AcademicsSearchPageResponse( - academics = academics.map(AcademicsSearchResponseElement::of), - total = total - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt deleted file mode 100644 index 7854a72c..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchResponseElement.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType - -data class AcademicsSearchResponseElement( - val id: Long, - val name: String, - val academicsType: AcademicsSearchType -) { - companion object { - fun of(academicsSearch: AcademicsSearchEntity): AcademicsSearchResponseElement { - return when { - academicsSearch.academics != null && - academicsSearch.course == null && - academicsSearch.scholarship == null -> - AcademicsSearchResponseElement( - id = academicsSearch.academics!!.id, - name = academicsSearch.academics!!.name, - academicsType = AcademicsSearchType.ACADEMICS - ) - academicsSearch.academics == null && - academicsSearch.course != null && - academicsSearch.scholarship == null -> - AcademicsSearchResponseElement( - id = academicsSearch.course!!.id, - name = academicsSearch.course!!.name, - academicsType = AcademicsSearchType.COURSE - ) - academicsSearch.academics == null && - academicsSearch.course == null && - academicsSearch.scholarship != null -> - AcademicsSearchResponseElement( - id = academicsSearch.scholarship!!.id, - name = academicsSearch.scholarship!!.name, - academicsType = AcademicsSearchType.SCHOLARSHIP - ) - else -> throw CserealException.Csereal401("AcademicsSearchEntity의 연결이 올바르지 않습니다.") - } - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt deleted file mode 100644 index 232b2fe2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/AcademicsSearchTopResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity - -data class AcademicsSearchTopResponse( - val topAcademics: List -) { - companion object { - fun of( - topAcademics: List - ) = AcademicsSearchTopResponse( - topAcademics = topAcademics.map(AcademicsSearchResponseElement::of) - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt similarity index 72% rename from src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt index 6d362ac8..e5c531a5 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/SubjectChangesDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt @@ -2,14 +2,14 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.AcademicsEntity -class SubjectChangesDto( - val time: String, +class DegreeRequirementsDto( + val year: Int, val description: String ) { companion object { fun of(entity: AcademicsEntity) = entity.run { - SubjectChangesDto( - time = this.time!!, + DegreeRequirementsDto( + year = this.year!!, description = this.description ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt new file mode 100644 index 00000000..8d4d2ad4 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt @@ -0,0 +1,17 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class DegreeRequirementsPageResponse( + val description: String, + val yearList: List +) { + companion object { + fun of(entity: AcademicsEntity, yearList: List) = entity.run { + DegreeRequirementsPageResponse( + description = this.description, + yearList = yearList.map { DegreeRequirementsDto.of(it) } + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt deleted file mode 100644 index 950434f2..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesPageResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.wafflestudio.csereal.core.academics.dto - -import com.wafflestudio.csereal.core.academics.database.AcademicsEntity - -class GeneralStudiesPageResponse( - val subjectChanges: List, - val description: String -) { - companion object { - fun of(entity: AcademicsEntity, subjectChangesEntity: List) = entity.run { - GeneralStudiesPageResponse( - subjectChanges = subjectChangesEntity.map { SubjectChangesDto.of(it) }, - description = this.description - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt index 533f9395..0312e296 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/ScholarshipDto.kt @@ -1,9 +1,11 @@ package com.wafflestudio.csereal.core.academics.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity data class ScholarshipDto( val id: Long, + val language: String, val name: String, val description: String ) { @@ -11,6 +13,7 @@ data class ScholarshipDto( fun of(scholarshipEntity: ScholarshipEntity): ScholarshipDto { return ScholarshipDto( id = scholarshipEntity.id, + language = LanguageType.makeLowercase(scholarshipEntity.language), name = scholarshipEntity.name, description = scholarshipEntity.description ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt index eb2738b2..63a0b3dc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsSearchService.kt @@ -1,14 +1,25 @@ package com.wafflestudio.csereal.core.academics.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.academics.database.AcademicsSearchRepository -import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchPageResponse -import com.wafflestudio.csereal.core.academics.dto.AcademicsSearchTopResponse +import com.wafflestudio.csereal.core.academics.api.res.AcademicsSearchResBody import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface AcademicsSearchService { - fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int): AcademicsSearchPageResponse - fun searchTopAcademics(keyword: String, number: Int): AcademicsSearchTopResponse + fun searchTopAcademics( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AcademicsSearchResBody + fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AcademicsSearchResBody } @Service @@ -16,24 +27,45 @@ class AcademicsSearchServiceImpl( private val academicsSearchRepository: AcademicsSearchRepository ) : AcademicsSearchService { @Transactional(readOnly = true) - override fun searchTopAcademics(keyword: String, number: Int) = - AcademicsSearchTopResponse.of( - academicsSearchRepository.searchTopAcademics( + override fun searchTopAcademics( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ) = + academicsSearchRepository.searchAcademics( + keyword = keyword, + language = language, + pageSize = number, + pageNum = 1 + ).let { (acds, total) -> + AcademicsSearchResBody.of( + total = total, + academics = acds, keyword = keyword, - number = number + amount = amount ) - ) + } @Transactional(readOnly = true) - override fun searchAcademics(keyword: String, pageSize: Int, pageNum: Int) = + override fun searchAcademics( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ) = academicsSearchRepository.searchAcademics( keyword = keyword, + language = language, pageSize = pageSize, pageNum = pageNum ).let { - AcademicsSearchPageResponse.of( + AcademicsSearchResBody.of( academics = it.first, - total = it.second + total = it.second, + keyword = keyword, + amount = amount ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt index 176d5b8b..d2be6702 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/service/AcademicsService.kt @@ -20,15 +20,40 @@ interface AcademicsService { attachments: List? ): AcademicsDto - fun readGuide(studentType: String): GuidePageResponse - fun readAcademicsYearResponses(studentType: String, postType: String): List - fun readGeneralStudies(): GeneralStudiesPageResponse - fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto + fun readGuide(language: String, studentType: String): GuidePageResponse + fun readAcademicsYearResponses( + language: String, + studentType: String, + postType: String + ): List + fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse + fun createCourse( + studentType: String, + request: CourseDto, + attachments: List? + ): CourseDto fun readAllCourses(language: String, studentType: String): List fun readCourse(language: String, name: String): CourseDto - fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto - fun readAllScholarship(studentType: String): ScholarshipPageResponse + fun createScholarshipDetail( + studentType: String, + request: ScholarshipDto + ): ScholarshipDto + fun readAllScholarship(language: String, studentType: String): ScholarshipPageResponse fun readScholarship(scholarshipId: Long): ScholarshipDto + fun migrateAcademicsDetail( + studentType: String, + postType: String, + requestList: List + ): List + fun migrateCourses(studentType: String, requestList: List): List + fun migrateScholarshipDetail( + studentType: String, + requestList: List + ): List + fun migrateAcademicsDetailAttachments( + academicsId: Long, + attachments: List? + ): AcademicsDto } // TODO: add Update, Delete method @@ -52,7 +77,8 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newAcademics = AcademicsEntity.of(enumStudentType, enumPostType, enumLanguageType, request) + val newAcademics = + AcademicsEntity.of(enumStudentType, enumPostType, enumLanguageType, request) if (attachments != null) { attachmentService.uploadAllAttachments(newAcademics, attachments) @@ -65,29 +91,44 @@ class AcademicsServiceImpl( academicsRepository.save(newAcademics) - val attachmentResponses = attachmentService.createAttachmentResponses(newAcademics.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newAcademics.attachments) return AcademicsDto.of(newAcademics, attachmentResponses) } @Transactional(readOnly = true) - override fun readGuide(studentType: String): GuidePageResponse { + override fun readGuide(language: String, studentType: String): GuidePageResponse { + val languageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - val academicsEntity = academicsRepository.findByStudentTypeAndPostType(enumStudentType, AcademicsPostType.GUIDE) - val attachmentResponses = attachmentService.createAttachmentResponses(academicsEntity.attachments) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + languageType, + enumStudentType, + AcademicsPostType.GUIDE + ) + val attachmentResponses = + attachmentService.createAttachmentResponses(academicsEntity.attachments) return GuidePageResponse.of(academicsEntity, attachmentResponses) } @Transactional(readOnly = true) - override fun readAcademicsYearResponses(studentType: String, postType: String): List { + override fun readAcademicsYearResponses( + language: String, + studentType: String, + postType: String + ): List { + val languageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumPostType = makeStringToAcademicsPostType(postType) - val academicsEntityList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByYearDesc( - enumStudentType, - enumPostType - ) + val academicsEntityList = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + languageType, + enumStudentType, + enumPostType + ) val academicsYearResponses = academicsEntityList.map { val attachments = attachmentService.createAttachmentResponses(it.attachments) @@ -98,21 +139,31 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readGeneralStudies(): GeneralStudiesPageResponse { - val academicsEntity = academicsRepository.findByStudentTypeAndPostType( - AcademicsStudentType.UNDERGRADUATE, - AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS - ) - val subjectChangesList = academicsRepository.findAllByStudentTypeAndPostTypeOrderByTimeDesc( - AcademicsStudentType.UNDERGRADUATE, - AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES - ) - - return GeneralStudiesPageResponse.of(academicsEntity, subjectChangesList) + override fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.DEGREE_REQUIREMENTS + ) + + val yearList = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.DEGREE_REQUIREMENTS_YEAR_LIST + ) + + return DegreeRequirementsPageResponse.of(academicsEntity, yearList) } @Transactional - override fun createCourse(studentType: String, request: CourseDto, attachments: List?): CourseDto { + override fun createCourse( + studentType: String, + request: CourseDto, + attachments: List? + ): CourseDto { val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) @@ -128,7 +179,8 @@ class AcademicsServiceImpl( } courseRepository.save(newCourse) - val attachmentResponses = attachmentService.createAttachmentResponses(newCourse.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newCourse.attachments) return CourseDto.of(newCourse, attachmentResponses) } @@ -138,8 +190,13 @@ class AcademicsServiceImpl( val enumStudentType = makeStringToAcademicsStudentType(studentType) val enumLanguageType = LanguageType.makeStringToLanguageType(language) val courseDtoList = - courseRepository.findAllByLanguageAndStudentTypeOrderByNameAsc(enumLanguageType, enumStudentType).map { - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + courseRepository.findAllByLanguageAndStudentTypeOrderByNameAsc( + enumLanguageType, + enumStudentType + ).map { + val attachmentResponses = + attachmentService.createAttachmentResponses(it.attachments) + CourseDto.of(it, attachmentResponses) } return courseDtoList @@ -156,8 +213,9 @@ class AcademicsServiceImpl( @Transactional override fun createScholarshipDetail(studentType: String, request: ScholarshipDto): ScholarshipDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - var newScholarship = ScholarshipEntity.of(enumStudentType, request) + var newScholarship = ScholarshipEntity.of(enumLanguageType, enumStudentType, request) // create search data newScholarship.apply { @@ -170,13 +228,16 @@ class AcademicsServiceImpl( } @Transactional(readOnly = true) - override fun readAllScholarship(studentType: String): ScholarshipPageResponse { + override fun readAllScholarship(language: String, studentType: String): ScholarshipPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) val enumStudentType = makeStringToAcademicsStudentType(studentType) - val academicsEntity = academicsRepository.findByStudentTypeAndPostType( - enumStudentType, - AcademicsPostType.SCHOLARSHIP - ) + val academicsEntity = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + enumStudentType, + AcademicsPostType.SCHOLARSHIP + ) val scholarshipEntityList = scholarshipRepository.findAllByStudentType(enumStudentType) return ScholarshipPageResponse.of(academicsEntity, scholarshipEntityList) @@ -185,10 +246,108 @@ class AcademicsServiceImpl( @Transactional(readOnly = true) override fun readScholarship(scholarshipId: Long): ScholarshipDto { val scholarship = scholarshipRepository.findByIdOrNull(scholarshipId) - ?: throw CserealException.Csereal404("id: $scholarshipId 에 해당하는 장학제도를 찾을 수 없습니다") + ?: throw CserealException.Csereal404("해당하는 장학제도를 찾을 수 없습니다") return ScholarshipDto.of(scholarship) } + @Transactional + override fun migrateAcademicsDetail( + studentType: String, + postType: String, + requestList: List + ): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val enumPostType = makeStringToAcademicsPostType(postType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newAcademics = AcademicsEntity.of( + enumStudentType, + enumPostType, + enumLanguageType, + request + ) + + newAcademics.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + academicsRepository.save(newAcademics) + + list.add(AcademicsDto.of(newAcademics, listOf())) + } + + return list + } + + @Transactional + override fun migrateCourses( + studentType: String, + requestList: List + ): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newCourse = CourseEntity.of(enumStudentType, enumLanguageType, request) + + newCourse.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + courseRepository.save(newCourse) + + list.add(CourseDto.of(newCourse, listOf())) + } + + return list + } + + @Transactional + override fun migrateScholarshipDetail( + studentType: String, + requestList: List + ): List { + val enumStudentType = makeStringToAcademicsStudentType(studentType) + val list = mutableListOf() + for (request in requestList) { + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newScholarship = ScholarshipEntity.of( + enumLanguageType, + enumStudentType, + request + ) + + newScholarship.apply { + academicsSearch = AcademicsSearchEntity.create(this) + } + + scholarshipRepository.save(newScholarship) + + list.add(ScholarshipDto.of(newScholarship)) + } + + return list + } + + @Transactional + override fun migrateAcademicsDetailAttachments( + academicsId: Long, + attachments: List? + ): AcademicsDto { + val academics = academicsRepository.findByIdOrNull(academicsId) + ?: throw CserealException.Csereal404("해당 내용을 찾을 수 없습니다.") + + if (attachments != null) { + attachmentService.uploadAllAttachments(academics, attachments) + } + + val attachmentResponses = attachmentService.createAttachmentResponses( + academics.attachments + ) + + return AcademicsDto.of(academics, attachmentResponses) + } + private fun makeStringToAcademicsStudentType(postType: String): AcademicsStudentType { try { val upperPostType = postType.replace("-", "_").uppercase() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt index b25edb3c..f5654461 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/AdmissionsController.kt @@ -9,6 +9,7 @@ import com.wafflestudio.csereal.core.admissions.service.AdmissionsService import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.web.bind.annotation.* @RequestMapping("/api/v1/admissions") @@ -41,15 +42,32 @@ class AdmissionsController( return admissionsService.readAdmission(mainType, postType, languageType) } - @GetMapping("/search") + @GetMapping("/search/top") fun searchTopAdmissions( @RequestParam(required = true) keyword: String, @RequestParam(required = true, defaultValue = "ko") language: String, - @RequestParam(required = true) number: Int + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int ) = admissionsService.searchTopAdmission( keyword, LanguageType.makeStringToLanguageType(language), - number + number, + amount + ) + + @GetMapping("/search") + fun searchPageAdmissions( + @RequestParam(required = true) keyword: String, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = admissionsService.searchPageAdmission( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount ) @PostMapping("/migrate") diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt new file mode 100644 index 00000000..49b868fa --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResBody.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.admissions.api.res + +data class AdmissionSearchResBody( + val total: Long, + val admissions: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt index a1e7d015..522aa41c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/api/res/AdmissionSearchResElem.kt @@ -1,28 +1,41 @@ package com.wafflestudio.csereal.core.admissions.api.res import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity -data class AdmissionSearchResBody( - val admissions: List -) - -data class AdmissionSearchResElem( +data class AdmissionSearchResElem private constructor( val id: Long, val name: String, val mainType: String, val postType: String, - val language: String + val language: String, + val partialDescription: String, + val boldStartIndex: Int, + val boldEndIndex: Int ) { companion object { fun of( - admissions: AdmissionsEntity - ) = AdmissionSearchResElem( - id = admissions.id, - name = admissions.name, - mainType = admissions.mainType.toJsonValue(), - postType = admissions.postType.toJsonValue(), - language = LanguageType.makeLowercase(admissions.language) - ) + admissions: AdmissionsEntity, + keyword: String, + amount: Int + ) = admissions.let { + val (boldStartIdx, partialDescription) = substringAroundKeyword( + keyword = keyword, + content = it.description, + amount = amount + ) + + AdmissionSearchResElem( + id = it.id, + name = it.name, + mainType = it.mainType.toJsonValue(), + postType = it.postType.toJsonValue(), + language = LanguageType.makeLowercase(it.language), + partialDescription = partialDescription.replace('\n', ' '), + boldStartIndex = boldStartIdx ?: 0, + boldEndIndex = boldStartIdx?.plus(keyword.length) ?: 0 + ) + } } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt index 34979cb0..71b4d450 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/database/AdmissionsRepository.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.admissions.database import com.querydsl.jpa.impl.JPAQueryFactory import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.admissions.database.QAdmissionsEntity.admissionsEntity import com.wafflestudio.csereal.core.admissions.type.AdmissionsMainType import com.wafflestudio.csereal.core.admissions.type.AdmissionsPostType @@ -18,7 +19,12 @@ interface AdmissionsRepository : JpaRepository, Admissio } interface AdmissionsCustomRepository { - fun searchTopAdmissions(keyword: String, language: LanguageType, number: Int): List + fun searchAdmissions( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -26,15 +32,30 @@ class AdmissionsCustomRepositoryImpl( private val commonRepository: CommonRepository, private val queryFactory: JPAQueryFactory ) : AdmissionsCustomRepository { - override fun searchTopAdmissions( + override fun searchAdmissions( keyword: String, language: LanguageType, - number: Int - ): List = - searchQueryOfLanguage(keyword, language) - .limit(number.toLong()) + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val total = searchCount(keyword, language) + val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validOffset = ( + if (validPageNum >= 1) validPageNum - 1 else 0 + ) * pageSize.toLong() + + val result = searchQueryOfLanguage(keyword, language) + .offset(validOffset) + .limit(pageSize.toLong()) .fetch() + return result to total + } + fun searchCount(keyword: String, language: LanguageType) = + searchQueryOfLanguage(keyword, language) + .select(admissionsEntity.countDistinct()) + .fetchOne()!! + fun searchQueryOfLanguage(keyword: String, language: LanguageType) = queryFactory.selectFrom( admissionsEntity diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt index a31e65d3..2959dab1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/admissions/service/AdmissionsService.kt @@ -4,6 +4,7 @@ import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.admissions.api.req.AdmissionMigrateElem import com.wafflestudio.csereal.core.admissions.api.req.AdmissionReqBody +import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResBody import com.wafflestudio.csereal.core.admissions.api.res.AdmissionSearchResElem import com.wafflestudio.csereal.core.admissions.database.AdmissionsEntity import com.wafflestudio.csereal.core.admissions.database.AdmissionsRepository @@ -28,8 +29,15 @@ interface AdmissionsService { fun migrateAdmissions(requestList: List): List - @Transactional(readOnly = true) - fun searchTopAdmission(keyword: String, language: LanguageType, number: Int): List + fun searchPageAdmission( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AdmissionSearchResBody + + fun searchTopAdmission(keyword: String, language: LanguageType, number: Int, amount: Int): AdmissionSearchResBody } @Service @@ -60,13 +68,41 @@ class AdmissionsServiceImpl( ?: throw CserealException.Csereal404("해당하는 페이지를 찾을 수 없습니다.") @Transactional(readOnly = true) - override fun searchTopAdmission(keyword: String, language: LanguageType, number: Int) = - admissionsRepository.searchTopAdmissions(keyword, language, number).map { - AdmissionSearchResElem.of(it) - } + override fun searchTopAdmission( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): AdmissionSearchResBody { + val (admissions, total) = admissionsRepository.searchAdmissions(keyword, language, number, 1) + return AdmissionSearchResBody( + total = total, + admissions = admissions.map { + AdmissionSearchResElem.of(it, keyword, amount) + } + ) + } + + @Transactional(readOnly = true) + override fun searchPageAdmission( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): AdmissionSearchResBody { + val (admissions, total) = admissionsRepository.searchAdmissions(keyword, language, pageSize, pageNum) + return AdmissionSearchResBody( + total = total, + admissions = admissions.map { + AdmissionSearchResElem.of(it, keyword, amount) + } + ) + } @Transactional override fun migrateAdmissions(requestList: List) = requestList.map { + // Todo: add admission migrate search val mainType = AdmissionsMainType.fromJsonValue(it.mainType) val postType = AdmissionsPostType.fromJsonValue(it.postType) val language = LanguageType.makeStringToLanguageType(it.language) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt index a90e6313..5a5f03dc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/database/ConferenceEntity.kt @@ -1,12 +1,16 @@ package com.wafflestudio.csereal.core.conference.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.dto.ConferenceDto import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import jakarta.persistence.* @Entity(name = "conference") class ConferenceEntity( + @Enumerated(EnumType.STRING) + var language: LanguageType, + var isDeleted: Boolean = false, var code: String, var abbreviation: String, @@ -21,9 +25,11 @@ class ConferenceEntity( ) : BaseTimeEntity() { companion object { fun of( + languageType: LanguageType, conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ) = ConferenceEntity( + language = languageType, code = conferenceDto.code, abbreviation = conferenceDto.abbreviation, name = conferenceDto.name, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt index ca427e78..d6e95c59 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/dto/ConferenceDto.kt @@ -1,11 +1,13 @@ package com.wafflestudio.csereal.core.conference.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity data class ConferenceDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, + val language: String, val code: String, val abbreviation: String, val name: String @@ -14,6 +16,7 @@ data class ConferenceDto( fun of(conferenceEntity: ConferenceEntity): ConferenceDto { return ConferenceDto( id = conferenceEntity.id, + language = LanguageType.makeLowercase(conferenceEntity.language), code = conferenceEntity.code, abbreviation = conferenceEntity.abbreviation, name = conferenceEntity.name diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt index 6cbe1e4d..9fe38d23 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.conference.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository @@ -40,7 +41,9 @@ class ConferenceServiceImpl( } @Transactional - override fun modifyConferences(conferenceModifyRequest: ConferenceModifyRequest): ConferencePage { + override fun modifyConferences( + conferenceModifyRequest: ConferenceModifyRequest + ): ConferencePage { val user = RequestContextHolder.getRequestAttributes()?.getAttribute( "loggedInUser", RequestAttributes.SCOPE_REQUEST @@ -76,7 +79,8 @@ class ConferenceServiceImpl( val conferencePage = ConferencePageEntity.of(user) conferencePageRepository.save(conferencePage) for (request in requestList) { - val conference = ConferenceEntity.of(request, conferencePage) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val conference = ConferenceEntity.of(enumLanguageType, request, conferencePage) conferenceRepository.save(conference) @@ -94,7 +98,9 @@ class ConferenceServiceImpl( conferenceDto: ConferenceDto, conferencePage: ConferencePageEntity ): ConferenceEntity { + val enumLanguageType = LanguageType.makeStringToLanguageType(conferenceDto.language) val newConference = ConferenceEntity.of( + enumLanguageType, conferenceDto, conferencePage ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt index bcd393c3..8053d71b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/MemberSearchController.kt @@ -1,6 +1,9 @@ package com.wafflestudio.csereal.core.member.api +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.service.MemberSearchService +import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam @@ -14,13 +17,19 @@ class MemberSearchController( @GetMapping("/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int - ) = memberSearchService.searchTopMember(keyword, number) + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String + ) = LanguageType.makeStringToLanguageType(language).let { + memberSearchService.searchTopMember(keyword, it, number) + } @GetMapping fun searchPage( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int - ) = memberSearchService.searchMember(keyword, pageSize, pageNum) + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = true) @Valid @Positive pageSize: Int, + @RequestParam(required = true) @Valid @Positive pageNum: Int + ) = LanguageType.makeStringToLanguageType(language).let { + memberSearchService.searchMember(keyword, it, pageSize, pageNum) + } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt index d4e0f771..7ac5a09d 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/ProfessorController.kt @@ -30,13 +30,17 @@ class ProfessorController( } @GetMapping("/active") - fun getActiveProfessors(): ResponseEntity { - return ResponseEntity.ok(professorService.getActiveProfessors()) + fun getActiveProfessors( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(professorService.getActiveProfessors(language)) } @GetMapping("/inactive") - fun getInactiveProfessors(): ResponseEntity> { - return ResponseEntity.ok(professorService.getInactiveProfessors()) + fun getInactiveProfessors( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(professorService.getInactiveProfessors(language)) } @AuthenticatedStaff @@ -46,7 +50,9 @@ class ProfessorController( @RequestPart("request") updateProfessorRequest: ProfessorDto, @RequestPart("mainImage") mainImage: MultipartFile? ): ResponseEntity { - return ResponseEntity.ok(professorService.updateProfessor(professorId, updateProfessorRequest, mainImage)) + return ResponseEntity.ok( + professorService.updateProfessor(professorId, updateProfessorRequest, mainImage) + ) } @AuthenticatedStaff @@ -63,7 +69,7 @@ class ProfessorController( return ResponseEntity.ok(professorService.migrateProfessors(requestList)) } - @PatchMapping("/migragteImage/{professorId}") + @PatchMapping("/migrateImage/{professorId}") fun migrateProfessorImage( @PathVariable professorId: Long, @RequestPart("mainImage") mainImage: MultipartFile diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt similarity index 63% rename from src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt index 41df25f1..b805c85b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResBody.kt @@ -1,10 +1,10 @@ -package com.wafflestudio.csereal.core.member.dto +package com.wafflestudio.csereal.core.member.api.res import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity -data class MemberSearchPageResponse( - val members: List, +data class MemberSearchResBody( + val results: List, val total: Long ) { companion object { @@ -12,8 +12,8 @@ data class MemberSearchPageResponse( members: List, total: Long, imageURLMaker: (MainImageEntity?) -> String? - ) = MemberSearchPageResponse( - members = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, + ) = MemberSearchResBody( + results = members.map { MemberSearchResponseElement.of(it, imageURLMaker) }, total = total ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt similarity index 84% rename from src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt index 5a474194..a7365722 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchResponseElement.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/api/res/MemberSearchResponseElement.kt @@ -1,12 +1,14 @@ -package com.wafflestudio.csereal.core.member.dto +package com.wafflestudio.csereal.core.member.api.res import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchEntity import com.wafflestudio.csereal.core.member.database.MemberSearchType import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity data class MemberSearchResponseElement( val id: Long, + val language: String, val name: String, val academicRankOrRole: String, val imageURL: String?, @@ -21,6 +23,7 @@ data class MemberSearchResponseElement( memberSearch.professor != null && memberSearch.staff == null -> MemberSearchResponseElement( id = memberSearch.professor!!.id, + language = memberSearch.language.let { LanguageType.makeLowercase(it) }, name = memberSearch.professor!!.name, academicRankOrRole = memberSearch.professor!!.academicRank, imageURL = imageURLMaker(memberSearch.professor!!.mainImage), @@ -29,6 +32,7 @@ data class MemberSearchResponseElement( memberSearch.professor == null && memberSearch.staff != null -> MemberSearchResponseElement( id = memberSearch.staff!!.id, + language = memberSearch.language.let { LanguageType.makeLowercase(it) }, name = memberSearch.staff!!.name, academicRankOrRole = memberSearch.staff!!.role, imageURL = imageURLMaker(memberSearch.staff!!.mainImage), diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt index 3944bf6e..6262fef2 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import jakarta.persistence.* @Entity(name = "member_search") @@ -8,6 +9,8 @@ class MemberSearchEntity( @Column(columnDefinition = "TEXT") var content: String, + val language: LanguageType, + @OneToOne @JoinColumn(name = "professor_id") val professor: ProfessorEntity? = null, @@ -20,6 +23,7 @@ class MemberSearchEntity( fun create(professor: ProfessorEntity): MemberSearchEntity { return MemberSearchEntity( content = createContent(professor), + language = professor.language, professor = professor ) } @@ -27,6 +31,7 @@ class MemberSearchEntity( fun create(staff: StaffEntity): MemberSearchEntity { return MemberSearchEntity( content = createContent(staff), + language = staff.language, staff = staff ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt index 662a2d41..2ab47803 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/MemberSearchRepository.kt @@ -2,7 +2,9 @@ package com.wafflestudio.csereal.core.member.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.member.database.QMemberSearchEntity.memberSearchEntity import com.wafflestudio.csereal.core.member.database.QProfessorEntity.professorEntity import com.wafflestudio.csereal.core.member.database.QStaffEntity.staffEntity @@ -13,8 +15,12 @@ interface MemberSearchRepository : JpaRepository, MemberSearchRepositoryCustom interface MemberSearchRepositoryCustom { - fun searchTopMember(keyword: String, number: Int): List - fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,15 +29,14 @@ class MemberSearchRepositoryCustomImpl( private val commonRepository: CommonRepository ) : MemberSearchRepositoryCustom { - override fun searchTopMember(keyword: String, number: Int): List { - return searchQuery(keyword) - .limit(number.toLong()) - .fetch() - } - - override fun searchMember(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { - val query = searchQuery(keyword) - val total = getSearchCount(keyword) + override fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val queryResult = query @@ -42,7 +47,7 @@ class MemberSearchRepositoryCustomImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, memberSearchEntity.content @@ -61,11 +66,12 @@ class MemberSearchRepositoryCustomImpl( staffEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + memberSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, memberSearchEntity.content @@ -77,15 +83,8 @@ class MemberSearchRepositoryCustomImpl( ).from( memberSearchEntity ).where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + memberSearchEntity.language.eq(language) ).fetchOne()!! } - - fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() - } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt index b1c80c43..00b46019 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorEntity.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.member.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.research.database.LabEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -10,6 +11,8 @@ import java.time.LocalDate @Entity(name = "professor") class ProfessorEntity( + @Enumerated(EnumType.STRING) + var language: LanguageType, var name: String, @@ -49,8 +52,9 @@ class ProfessorEntity( override fun bringMainImage(): MainImageEntity? = mainImage companion object { - fun of(professorDto: ProfessorDto): ProfessorEntity { + fun of(languageType: LanguageType, professorDto: ProfessorDto): ProfessorEntity { return ProfessorEntity( + language = languageType, name = professorDto.name, status = professorDto.status, academicRank = professorDto.academicRank, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt index f190d236..d11b54c3 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/database/ProfessorRepository.kt @@ -1,8 +1,15 @@ package com.wafflestudio.csereal.core.member.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ProfessorRepository : JpaRepository { - fun findByStatus(status: ProfessorStatus): List - fun findByStatusNot(status: ProfessorStatus): List + fun findByLanguageAndStatus( + languageType: LanguageType, + status: ProfessorStatus + ): List + fun findByLanguageAndStatusNot( + languageType: LanguageType, + status: ProfessorStatus + ): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt deleted file mode 100644 index 6e577a56..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/MemberSearchTopResponse.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.wafflestudio.csereal.core.member.dto - -import com.wafflestudio.csereal.core.member.database.MemberSearchEntity -import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity - -data class MemberSearchTopResponse( - val topMembers: List -) { - companion object { - fun of( - topMembers: List, - imageURLMaker: (MainImageEntity?) -> String? - ) = MemberSearchTopResponse( - topMembers = topMembers.map { - MemberSearchResponseElement.of(it, imageURLMaker) - } - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt index 90bf7f47..072db7e1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/ProfessorDto.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.dto import com.fasterxml.jackson.annotation.JsonInclude +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorStatus import java.time.LocalDate @@ -8,6 +9,7 @@ import java.time.LocalDate data class ProfessorDto( @JsonInclude(JsonInclude.Include.NON_NULL) var id: Long? = null, + val language: String, val name: String, val status: ProfessorStatus, val academicRank: String, @@ -31,6 +33,7 @@ data class ProfessorDto( fun of(professorEntity: ProfessorEntity, imageURL: String?): ProfessorDto { return ProfessorDto( id = professorEntity.id, + language = LanguageType.makeLowercase(professorEntity.language), name = professorEntity.name, status = professorEntity.status, academicRank = professorEntity.academicRank, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt index 35651b02..ca338b53 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/MemberSearchService.kt @@ -1,15 +1,16 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.member.api.res.MemberSearchResBody import com.wafflestudio.csereal.core.member.database.MemberSearchRepository -import com.wafflestudio.csereal.core.member.dto.MemberSearchPageResponse -import com.wafflestudio.csereal.core.member.dto.MemberSearchTopResponse import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional interface MemberSearchService { - fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse - fun searchMember(keyword: String, pageSize: Int, pageNum: Int): MemberSearchPageResponse + fun searchTopMember(keyword: String, language: LanguageType, number: Int): MemberSearchResBody + + fun searchMember(keyword: String, language: LanguageType, pageSize: Int, pageNum: Int): MemberSearchResBody } @Service @@ -18,14 +19,19 @@ class MemberSearchServiceImpl( private val mainImageService: MainImageService ) : MemberSearchService { @Transactional(readOnly = true) - override fun searchTopMember(keyword: String, number: Int): MemberSearchTopResponse { - val entityResults = memberSearchRepository.searchTopMember(keyword, number) - return MemberSearchTopResponse.of(entityResults, mainImageService::createImageURL) + override fun searchTopMember(keyword: String, language: LanguageType, number: Int): MemberSearchResBody { + val (entityResults, total) = memberSearchRepository.searchMember(keyword, language, number, 1) + return MemberSearchResBody.of(entityResults, total, mainImageService::createImageURL) } @Transactional(readOnly = true) - override fun searchMember(keyword: String, pageSize: Int, pageNum: Int): MemberSearchPageResponse { - val (entityResults, total) = memberSearchRepository.searchMember(keyword, pageSize, pageNum) - return MemberSearchPageResponse.of(entityResults, total, mainImageService::createImageURL) + override fun searchMember( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): MemberSearchResBody { + val (entityResults, total) = memberSearchRepository.searchMember(keyword, language, pageSize, pageNum) + return MemberSearchResBody.of(entityResults, total, mainImageService::createImageURL) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt index ebc22b35..d064b7f8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorService.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.member.service import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.* import com.wafflestudio.csereal.core.member.dto.ProfessorDto import com.wafflestudio.csereal.core.member.dto.ProfessorPageDto @@ -19,8 +20,8 @@ import org.springframework.web.multipart.MultipartFile interface ProfessorService { fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto fun getProfessor(professorId: Long): ProfessorDto - fun getActiveProfessors(): ProfessorPageDto - fun getInactiveProfessors(): List + fun getActiveProfessors(language: String): ProfessorPageDto + fun getInactiveProfessors(language: String): List fun updateProfessor( professorId: Long, updateProfessorRequest: ProfessorDto, @@ -39,8 +40,12 @@ class ProfessorServiceImpl( private val mainImageService: MainImageService, private val applicationEventPublisher: ApplicationEventPublisher ) : ProfessorService { - override fun createProfessor(createProfessorRequest: ProfessorDto, mainImage: MultipartFile?): ProfessorDto { - val professor = ProfessorEntity.of(createProfessorRequest) + override fun createProfessor( + createProfessorRequest: ProfessorDto, + mainImage: MultipartFile? + ): ProfessorDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(createProfessorRequest.language) + val professor = ProfessorEntity.of(enumLanguageType, createProfessorRequest) if (createProfessorRequest.labId != null) { val lab = labRepository.findByIdOrNull(createProfessorRequest.labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${createProfessorRequest.labId}") @@ -87,24 +92,35 @@ class ProfessorServiceImpl( } @Transactional(readOnly = true) - override fun getActiveProfessors(): ProfessorPageDto { - val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 350여 명의 대학원생에게 세계 최고 " + - "수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 " + - "시작으로 교내에서 가장 국제화가 활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + - "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매 학기 전공 필수 과목을 비롯한 " + - "30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 " + - "CSE int’l Luncheon을 개최하여 학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." - val professors = professorRepository.findByStatusNot(ProfessorStatus.INACTIVE).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - SimpleProfessorDto.of(it, imageURL) - } - .sortedBy { it.name } + override fun getActiveProfessors(language: String): ProfessorPageDto { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val description = "컴퓨터공학부는 35명의 훌륭한 교수진과 최신 시설을 갖추고 400여 명의 학부생과 " + + "350여 명의 대학원생에게 세계 최고 수준의 교육 연구 환경을 제공하고 있다. 2005년에는 서울대학교 " + + "최초로 외국인 정교수인 Robert Ian McKay 교수를 임용한 것을 시작으로 교내에서 가장 국제화가 " + + "활발하게 이루어지고 있는 학부로 평가받고 있다. 현재 훌륭한 외국인 교수님 두 분이 학부 학생들의 " + + "교육 및 연구 지도에 총력을 기울이고 있다.\n\n다수의 외국인 학부생, 대학원생이 재학 중에 있으며 매" + + " 학기 전공 필수 과목을 비롯한 30% 이상의 과목이 영어로 개설되고 있어 외국인 학생의 학업을 돕는 " + + "동시에 한국인 학생이 세계로 진출하는 초석이 되고 있다. 또한 CSE int’l Luncheon을 개최하여 " + + "학부 내 외국인 구성원의 화합과 생활의 불편함을 최소화하는 등 학부 차원에서 최선을 다하고 있다." + val professors = + professorRepository.findByLanguageAndStatusNot( + enumLanguageType, + ProfessorStatus.INACTIVE + ).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + SimpleProfessorDto.of(it, imageURL) + } + .sortedBy { it.name } return ProfessorPageDto(description, professors) } @Transactional(readOnly = true) - override fun getInactiveProfessors(): List { - return professorRepository.findByStatus(ProfessorStatus.INACTIVE).map { + override fun getInactiveProfessors(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + return professorRepository.findByLanguageAndStatus( + enumLanguageType, + ProfessorStatus.INACTIVE + ).map { val imageURL = mainImageService.createImageURL(it.mainImage) SimpleProfessorDto.of(it, imageURL) } @@ -123,7 +139,9 @@ class ProfessorServiceImpl( if (updateProfessorRequest.labId != null && updateProfessorRequest.labId != professor.lab?.id) { val lab = labRepository.findByIdOrNull(updateProfessorRequest.labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}") + ?: throw CserealException.Csereal404( + "해당 연구실을 찾을 수 없습니다. LabId: ${updateProfessorRequest.labId}" + ) professor.addLab(lab) } @@ -199,10 +217,13 @@ class ProfessorServiceImpl( val list = mutableListOf() for (request in requestList) { - val professor = ProfessorEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val professor = ProfessorEntity.of(enumLanguageType, request) if (request.labName != null) { val lab = labRepository.findByName(request.labName) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}") + ?: throw CserealException.Csereal404( + "해당 연구실을 찾을 수 없습니다. LabName: ${request.labName}" + ) professor.addLab(lab) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt index 7ee21f75..87407f66 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt @@ -1,10 +1,15 @@ package com.wafflestudio.csereal.core.research.api import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.core.research.dto.* +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest +import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.research.dto.ResearchGroupResponse import com.wafflestudio.csereal.core.research.service.ResearchSearchService import com.wafflestudio.csereal.core.research.service.ResearchService import jakarta.validation.Valid +import jakarta.validation.constraints.Positive import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -28,13 +33,17 @@ class ResearchController( } @GetMapping("/groups") - fun readAllResearchGroups(): ResponseEntity { - return ResponseEntity.ok(researchService.readAllResearchGroups()) + fun readAllResearchGroups( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(researchService.readAllResearchGroups(language)) } @GetMapping("/centers") - fun readAllResearchCenters(): ResponseEntity> { - return ResponseEntity.ok(researchService.readAllResearchCenters()) + fun readAllResearchCenters( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(researchService.readAllResearchCenters(language)) } @AuthenticatedStaff @@ -62,8 +71,10 @@ class ResearchController( } @GetMapping("/labs") - fun readAllLabs(): ResponseEntity> { - return ResponseEntity.ok(researchService.readAllLabs()) + fun readAllLabs( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(researchService.readAllLabs(language)) } @GetMapping("/lab/{labId}") @@ -130,13 +141,28 @@ class ResearchController( @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, - @RequestParam(required = true) number: Int - ) = researchSearchService.searchTopResearch(keyword, number) + @RequestParam(required = true) @Valid @Positive number: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = researchSearchService.searchTopResearch( + keyword, + LanguageType.makeStringToLanguageType(language), + number, + amount + ) @GetMapping("/search") fun searchPage( @RequestParam(required = true) keyword: String, @RequestParam(required = true) pageSize: Int, - @RequestParam(required = true) pageNum: Int - ) = researchSearchService.searchResearch(keyword, pageSize, pageNum) + @RequestParam(required = true) pageNum: Int, + @RequestParam(required = true, defaultValue = "ko") language: String, + @RequestParam(required = false, defaultValue = "30") @Valid @Positive amount: Int + ) = researchSearchService.searchResearch( + keyword, + LanguageType.makeStringToLanguageType(language), + pageSize, + pageNum, + amount + ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt new file mode 100644 index 00000000..04616d92 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResBody.kt @@ -0,0 +1,22 @@ +package com.wafflestudio.csereal.core.research.api.res + +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity + +data class ResearchSearchResBody( + val results: List, + val total: Long +) { + companion object { + fun of( + researches: List, + keyword: String, + amount: Int, + total: Long + ) = ResearchSearchResBody( + results = researches.map { + ResearchSearchResElement.of(it, keyword, amount) + }, + total = total + ) + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt new file mode 100644 index 00000000..6a3f825e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/res/ResearchSearchResElement.kt @@ -0,0 +1,90 @@ +package com.wafflestudio.csereal.core.research.api.res + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.properties.LanguageType +import com.wafflestudio.csereal.common.utils.substringAroundKeyword +import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity +import com.wafflestudio.csereal.core.research.database.ResearchSearchType + +data class ResearchSearchResElement( + val id: Long, + val language: String, + val name: String, + val researchType: ResearchSearchType, + val partialDescription: String, + val boldStartIdx: Int, + val boldEndIdx: Int +) { + companion object { + fun of( + researchSearchEntity: ResearchSearchEntity, + keyword: String, + amount: Int + ): ResearchSearchResElement = + when { + researchSearchEntity.research != null && + researchSearchEntity.lab == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.research!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.RESEARCH, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + researchSearchEntity.lab != null && + researchSearchEntity.research == null && + researchSearchEntity.conferenceElement == null + -> researchSearchEntity.lab!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.LAB, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + researchSearchEntity.conferenceElement != null && + researchSearchEntity.research == null && + researchSearchEntity.lab == null + -> researchSearchEntity.conferenceElement!!.let { + val (startIdx, partialDesc) = substringAroundKeyword( + keyword, + researchSearchEntity.content, + amount + ) + ResearchSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, + researchType = ResearchSearchType.CONFERENCE, + partialDescription = partialDesc, + boldStartIdx = startIdx ?: 0, + boldEndIdx = startIdx?.plus(keyword.length) ?: 0 + ) + } + + else -> throw CserealException.Csereal401( + "ResearchSearchEntity의 연결이 올바르지 않습니다." + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 78d59a55..66daa817 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest @@ -9,6 +10,7 @@ import jakarta.persistence.* @Entity(name = "lab") class LabEntity( + var language: LanguageType, var name: String, @OneToMany(mappedBy = "lab") @@ -36,8 +38,9 @@ class LabEntity( ) : BaseTimeEntity() { companion object { - fun of(labDto: LabDto, researchGroup: ResearchEntity): LabEntity { + fun of(languageType: LanguageType, labDto: LabDto, researchGroup: ResearchEntity): LabEntity { return LabEntity( + language = languageType, name = labDto.name, location = labDto.location, tel = labDto.tel, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt index abbe4372..71a26786 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabRepository.kt @@ -1,8 +1,9 @@ package com.wafflestudio.csereal.core.research.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface LabRepository : JpaRepository { - fun findAllByOrderByName(): List + fun findAllByLanguageOrderByName(languageType: LanguageType): List fun findByName(name: String): LabEntity? } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt index 20997609..33b1a55c 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchEntity.kt @@ -3,6 +3,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity import com.wafflestudio.csereal.common.controller.AttachmentContentEntityType import com.wafflestudio.csereal.common.controller.MainImageContentEntityType +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.dto.ResearchDto import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity @@ -13,6 +14,9 @@ class ResearchEntity( @Enumerated(EnumType.STRING) var postType: ResearchPostType, + @Enumerated(EnumType.STRING) + var language: LanguageType, + var name: String, @Column(columnDefinition = "mediumText") @@ -34,9 +38,10 @@ class ResearchEntity( override fun bringAttachments() = attachments companion object { - fun of(researchDto: ResearchDto): ResearchEntity { + fun of(languageType: LanguageType, researchDto: ResearchDto): ResearchEntity { return ResearchEntity( postType = researchDto.postType, + language = languageType, name = researchDto.name, description = researchDto.description ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt index a91ac5af..b4dd7323 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchRepository.kt @@ -1,8 +1,12 @@ package com.wafflestudio.csereal.core.research.database +import com.wafflestudio.csereal.common.properties.LanguageType import org.springframework.data.jpa.repository.JpaRepository interface ResearchRepository : JpaRepository { fun findByName(name: String): ResearchEntity? - fun findAllByPostTypeOrderByName(postType: ResearchPostType): List + fun findAllByPostTypeAndLanguageOrderByName( + postType: ResearchPostType, + languageType: LanguageType + ): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt index 96dea54f..d07c5ae7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchEntity.kt @@ -1,6 +1,7 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.utils.cleanTextFromHtml import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import jakarta.persistence.* @@ -10,6 +11,9 @@ class ResearchSearchEntity( @Column(columnDefinition = "TEXT") var content: String, + @Enumerated(value = EnumType.STRING) + val language: LanguageType, + @OneToOne @JoinColumn(name = "research_id") val research: ResearchEntity? = null, @@ -26,6 +30,7 @@ class ResearchSearchEntity( fun create(research: ResearchEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(research), + language = research.language, research = research ) } @@ -33,6 +38,7 @@ class ResearchSearchEntity( fun create(lab: LabEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(lab), + language = lab.language, lab = lab ) } @@ -40,6 +46,7 @@ class ResearchSearchEntity( fun create(conference: ConferenceEntity): ResearchSearchEntity { return ResearchSearchEntity( content = createContent(conference), + language = conference.language, conferenceElement = conference ) } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt index b9bf2b6d..3c82b593 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchSearchRepository.kt @@ -2,7 +2,9 @@ package com.wafflestudio.csereal.core.research.database import com.querydsl.jpa.impl.JPAQuery import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.common.repository.CommonRepository +import com.wafflestudio.csereal.common.utils.exchangePageNum import com.wafflestudio.csereal.core.conference.database.QConferenceEntity.conferenceEntity import com.wafflestudio.csereal.core.research.database.QLabEntity.labEntity import com.wafflestudio.csereal.core.research.database.QResearchEntity.researchEntity @@ -13,9 +15,12 @@ import org.springframework.stereotype.Repository interface ResearchSearchRepository : JpaRepository, ResearchSearchRepositoryCustom interface ResearchSearchRepositoryCustom { - fun searchTopResearch(keyword: String, number: Int): List - - fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> + fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> } @Repository @@ -23,15 +28,14 @@ class ResearchSearchRepositoryCustomImpl( private val queryFactory: JPAQueryFactory, private val commonRepository: CommonRepository ) : ResearchSearchRepositoryCustom { - override fun searchTopResearch(keyword: String, number: Int): List { - return searchQuery(keyword) - .limit(number.toLong()) - .fetch() - } - - override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): Pair, Long> { - val query = searchQuery(keyword) - val total = getSearchCount(keyword) + override fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int + ): Pair, Long> { + val query = searchQuery(keyword, language) + val total = getSearchCount(keyword, language) val validPageNum = exchangePageNum(pageSize, pageNum, total) val validOffset = (if (validPageNum >= 1) validPageNum - 1 else 0) * pageSize.toLong() @@ -43,7 +47,7 @@ class ResearchSearchRepositoryCustomImpl( return queryResult to total } - fun searchQuery(keyword: String): JPAQuery { + fun searchQuery(keyword: String, language: LanguageType): JPAQuery { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, researchSearchEntity.content @@ -64,11 +68,12 @@ class ResearchSearchRepositoryCustomImpl( conferenceEntity ).fetchJoin() .where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + researchSearchEntity.language.eq(language) ) } - fun getSearchCount(keyword: String): Long { + fun getSearchCount(keyword: String, language: LanguageType): Long { val searchDoubleTemplate = commonRepository.searchFullSingleTextTemplate( keyword, researchSearchEntity.content @@ -80,15 +85,8 @@ class ResearchSearchRepositoryCustomImpl( ).from( researchSearchEntity ).where( - searchDoubleTemplate.gt(0.0) + searchDoubleTemplate.gt(0.0), + researchSearchEntity.language.eq(language) ).fetchOne()!! } - - fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() - } - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt index 9060bd3f..19126f2b 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/LabDto.kt @@ -1,9 +1,11 @@ package com.wafflestudio.csereal.core.research.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.database.LabEntity data class LabDto( val id: Long, + val language: String, val name: String, val professors: List?, val location: String?, @@ -19,6 +21,7 @@ data class LabDto( fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { LabDto( id = this.id, + language = LanguageType.makeLowercase(entity.language), name = this.name, professors = this.professors.map { LabProfessorResponse(id = it.id, name = it.name) }, location = this.location, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt index c1190f39..11325c04 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchDto.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.research.dto +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.research.database.ResearchPostType import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse @@ -8,6 +9,7 @@ import java.time.LocalDateTime data class ResearchDto( val id: Long, val postType: ResearchPostType, + val language: String, val name: String, val description: String?, val createdAt: LocalDateTime?, @@ -21,6 +23,7 @@ data class ResearchDto( ResearchDto( id = this.id, postType = this.postType, + language = LanguageType.makeLowercase(entity.language), name = this.name, description = this.description, createdAt = this.createdAt, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt deleted file mode 100644 index 59cf3b61..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchPageResponse.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity - -data class ResearchSearchPageResponse( - val researches: List, - val total: Long -) { - companion object { - fun of( - researches: List, - total: Long - ) = ResearchSearchPageResponse( - researches = researches.map(ResearchSearchResponseElement::of), - total = total - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt deleted file mode 100644 index ea5e5b37..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchResponseElement.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.common.CserealException -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity -import com.wafflestudio.csereal.core.research.database.ResearchSearchType - -data class ResearchSearchResponseElement( - val id: Long, - val name: String, - val researchType: ResearchSearchType -) { - companion object { - fun of( - researchSearchEntity: ResearchSearchEntity - ): ResearchSearchResponseElement = - when { - researchSearchEntity.research != null && - researchSearchEntity.lab == null && - researchSearchEntity.conferenceElement == null - -> researchSearchEntity.research!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.RESEARCH - ) - } - - researchSearchEntity.lab != null && - researchSearchEntity.research == null && - researchSearchEntity.conferenceElement == null - -> researchSearchEntity.lab!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.LAB - ) - } - - researchSearchEntity.conferenceElement != null && - researchSearchEntity.research == null && - researchSearchEntity.lab == null - -> researchSearchEntity.conferenceElement!!.let { - ResearchSearchResponseElement( - id = it.id, - name = it.name, - researchType = ResearchSearchType.CONFERENCE - ) - } - - else -> throw CserealException.Csereal401( - "ResearchSearchEntity의 연결이 올바르지 않습니다." - ) - } - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt deleted file mode 100644 index 784e2c47..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchSearchTopResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.wafflestudio.csereal.core.research.dto - -import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity - -data class ResearchSearchTopResponse( - val topResearches: List -) { - companion object { - fun of( - topResearches: List - ) = ResearchSearchTopResponse( - topResearches = topResearches.map(ResearchSearchResponseElement::of) - ) - } -} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt index 23af349a..8b9b0bac 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchSearchService.kt @@ -1,13 +1,13 @@ package com.wafflestudio.csereal.core.research.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.event.ProfessorCreatedEvent import com.wafflestudio.csereal.core.member.event.ProfessorDeletedEvent import com.wafflestudio.csereal.core.member.event.ProfessorModifiedEvent import com.wafflestudio.csereal.core.research.database.LabRepository import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository -import com.wafflestudio.csereal.core.research.dto.ResearchSearchPageResponse -import com.wafflestudio.csereal.core.research.dto.ResearchSearchTopResponse +import com.wafflestudio.csereal.core.research.api.res.ResearchSearchResBody import org.springframework.context.event.EventListener import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service @@ -18,8 +18,14 @@ interface ResearchSearchService { fun professorDeletedEventListener(professorDeletedEvent: ProfessorDeletedEvent) fun professorModifiedEventListener(professorModifiedEvent: ProfessorModifiedEvent) fun deleteResearchSearch(researchSearchEntity: ResearchSearchEntity) - fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse - fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse + fun searchTopResearch(keyword: String, language: LanguageType, number: Int, amount: Int): ResearchSearchResBody + fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): ResearchSearchResBody } @Service @@ -28,15 +34,36 @@ class ResearchSearchServiceImpl( private val researchSearchRepository: ResearchSearchRepository ) : ResearchSearchService { @Transactional(readOnly = true) - override fun searchTopResearch(keyword: String, number: Int): ResearchSearchTopResponse = - ResearchSearchTopResponse.of( - researchSearchRepository.searchTopResearch(keyword, number) - ) + override fun searchTopResearch( + keyword: String, + language: LanguageType, + number: Int, + amount: Int + ): ResearchSearchResBody = + researchSearchRepository.searchResearch(keyword, language, number, 1).let { + ResearchSearchResBody.of( + researches = it.first, + keyword = keyword, + amount = amount, + total = it.second + ) + } @Transactional(readOnly = true) - override fun searchResearch(keyword: String, pageSize: Int, pageNum: Int): ResearchSearchPageResponse = - researchSearchRepository.searchResearch(keyword, pageSize, pageNum).let { - ResearchSearchPageResponse.of(it.first, it.second) + override fun searchResearch( + keyword: String, + language: LanguageType, + pageSize: Int, + pageNum: Int, + amount: Int + ): ResearchSearchResBody = + researchSearchRepository.searchResearch(keyword, language, pageSize, pageNum).let { + ResearchSearchResBody.of( + researches = it.first, + keyword = keyword, + amount = amount, + total = it.second + ) } @EventListener diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt index 2dc97539..4e700af0 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/service/ResearchService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.research.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.properties.EndpointProperties +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* @@ -20,8 +21,8 @@ interface ResearchService { attachments: List? ): ResearchDto - fun readAllResearchGroups(): ResearchGroupResponse - fun readAllResearchCenters(): List + fun readAllResearchGroups(language: String): ResearchGroupResponse + fun readAllResearchCenters(language: String): List fun updateResearchDetail( researchId: Long, request: ResearchDto, @@ -30,7 +31,7 @@ interface ResearchService { ): ResearchDto fun createLab(request: LabDto, pdf: MultipartFile?): LabDto - fun readAllLabs(): List + fun readAllLabs(language: String): List fun readLab(labId: Long): LabDto fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto fun migrateResearchDetail(requestList: List): List @@ -58,7 +59,8 @@ class ResearchServiceImpl( mainImage: MultipartFile?, attachments: List? ): ResearchDto { - val newResearch = ResearchEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newResearch = ResearchEntity.of(enumLanguageType, request) if (request.labs != null) { for (lab in request.labs) { @@ -82,37 +84,49 @@ class ResearchServiceImpl( researchRepository.save(newResearch) val imageURL = mainImageService.createImageURL(newResearch.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(newResearch.attachments) + val attachmentResponses = + attachmentService.createAttachmentResponses(newResearch.attachments) return ResearchDto.of(newResearch, imageURL, attachmentResponses) } @Transactional(readOnly = true) - override fun readAllResearchGroups(): ResearchGroupResponse { + override fun readAllResearchGroups(language: String): ResearchGroupResponse { // Todo: description 수정 필요 val description = "세계가 주목하는 컴퓨터공학부의 많은 교수들은 ACM, IEEE 등 " + - "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, 기조연설자 등으로 활발하게 활동하고 있습니다. " + - "정부 지원과제, 민간 산업체 지원 연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, " + - "오늘도 인류가 꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." - - val researchGroups = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.GROUPS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) - } + "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, " + + "기조연설자 등으로 활발하게 활동하고 있습니다. 정부 지원과제, 민간 산업체 지원 " + + "연구과제 등도 성공적으로 수행, 우수한 성과들을 내놓고 있으며, 오늘도 인류가 " + + "꿈꾸는 행복하고 편리한 세상을 위해 변화와 혁신, 연구와 도전을 계속하고 있습니다." + + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val researchGroups = + researchRepository.findAllByPostTypeAndLanguageOrderByName( + ResearchPostType.GROUPS, + enumLanguageType + ).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) + } return ResearchGroupResponse(description, researchGroups) } @Transactional(readOnly = true) - override fun readAllResearchCenters(): List { - val researchCenters = researchRepository.findAllByPostTypeOrderByName(ResearchPostType.CENTERS).map { - val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) - } + override fun readAllResearchCenters(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val researchCenters = + researchRepository.findAllByPostTypeAndLanguageOrderByName( + ResearchPostType.CENTERS, + enumLanguageType + ).map { + val imageURL = mainImageService.createImageURL(it.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) + + ResearchDto.of(it, imageURL, attachmentResponses) + } return researchCenters } @@ -182,7 +196,8 @@ class ResearchServiceImpl( throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - val newLab = LabEntity.of(request, researchGroup) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newLab = LabEntity.of(enumLanguageType, request, researchGroup) if (request.professors != null) { for (professor in request.professors) { @@ -208,8 +223,9 @@ class ResearchServiceImpl( } @Transactional(readOnly = true) - override fun readAllLabs(): List { - val labs = labRepository.findAllByOrderByName().map { + override fun readAllLabs(language: String): List { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val labs = labRepository.findAllByLanguageOrderByName(enumLanguageType).map { var pdfURL = "" if (it.pdf != null) { pdfURL = createPdfURL(it.pdf!!) @@ -292,7 +308,8 @@ class ResearchServiceImpl( override fun migrateResearchDetail(requestList: List): List { val list = mutableListOf() for (request in requestList) { - val newResearch = ResearchEntity.of(request) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newResearch = ResearchEntity.of(enumLanguageType, request) newResearch.researchSearch = ResearchSearchEntity.create(newResearch) @@ -315,7 +332,8 @@ class ResearchServiceImpl( throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } - val newLab = LabEntity.of(request, researchGroup) + val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) + val newLab = LabEntity.of(enumLanguageType, request, researchGroup) newLab.researchSearch = ResearchSearchEntity.create(newLab) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt new file mode 100644 index 00000000..cb6863f4 --- /dev/null +++ b/src/test/kotlin/com/wafflestudio/csereal/core/about/AboutServiceTest.kt @@ -0,0 +1,106 @@ +package com.wafflestudio.csereal.core.about + +// FIXME: org.springframework.dao.InvalidDataAccessResourceUsageException +//import com.querydsl.jpa.impl.JPAQueryFactory +//import com.wafflestudio.csereal.core.about.database.* +//import com.wafflestudio.csereal.core.about.dto.AboutDto +//import com.wafflestudio.csereal.core.about.service.AboutService +//import com.wafflestudio.csereal.core.about.service.AboutServiceImpl +//import com.wafflestudio.csereal.global.config.TestConfig +//import io.kotest.core.spec.style.BehaviorSpec +//import io.kotest.extensions.spring.SpringExtension +//import io.kotest.extensions.spring.SpringTestExtension +//import io.kotest.extensions.spring.SpringTestLifecycleMode +//import io.kotest.matchers.shouldBe +//import io.kotest.matchers.shouldNotBe +//import io.mockk.impl.annotations.MockK +//import io.swagger.v3.oas.annotations.extensions.Extension +//import jakarta.persistence.EntityManager +//import org.junit.jupiter.api.extension.ExtendWith +//import org.springframework.boot.test.context.SpringBootTest +//import org.springframework.context.annotation.Import +//import org.springframework.context.annotation.Profile +//import org.springframework.data.repository.findByIdOrNull +//import org.springframework.test.context.ActiveProfiles +//import org.springframework.transaction.annotation.Transactional +// +////@SpringBootTest(classes = [ +//// AboutCustomRepositoryImpl::class, +//// AboutServiceImpl::class, +////]) +////@Import(TestConfig::class) +//@SpringBootTest +//@Transactional +//class AboutServiceTest( +// private val aboutService: AboutService, +// private val aboutRepository: AboutRepository, +//): BehaviorSpec ({ +// extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) +// +// Given("이미지, 첨부 파일 없는 About 정보가 주어지는 경우") { +// val postTypeStr = "facilities" +// val aboutDto = AboutDto( +// name = "test name", +// description = "

test description

", +// language = "ko", +// locations = listOf("testLoc1", "testLoc2"), +// attachments = null, +// imageURL = null, +// createdAt = null, +// modifiedAt = null, +// year = null, +// ) +// +// When("About을 생성한다면") { +// val createdAboutDto = aboutService.createAbout( +// postTypeStr, +// aboutDto, +// null, +// null +// ) +// +// Then("반환된 Dto가 주어진 정보와 일치하여야 한다.") { +// createdAboutDto.id shouldNotBe null +// createdAboutDto.name shouldBe aboutDto.name +// createdAboutDto.description shouldBe aboutDto.description +// createdAboutDto.language shouldBe aboutDto.language +// createdAboutDto.locations shouldBe aboutDto.locations +// } +// +// Then("About이 DB에 저장되어야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.name shouldBe aboutDto.name +// about.description shouldBe aboutDto.description +// about.language shouldBe aboutDto.language +// about.locations shouldBe aboutDto.locations +// } +// +// Then("About의 postType이 facilities여야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.postType shouldBe AboutPostType.FACILITIES +// } +// +// Then("About의 searchContent가 생성되어야 한다.") { +// val about = createdAboutDto.id?.let { +// aboutRepository.findByIdOrNull(it) +// } +// +// about shouldNotBe null +// about!!.searchContent shouldBe """ +// test name +// test description +// testLoc1 +// testLoc2 +// """.trimIndent() +// } +// } +// } +//}) diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt index 206cf76d..2b6a87f0 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/conference/service/ConferenceServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.conference.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.conference.database.ConferenceEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageEntity import com.wafflestudio.csereal.core.conference.database.ConferencePageRepository @@ -76,18 +77,21 @@ class ConferenceServiceTest( val conferences = conferenceRepository.saveAll( listOf( ConferenceEntity( + language = LanguageType.KO, code = "code1", name = "name1", abbreviation = "abbreviation1", conferencePage = conferencePage ), ConferenceEntity( + language = LanguageType.KO, code = "code2", name = "name2", abbreviation = "abbreviation2", conferencePage = conferencePage ), ConferenceEntity( + language = LanguageType.KO, code = "code3", name = "name3", abbreviation = "abbreviation3", @@ -105,11 +109,13 @@ class ConferenceServiceTest( val deleteConferenceId = conferences[1].id val modifiedConference = ConferenceDto( id = conferences.first().id, + language = "ko", code = "code0", name = "modifiedName", abbreviation = "modifiedAbbreviation" ) val newConference = ConferenceDto( + language = "ko", code = "code9", name = "newName", abbreviation = "newAbbreviation" diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt index b3a8b144..6e38fa5e 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/ProfessorServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus @@ -13,9 +14,11 @@ import io.kotest.matchers.shouldNotBe import jakarta.transaction.Transactional import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.ActiveProfiles import java.time.LocalDate @SpringBootTest +@ActiveProfiles("test") @Transactional class ProfessorServiceTest( private val professorService: ProfessorService, @@ -35,11 +38,13 @@ class ProfessorServiceTest( val date = LocalDate.now() val researchEntity = ResearchEntity( + language = LanguageType.KO, name = "researchName", description = null, postType = ResearchPostType.LABS ) var labEntity = LabEntity( + language = LanguageType.KO, name = "labName", location = null, tel = null, @@ -54,6 +59,7 @@ class ProfessorServiceTest( labEntity = labRepository.save(labEntity) val professorDto = ProfessorDto( + language = "ko", name = "name", email = "email", status = ProfessorStatus.ACTIVE, @@ -103,6 +109,7 @@ class ProfessorServiceTest( val memberSearchEntity = memberSearchRepository.findAll()[0] memberSearchEntity.professor?.id shouldBe createdProfessorDto.id + memberSearchEntity.language shouldBe LanguageType.KO val contentExpected = """ name @@ -133,11 +140,13 @@ class ProfessorServiceTest( Given("생성되어 있는 간단한 교수에 대하여") { val date = LocalDate.now() val researchEntity = ResearchEntity( + language = LanguageType.KO, name = "researchName", description = null, postType = ResearchPostType.LABS ) val labEntity1 = LabEntity( + language = LanguageType.KO, name = "labName1", location = null, tel = null, @@ -148,6 +157,7 @@ class ProfessorServiceTest( research = researchEntity ) val labEntity2 = LabEntity( + language = LanguageType.KO, name = "labName2", location = null, tel = null, @@ -162,6 +172,7 @@ class ProfessorServiceTest( val createdProfessorDto = professorService.createProfessor( ProfessorDto( + language = "ko", name = "name", email = "email", status = ProfessorStatus.ACTIVE, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt index 37043fad..40644618 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/member/service/StaffServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.member.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.StaffRepository import com.wafflestudio.csereal.core.member.dto.StaffDto @@ -11,8 +12,10 @@ import io.kotest.matchers.shouldNotBe import jakarta.transaction.Transactional import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.ActiveProfiles @SpringBootTest +@ActiveProfiles("test") @Transactional class StaffServiceTest( private val staffService: StaffService, @@ -60,6 +63,7 @@ class StaffServiceTest( val staffEntity = staffRepository.findByIdOrNull(createdStaffDto.id!!)!! val memberSearch = staffEntity.memberSearch!! + memberSearch.language shouldBe LanguageType.KO memberSearch.content shouldBe """ name role diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt index 032743ba..76603b52 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchSearchServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus import com.wafflestudio.csereal.core.member.dto.ProfessorDto @@ -47,6 +48,7 @@ class ResearchSearchServiceTest( // Save professors val professor1Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "professor1", email = null, status = ProfessorStatus.ACTIVE, @@ -67,6 +69,7 @@ class ResearchSearchServiceTest( ) val professor2Dto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "professor2", email = null, status = ProfessorStatus.ACTIVE, @@ -92,6 +95,7 @@ class ResearchSearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -101,6 +105,7 @@ class ResearchSearchServiceTest( // Save lab val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), @@ -118,6 +123,7 @@ class ResearchSearchServiceTest( val emptyLabDto = LabDto( id = -1, + language = "ko", name = "nameE", professors = listOf(), acronym = "acronymE", @@ -160,6 +166,7 @@ class ResearchSearchServiceTest( When("professor가 추가된다면") { val process3CreatedDto = professorService.createProfessor( createProfessorRequest = ProfessorDto( + language = "ko", name = "newProfessor", email = "email", status = ProfessorStatus.ACTIVE, diff --git a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt index 468372f1..c7e8e91d 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/core/reseach/service/ResearchServiceTest.kt @@ -1,5 +1,6 @@ package com.wafflestudio.csereal.core.reseach.service +import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.member.database.ProfessorEntity import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus @@ -16,9 +17,11 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.context.ActiveProfiles import org.springframework.transaction.annotation.Transactional @SpringBootTest +@ActiveProfiles("test") @Transactional class ResearchServiceTest( private val researchService: ResearchService, @@ -43,6 +46,7 @@ class ResearchServiceTest( Given("간단한 Research를 생성하려고 할 때") { val researchDto = ResearchDto( id = -1, + language = "ko", name = "name", postType = ResearchPostType.CENTERS, description = "description", @@ -77,6 +81,7 @@ class ResearchServiceTest( val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! val researchSearch = research.researchSearch researchSearch shouldNotBe null + researchSearch!!.language shouldBe LanguageType.KO researchSearch!!.content shouldBe """ @@ -92,6 +97,7 @@ class ResearchServiceTest( Given("간단한 Research를 수정하려고 할 때") { val researchDto = ResearchDto( id = -1, + language = "ko", name = "name", postType = ResearchPostType.CENTERS, description = "description", @@ -111,6 +117,7 @@ class ResearchServiceTest( When("Research를 수정한다면") { val researchUpdateRequest = ResearchDto( id = createdResearchDto.id, + language = "ko", name = "name2", postType = ResearchPostType.GROUPS, description = "description2", @@ -156,6 +163,7 @@ class ResearchServiceTest( // Save professors val professor1 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor1", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -170,6 +178,7 @@ class ResearchServiceTest( ) val professor2 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor2", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -186,6 +195,7 @@ class ResearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -194,6 +204,7 @@ class ResearchServiceTest( val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), @@ -235,6 +246,7 @@ class ResearchServiceTest( val lab = labRepository.findByIdOrNull(createdLabDto.id)!! val researchSearch = lab.researchSearch researchSearch shouldNotBe null + researchSearch!!.language shouldBe LanguageType.KO researchSearch!!.content shouldBe """ @@ -258,6 +270,7 @@ class ResearchServiceTest( // Save professors val professor1 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor1", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -272,6 +285,7 @@ class ResearchServiceTest( ) val professor2 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor2", status = ProfessorStatus.ACTIVE, academicRank = "professor", @@ -288,6 +302,7 @@ class ResearchServiceTest( // Save research val research = researchRepository.save( ResearchEntity( + language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, description = null @@ -297,6 +312,7 @@ class ResearchServiceTest( // Save lab val labDto = LabDto( id = -1, + language = "ko", name = "name", professors = listOf( LabProfessorResponse(professor1.id, professor1.name), @@ -318,6 +334,7 @@ class ResearchServiceTest( When("pdf를 제외하고 Lab을 수정한다면") { val professor3 = professorRepository.save( ProfessorEntity( + language = LanguageType.KO, name = "professor3", status = ProfessorStatus.ACTIVE, academicRank = "professor",