diff --git a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt index f0f59159..da3564b8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/common/utils/Utils.kt @@ -1,8 +1,13 @@ package com.wafflestudio.csereal.common.utils +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.common.mockauth.CustomPrincipal import org.jsoup.Jsoup import org.jsoup.parser.Parser import org.jsoup.safety.Safelist +import org.springframework.security.core.Authentication +import org.springframework.security.oauth2.core.oidc.user.OidcUser +import kotlin.math.ceil fun cleanTextFromHtml(description: String): String { val cleanDescription = Jsoup.clean(description, Safelist.none()) @@ -27,15 +32,27 @@ fun substringAroundKeyword(keyword: String, content: String, amount: Int): Pair< } } -fun exchangePageNum(pageSize: Int, pageNum: Int, total: Long): Int { +fun exchangeValidPageNum(pageSize: Int, pageNum: Int, total: Long): Int { // Validate if (!(pageSize > 0 && pageNum > 0 && total >= 0)) { throw RuntimeException() } - return if ((pageNum - 1) * pageSize < total) { - pageNum - } else { - Math.ceil(total.toDouble() / pageSize).toInt() + return when { + total == 0L -> 1 + (pageNum - 1) * pageSize < total -> pageNum + else -> ceil(total.toDouble() / pageSize).toInt() + } +} + +fun getUsername(authentication: Authentication?): String? { + val principal = authentication?.principal + + return principal?.let { + when (principal) { + is OidcUser -> principal.idToken.getClaim("username") + is CustomPrincipal -> principal.userEntity.username + else -> throw CserealException.Csereal401("Unsupported principal type") + } } } 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 ac46e33e..8af09233 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 @@ -48,7 +48,7 @@ class AboutController( @GetMapping("/student-clubs") fun readAllClubs( @RequestParam(required = false, defaultValue = "ko") language: String - ): ResponseEntity> { + ): ResponseEntity> { return ResponseEntity.ok(aboutService.readAllClubs(language)) } @@ -67,8 +67,10 @@ class AboutController( } @GetMapping("/future-careers") - fun readFutureCareers(): ResponseEntity { - return ResponseEntity.ok(aboutService.readFutureCareers()) + fun readFutureCareers( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(aboutService.readFutureCareers(language)) } @GetMapping("/search/top") 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 727cbb09..1c00139b 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 @@ -4,7 +4,7 @@ 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.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.core.about.database.QAboutEntity.aboutEntity import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -41,10 +41,8 @@ class AboutCustomRepositoryImpl( 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 validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = searchQueryExpr(keyword, language) .offset(validOffset) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt index 89b70984..45968622 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/StudentClubDto.kt @@ -3,21 +3,43 @@ package com.wafflestudio.csereal.core.about.dto import com.fasterxml.jackson.annotation.JsonInclude import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.about.database.AboutEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse +import java.time.LocalDateTime data class StudentClubDto( @JsonInclude(JsonInclude.Include.NON_NULL) val id: Long? = null, val language: String, val name: String, - val description: String + val engName: String, + val description: String, + val year: Int?, + val createdAt: LocalDateTime?, + val modifiedAt: LocalDateTime?, + val locations: List?, + val imageURL: String?, + val attachments: List? ) { companion object { - fun of(entity: AboutEntity): StudentClubDto = entity.run { + fun of( + entity: AboutEntity, + name: String, + engName: String, + imageURL: String?, + attachmentResponses: List + ): StudentClubDto = entity.run { StudentClubDto( id = this.id, language = LanguageType.makeLowercase(this.language), - name = this.name!!, - description = this.description + name = name, + engName = engName, + description = this.description, + year = this.year, + createdAt = this.createdAt, + modifiedAt = this.modifiedAt, + locations = this.locations, + imageURL = imageURL, + attachments = attachmentResponses ) } } 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 e85732e7..884e1554 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 @@ -22,10 +22,10 @@ interface AboutService { ): AboutDto fun readAbout(language: String, postType: String): AboutDto - fun readAllClubs(language: String): List + fun readAllClubs(language: String): List fun readAllFacilities(language: String): List fun readAllDirections(language: String): List - fun readFutureCareers(): FutureCareersPage + fun readFutureCareers(language: String): FutureCareersPage fun searchTopAbout( keyword: String, @@ -104,17 +104,19 @@ class AboutServiceImpl( } @Transactional(readOnly = true) - override fun readAllClubs(language: String): List { + override fun readAllClubs(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) val clubs = aboutRepository.findAllByLanguageAndPostTypeOrderByName( languageType, AboutPostType.STUDENT_CLUBS ).map { + val name = it.name!!.split("(")[0] + val engName = it.name!!.split("(")[1].replaceFirst(")", "") val imageURL = mainImageService.createImageURL(it.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - AboutDto.of(it, imageURL, attachmentResponses) + StudentClubDto.of(it, name, engName, imageURL, attachmentResponses) } return clubs @@ -154,17 +156,13 @@ class AboutServiceImpl( } @Transactional - override fun readFutureCareers(): FutureCareersPage { - val description = "컴퓨터공학을 전공함으로써 벤처기업을 창업할 수 있을 뿐 " + - "아니라 시스템엔지니어, 보안전문가, 소프트웨어개발자, 데이터베이스관리자 등 " + - "많은 IT 전문 분야로의 진출이 가능하다. 또한 컴퓨터공학은 바이오, 전자전기, " + - "로봇, 기계, 의료 등 이공계 영역뿐만 아니라 정치, 경제, 사회, 문화의 다양한 분야와 " + - "결합되어 미래 지식정보사회에 대한 새로운 가능성을 제시하고 있고 새로운 학문적 과제가 " + - "지속적으로 생산되기 때문에 많은 전문연구인력이 필요하다.\n" + - "\n" + - "서울대학교 컴퓨터공학부의 경우 학부 졸업생 절반 이상이 대학원에 진학하고 있다. " + - "대학원에 진학하면 여러 전공분야 중 하나를 선택하여 보다 깊이 있는 지식의 습득과 연구과정을 거치게 되며 " + - "그 이후로는 국내외 관련 산업계, 학계에 주로 진출하고 있고, 새로운 아이디어로 벤처기업을 창업하기도 한다." + override fun readFutureCareers(language: String): FutureCareersPage { + val languageType = LanguageType.makeStringToLanguageType(language) + val description = + aboutRepository.findByLanguageAndPostType( + languageType, + AboutPostType.FUTURE_CAREERS + ).description val statList = mutableListOf() for (i: Int in 2021 downTo 2011) { @@ -342,7 +340,8 @@ class AboutServiceImpl( for (request in requestList) { val language = request.language - val name = request.name + val name = request.name.split("(")[0] + val engName = request.name.split("(")[1].replaceFirst(")", "") val aboutDto = AboutDto( id = null, @@ -363,7 +362,7 @@ class AboutServiceImpl( syncSearchOfAbout(newAbout) newAbout = aboutRepository.save(newAbout) - list.add(StudentClubDto.of(newAbout)) + list.add(StudentClubDto.of(newAbout, name, engName, null, listOf())) } return list } 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 06d8a5cc..88f69bd0 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 @@ -41,6 +41,13 @@ class AcademicsController( return ResponseEntity.ok(academicsService.readGuide(language, studentType)) } + @GetMapping("/undergraduate/general-studies-requirements") + fun readGeneralStudiesRequirements( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity { + return ResponseEntity.ok(academicsService.readGeneralStudiesRequirements(language)) + } + @GetMapping("/{studentType}/{postType}") fun readAcademicsYearResponses( @RequestParam(required = false, defaultValue = "ko") language: String, 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 index 8958fdcf..452666c5 100644 --- 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 @@ -3,14 +3,18 @@ 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.AcademicsPostType import com.wafflestudio.csereal.core.academics.database.AcademicsSearchEntity import com.wafflestudio.csereal.core.academics.database.AcademicsSearchType +import com.wafflestudio.csereal.core.academics.database.AcademicsStudentType data class AcademicsSearchResElement( val id: Long, val language: String, val name: String, - val academicsType: AcademicsSearchType, + val postType: AcademicsSearchType, + val studentType: AcademicsStudentType? = null, + val academicType: AcademicsPostType? = null, val partialDescription: String, val boldStartIndex: Int, val boldEndIndex: Int @@ -30,17 +34,21 @@ data class AcademicsSearchResElement( 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!!.let { + AcademicsSearchResElement( + id = it.id, + name = it.name, + language = it.language.let { lan -> + LanguageType.makeLowercase(lan) + }, + postType = AcademicsSearchType.ACADEMICS, + academicType = it.postType, + studentType = it.studentType, + partialDescription = partialDescription.replace("\n", " "), + boldStartIndex = startIdx ?: 0, + boldEndIndex = startIdx?.plus(keyword.length) ?: 0 + ) + } } academicsSearch.academics == null && @@ -57,7 +65,7 @@ data class AcademicsSearchResElement( language = academicsSearch.course!!.language.let { LanguageType.makeLowercase(it) }, - academicsType = AcademicsSearchType.COURSE, + postType = AcademicsSearchType.COURSE, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 @@ -78,7 +86,7 @@ data class AcademicsSearchResElement( language = academicsSearch.scholarship!!.language.let { LanguageType.makeLowercase(it) }, - academicsType = AcademicsSearchType.SCHOLARSHIP, + postType = AcademicsSearchType.SCHOLARSHIP, partialDescription = partialDescription.replace("\n", " "), boldStartIndex = startIdx ?: 0, boldEndIndex = startIdx?.plus(keyword.length) ?: 0 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 fb77471f..ac8b4d07 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 @@ -3,4 +3,11 @@ package com.wafflestudio.csereal.core.academics.database enum class AcademicsPostType { GUIDE, GENERAL_STUDIES_REQUIREMENTS, GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES, CURRICULUM, DEGREE_REQUIREMENTS, DEGREE_REQUIREMENTS_YEAR_LIST, COURSE_CHANGES, SCHOLARSHIP; + + // GUIDE: 학부 안내 + // GENERAL_STUDIES_REQUIREMENTS: 필수 교양 과목 + // CURRICULUM: 전공 이수 표준 형태 + // DEGREE_REQUIREMENTS: 졸업 규정 + // 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 2aeeb80e..a3b0beff 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 @@ -9,6 +9,12 @@ interface AcademicsRepository : JpaRepository { studentType: AcademicsStudentType, postType: AcademicsPostType ): AcademicsEntity + fun findByLanguageAndStudentTypeAndPostTypeAndYear( + languageType: LanguageType, + studentType: AcademicsStudentType, + postType: AcademicsPostType, + year: Int? + ): AcademicsEntity fun findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( languageType: LanguageType, studentType: AcademicsStudentType, 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 df24e29a..70e35c03 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 @@ -4,7 +4,7 @@ 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.common.utils.exchangeValidPageNum 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 @@ -37,8 +37,8 @@ class AcademicsSearchCustomRepositoryImpl( 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 validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = query.offset(validOffset) .limit(pageSize.toLong()) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt index e5c531a5..604f2c69 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsDto.kt @@ -1,16 +1,18 @@ package com.wafflestudio.csereal.core.academics.dto import com.wafflestudio.csereal.core.academics.database.AcademicsEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse class DegreeRequirementsDto( val year: Int, - val description: String + val attachments: List + ) { companion object { - fun of(entity: AcademicsEntity) = entity.run { + fun of(entity: AcademicsEntity, attachments: List) = entity.run { DegreeRequirementsDto( year = this.year!!, - description = this.description + attachments = attachments ) } } 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 index 8d4d2ad4..3fdfecb1 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/DegreeRequirementsPageResponse.kt @@ -7,10 +7,10 @@ class DegreeRequirementsPageResponse( val yearList: List ) { companion object { - fun of(entity: AcademicsEntity, yearList: List) = entity.run { + fun of(entity: AcademicsEntity, yearList: List) = entity.run { DegreeRequirementsPageResponse( description = this.description, - yearList = yearList.map { DegreeRequirementsDto.of(it) } + yearList = yearList ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt new file mode 100644 index 00000000..efb541fa --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesDto.kt @@ -0,0 +1,19 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class GeneralStudiesDto( + val id: Long, + val year: Int, + val description: String +) { + companion object { + fun of(academicsEntity: AcademicsEntity): GeneralStudiesDto { + return GeneralStudiesDto( + id = academicsEntity.id, + year = academicsEntity.year!!, + description = academicsEntity.description + ) + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt new file mode 100644 index 00000000..0851527d --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/academics/dto/GeneralStudiesRequirementsPageResponse.kt @@ -0,0 +1,23 @@ +package com.wafflestudio.csereal.core.academics.dto + +import com.wafflestudio.csereal.core.academics.database.AcademicsEntity + +class GeneralStudiesRequirementsPageResponse( + val overview: String, + val subjectChanges: String, + val generalStudies: List +) { + companion object { + fun of( + overview: AcademicsEntity, + subjectChanges: AcademicsEntity, + generalStudies: List + ): GeneralStudiesRequirementsPageResponse { + return GeneralStudiesRequirementsPageResponse( + overview = overview.description, + subjectChanges = subjectChanges.description, + generalStudies = generalStudies.map { GeneralStudiesDto.of(it) } + ) + } + } +} 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 d2be6702..1e033e68 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 @@ -26,6 +26,7 @@ interface AcademicsService { studentType: String, postType: String ): List + fun readGeneralStudiesRequirements(language: String): GeneralStudiesRequirementsPageResponse fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse fun createCourse( studentType: String, @@ -138,6 +139,31 @@ class AcademicsServiceImpl( return academicsYearResponses } + @Transactional(readOnly = true) + override fun readGeneralStudiesRequirements(language: String): GeneralStudiesRequirementsPageResponse { + val enumLanguageType = LanguageType.makeStringToLanguageType(language) + val overview = + academicsRepository.findByLanguageAndStudentTypeAndPostTypeAndYear( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS, + null + ) + val subjectChanges = + academicsRepository.findByLanguageAndStudentTypeAndPostType( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS_SUBJECT_CHANGES + ) + val generalStudiesEntity = + academicsRepository.findAllByLanguageAndStudentTypeAndPostTypeOrderByYearDesc( + enumLanguageType, + AcademicsStudentType.UNDERGRADUATE, + AcademicsPostType.GENERAL_STUDIES_REQUIREMENTS + ).filter { academicsEntity -> academicsEntity.year != null } + return GeneralStudiesRequirementsPageResponse.of(overview, subjectChanges, generalStudiesEntity) + } + @Transactional(readOnly = true) override fun readDegreeRequirements(language: String): DegreeRequirementsPageResponse { val enumLanguageType = LanguageType.makeStringToLanguageType(language) @@ -153,7 +179,10 @@ class AcademicsServiceImpl( enumLanguageType, AcademicsStudentType.UNDERGRADUATE, AcademicsPostType.DEGREE_REQUIREMENTS_YEAR_LIST - ) + ).map { + val attachments = attachmentService.createAttachmentResponses(it.attachments) + DegreeRequirementsDto.of(it, attachments) + } return DegreeRequirementsPageResponse.of(academicsEntity, yearList) } 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 522aa41c..4de31ae2 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 @@ -22,7 +22,7 @@ data class AdmissionSearchResElem private constructor( ) = admissions.let { val (boldStartIdx, partialDescription) = substringAroundKeyword( keyword = keyword, - content = it.description, + content = it.searchContent, amount = amount ) 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 71b4d450..67ae8be4 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,7 +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.common.utils.exchangeValidPageNum 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 @@ -39,10 +39,8 @@ class AdmissionsCustomRepositoryImpl( 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 validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val result = searchQueryOfLanguage(keyword, language) .offset(validOffset) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt index 8edfed53..c6f5915e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/conference/api/ConferenceController.kt @@ -27,7 +27,6 @@ class ConferenceController( return ResponseEntity.ok(conferenceService.modifyConferences(conferenceModifyRequest)) } - @AuthenticatedStaff @PostMapping("/migrate") fun migrateConferences( @RequestBody requestList: List 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 2ab47803..bb358ee2 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 @@ -4,7 +4,7 @@ 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.common.utils.exchangeValidPageNum 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 @@ -38,7 +38,7 @@ class MemberSearchRepositoryCustomImpl( val query = searchQuery(keyword, language) val total = getSearchCount(keyword, language) - val validPageNum = exchangePageNum(pageSize, pageNum, total) + val validPageNum = exchangeValidPageNum(pageSize, pageNum, total) val queryResult = query .offset((validPageNum - 1) * pageSize.toLong()) .limit(pageSize.toLong()) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt index 265deaf1..c8756c7e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/member/dto/SimpleProfessorDto.kt @@ -6,6 +6,7 @@ data class SimpleProfessorDto( val id: Long, val name: String, val academicRank: String, + val status: String, val labId: Long?, val labName: String?, val phone: String?, @@ -18,6 +19,7 @@ data class SimpleProfessorDto( id = professorEntity.id, name = professorEntity.name, academicRank = professorEntity.academicRank, + status = professorEntity.status.toString(), labId = professorEntity.lab?.id, labName = professorEntity.lab?.name, phone = professorEntity.phone, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt index 193a097a..0f253dc7 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/api/NewsController.kt @@ -1,10 +1,10 @@ package com.wafflestudio.csereal.core.news.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.news.dto.NewsDto import com.wafflestudio.csereal.core.news.dto.NewsSearchResponse +import com.wafflestudio.csereal.core.news.dto.NewsTotalSearchDto import com.wafflestudio.csereal.core.news.service.NewsService import com.wafflestudio.csereal.core.user.database.Role import com.wafflestudio.csereal.core.user.database.UserRepository @@ -16,7 +16,6 @@ import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -34,15 +33,9 @@ class NewsController( @RequestParam(required = false, defaultValue = "10") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -59,16 +52,29 @@ class NewsController( @NotBlank keyword: String, @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int - ) = ResponseEntity.ok( - newsService.searchTotalNews(keyword, number, stringLength) - ) + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + authentication: Authentication? + ): NewsTotalSearchDto { + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + + return newsService.searchTotalNews(keyword, number, stringLength, isStaff) + } @GetMapping("/{newsId}") fun readNews( - @PathVariable newsId: Long + @PathVariable newsId: Long, + authentication: Authentication? ): ResponseEntity { - return ResponseEntity.ok(newsService.readNews(newsId)) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(newsService.readNews(newsId, isStaff)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt index a27805d1..cf6711d9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/database/NewsRepository.kt @@ -46,7 +46,8 @@ interface CustomNewsRepository { keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String? + imageUrlCreator: (MainImageEntity?) -> String?, + isStaff: Boolean ): NewsTotalSearchDto fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse @@ -135,7 +136,8 @@ class NewsRepositoryImpl( keyword: String, number: Int, amount: Int, - imageUrlCreator: (MainImageEntity?) -> String? + imageUrlCreator: (MainImageEntity?) -> String?, + isStaff: Boolean ): NewsTotalSearchDto { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -143,6 +145,8 @@ class NewsRepositoryImpl( newsEntity.plainTextDescription ) + val privateBoolean = newsEntity.isPrivate.eq(false).takeUnless { isStaff } + val searchResult = queryFactory.select( newsEntity.id, newsEntity.title, @@ -151,7 +155,7 @@ class NewsRepositoryImpl( mainImageEntity ).from(newsEntity) .leftJoin(mainImageEntity) - .where(doubleTemplate.gt(0.0)) + .where(doubleTemplate.gt(0.0), privateBoolean) .orderBy(newsEntity.date.desc()) .limit(number.toLong()) .fetch() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt index e45a4169..0104a651 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/news/service/NewsService.kt @@ -23,7 +23,7 @@ interface NewsService { isStaff: Boolean ): NewsSearchResponse - fun readNews(newsId: Long): NewsDto + fun readNews(newsId: Long, isStaff: Boolean): NewsDto fun createNews(request: NewsDto, mainImage: MultipartFile?, attachments: List?): NewsDto fun updateNews( newsId: Long, @@ -34,7 +34,7 @@ interface NewsService { fun deleteNews(newsId: Long) fun enrollTag(tagName: String) - fun searchTotalNews(keyword: String, number: Int, amount: Int): NewsTotalSearchDto + fun searchTotalNews(keyword: String, number: Int, amount: Int, isStaff: Boolean): NewsTotalSearchDto fun readAllSlides(pageNum: Long, pageSize: Int): AdminSlidesResponse fun unSlideManyNews(request: List) } @@ -62,21 +62,25 @@ class NewsServiceImpl( override fun searchTotalNews( keyword: String, number: Int, - amount: Int + amount: Int, + isStaff: Boolean ) = newsRepository.searchTotalNews( keyword, number, amount, - mainImageService::createImageURL + mainImageService::createImageURL, + isStaff ) @Transactional(readOnly = true) - override fun readNews(newsId: Long): NewsDto { + override fun readNews(newsId: Long, isStaff: Boolean): NewsDto { val news: NewsEntity = newsRepository.findByIdOrNull(newsId) ?: throw CserealException.Csereal404("존재하지 않는 새소식입니다.(newsId: $newsId)") if (news.isDeleted) throw CserealException.Csereal404("삭제된 새소식입니다.(newsId: $newsId)") + if (news.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val imageURL = mainImageService.createImageURL(news.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(news.attachments) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt index 611d7ba8..9d5dd471 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/api/NoticeController.kt @@ -1,8 +1,7 @@ package com.wafflestudio.csereal.core.notice.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.notice.dto.* import com.wafflestudio.csereal.core.notice.service.NoticeService import com.wafflestudio.csereal.core.user.database.Role @@ -15,7 +14,6 @@ import org.springframework.data.domain.PageRequest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -33,15 +31,9 @@ class NoticeController( @RequestParam(required = false, defaultValue = "20") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -58,16 +50,29 @@ class NoticeController( @NotBlank keyword: String, @RequestParam(required = true) @Positive number: Int, - @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int - ) = ResponseEntity.ok( - noticeService.searchTotalNotice(keyword, number, stringLength) - ) + @RequestParam(required = false, defaultValue = "200") @Positive stringLength: Int, + authentication: Authentication? + ): NoticeTotalSearchResponse { + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + + return noticeService.searchTotalNotice(keyword, number, stringLength, isStaff) + } @GetMapping("/{noticeId}") fun readNotice( - @PathVariable noticeId: Long + @PathVariable noticeId: Long, + authentication: Authentication? ): ResponseEntity { - return ResponseEntity.ok(noticeService.readNotice(noticeId)) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(noticeService.readNotice(noticeId, isStaff)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt index 17269405..00b0ee23 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/database/NoticeRepository.kt @@ -17,6 +17,7 @@ import org.springframework.stereotype.Component import java.time.LocalDateTime interface NoticeRepository : JpaRepository, CustomNoticeRepository { + fun findByIdAndIsPrivateFalse(id: Long): NoticeEntity? fun findAllByIsPrivateFalseAndIsImportantTrueAndIsDeletedFalse(): List fun findAllByIsImportantTrueAndIsDeletedFalse(): List fun findFirstByIsDeletedFalseAndIsPrivateFalseAndCreatedAtLessThanOrderByCreatedAtDesc( @@ -37,7 +38,7 @@ interface CustomNoticeRepository { isStaff: Boolean ): NoticeSearchResponse - fun totalSearchNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse + fun totalSearchNotice(keyword: String, number: Int, stringLength: Int, isStaff: Boolean): NoticeTotalSearchResponse } @Component @@ -48,7 +49,8 @@ class NoticeRepositoryImpl( override fun totalSearchNotice( keyword: String, number: Int, - stringLength: Int + stringLength: Int, + isStaff: Boolean ): NoticeTotalSearchResponse { val doubleTemplate = commonRepository.searchFullDoubleTextTemplate( keyword, @@ -56,13 +58,15 @@ class NoticeRepositoryImpl( noticeEntity.plainTextDescription ) + val privateBoolean = noticeEntity.isPrivate.eq(false).takeUnless { isStaff } + val query = queryFactory.select( noticeEntity.id, noticeEntity.title, noticeEntity.createdAt, noticeEntity.plainTextDescription ).from(noticeEntity) - .where(doubleTemplate.gt(0.0)) + .where(doubleTemplate.gt(0.0), privateBoolean) val total = query.clone().select(noticeEntity.countDistinct()).fetchOne()!! diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt index c1c03b0a..86a3cc74 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/notice/service/NoticeService.kt @@ -24,9 +24,9 @@ interface NoticeService { isStaff: Boolean ): NoticeSearchResponse - fun searchTotalNotice(keyword: String, number: Int, stringLength: Int): NoticeTotalSearchResponse + fun searchTotalNotice(keyword: String, number: Int, stringLength: Int, isStaff: Boolean): NoticeTotalSearchResponse - fun readNotice(noticeId: Long): NoticeDto + fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto fun createNotice(request: NoticeDto, attachments: List?): NoticeDto fun updateNotice( noticeId: Long, @@ -64,16 +64,19 @@ class NoticeServiceImpl( override fun searchTotalNotice( keyword: String, number: Int, - stringLength: Int - ) = noticeRepository.totalSearchNotice(keyword, number, stringLength) + stringLength: Int, + isStaff: Boolean + ) = noticeRepository.totalSearchNotice(keyword, number, stringLength, isStaff) @Transactional(readOnly = true) - override fun readNotice(noticeId: Long): NoticeDto { + override fun readNotice(noticeId: Long, isStaff: Boolean): NoticeDto { val notice = noticeRepository.findByIdOrNull(noticeId) ?: throw CserealException.Csereal404("존재하지 않는 공지사항입니다.(noticeId: $noticeId)") if (notice.isDeleted) throw CserealException.Csereal404("삭제된 공지사항입니다.(noticeId: $noticeId)") + if (notice.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val attachmentResponses = attachmentService.createAttachmentResponses(notice.attachments) val prevNotice = 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 index 6a3f825e..593e5138 100644 --- 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 @@ -3,6 +3,7 @@ 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.ResearchPostType import com.wafflestudio.csereal.core.research.database.ResearchSearchEntity import com.wafflestudio.csereal.core.research.database.ResearchSearchType @@ -35,7 +36,10 @@ data class ResearchSearchResElement( id = it.id, name = it.name, language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, - researchType = ResearchSearchType.RESEARCH, + researchType = when (it.postType) { + ResearchPostType.GROUPS -> ResearchSearchType.RESEARCH_GROUP + ResearchPostType.CENTERS -> ResearchSearchType.RESEARCH_CENTER + }, partialDescription = partialDesc, boldStartIdx = startIdx ?: 0, boldEndIdx = startIdx?.plus(keyword.length) ?: 0 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 33b1a55c..e0c3db51 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 @@ -22,6 +22,8 @@ class ResearchEntity( @Column(columnDefinition = "mediumText") var description: String?, + var websiteURL: String?, + @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) var labs: MutableList = mutableListOf(), @@ -43,7 +45,8 @@ class ResearchEntity( postType = researchDto.postType, language = languageType, name = researchDto.name, - description = researchDto.description + description = researchDto.description, + websiteURL = researchDto.websiteURL ) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt index 7d651dd8..4e5d6447 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt @@ -4,6 +4,5 @@ enum class ResearchPostType( val krName: String ) { GROUPS("연구 그룹"), - CENTERS("연구 센터"), - LABS("연구실 목록"); + CENTERS("연구 센터"); } 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 d07c5ae7..cec416bb 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 @@ -58,6 +58,7 @@ class ResearchSearchEntity( appendLine(cleanTextFromHtml(it)) } research.labs.forEach { appendLine(it.name) } + research.websiteURL?.let { appendLine(it) } }.toString() fun createContent(lab: LabEntity) = StringBuilder().apply { @@ -94,17 +95,6 @@ class ResearchSearchEntity( } } - fun ofType(): ResearchSearchType { - return when { - research != null && lab == null && conferenceElement == null -> ResearchSearchType.RESEARCH - research == null && lab != null && conferenceElement == null -> ResearchSearchType.LAB - research == null && lab == null && conferenceElement != null -> ResearchSearchType.CONFERENCE - else -> throw RuntimeException( - "ResearchSearchEntity must have either research or lab or conference" - ) - } - } - fun update(research: ResearchEntity) { this.content = createContent(research) } @@ -119,7 +109,8 @@ class ResearchSearchEntity( } enum class ResearchSearchType { - RESEARCH, + RESEARCH_GROUP, + RESEARCH_CENTER, LAB, 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 3c82b593..d415297d 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 @@ -4,7 +4,7 @@ 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.common.utils.exchangeValidPageNum 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 @@ -37,8 +37,8 @@ class ResearchSearchRepositoryCustomImpl( 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 validPageNum = exchangeValidPageNum(pageSize, pageNum, total) + val validOffset = (validPageNum - 1) * pageSize.toLong() val queryResult = query .offset(validOffset) .limit(pageSize.toLong()) 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 19126f2b..3250be51 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 @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.research.dto import com.wafflestudio.csereal.common.properties.LanguageType import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse data class LabDto( val id: Long, @@ -11,14 +12,14 @@ data class LabDto( val location: String?, val tel: String?, val acronym: String?, - val pdf: String?, + val pdf: AttachmentResponse?, val youtube: String?, val group: String, val description: String?, val websiteURL: String? ) { companion object { - fun of(entity: LabEntity, pdfURL: String): LabDto = entity.run { + fun of(entity: LabEntity, pdf: AttachmentResponse?): LabDto = entity.run { LabDto( id = this.id, language = LanguageType.makeLowercase(entity.language), @@ -27,7 +28,7 @@ data class LabDto( location = this.location, tel = this.tel, acronym = this.acronym, - pdf = pdfURL, + pdf = pdf, youtube = this.youtube, group = this.research.name, description = this.description, 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 11325c04..e87c6668 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 @@ -12,6 +12,7 @@ data class ResearchDto( val language: String, val name: String, val description: String?, + val websiteURL: String?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val labs: List?, @@ -26,6 +27,7 @@ data class ResearchDto( language = LanguageType.makeLowercase(entity.language), name = this.name, description = this.description, + websiteURL = this.websiteURL, createdAt = this.createdAt, modifiedAt = this.modifiedAt, labs = this.labs.map { ResearchLabResponse(id = it.id, name = it.name) }, 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 4e700af0..023eac57 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 @@ -209,28 +209,27 @@ class ResearchServiceImpl( } } - var pdfURL = "" if (pdf != null) { - val attachmentDto = attachmentService.uploadAttachmentInLabEntity(newLab, pdf) - pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" + attachmentService.uploadAttachmentInLabEntity(newLab, pdf) } newLab.researchSearch = ResearchSearchEntity.create(newLab) labRepository.save(newLab) - return LabDto.of(newLab, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(newLab.pdf) + + return LabDto.of(newLab, attachmentResponse) } @Transactional(readOnly = true) 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!!) - } - LabDto.of(it, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(it.pdf) + LabDto.of(it, attachmentResponse) } return labs @@ -240,12 +239,11 @@ class ResearchServiceImpl( override fun readLab(labId: Long): LabDto { val lab = labRepository.findByIdOrNull(labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=$labId)") - var pdfURL = "" - if (lab.pdf != null) { - pdfURL = createPdfURL(lab.pdf!!) - } - return LabDto.of(lab, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(lab.pdf) + + return LabDto.of(lab, attachmentResponse) } private fun createPdfURL(pdf: AttachmentEntity): String { @@ -296,12 +294,10 @@ class ResearchServiceImpl( labEntity.researchSearch = ResearchSearchEntity.create(labEntity) } - return LabDto.of( - labEntity, - labEntity.pdf?.let { - createPdfURL(it) - } ?: "" - ) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(labEntity.pdf) + + return LabDto.of(labEntity, attachmentResponse) } @Transactional @@ -339,7 +335,7 @@ class ResearchServiceImpl( labRepository.save(newLab) - list.add(LabDto.of(newLab, "")) + list.add(LabDto.of(newLab, null)) } return list } @@ -372,12 +368,13 @@ class ResearchServiceImpl( val lab = labRepository.findByIdOrNull(labId) ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.") - var pdfURL = "" if (pdf != null) { val attachmentDto = attachmentService.uploadAttachmentInLabEntity(lab, pdf) - pdfURL = "${endpointProperties.backend}/v1/file/${attachmentDto.filename}" } - return LabDto.of(lab, pdfURL) + val attachmentResponse = + attachmentService.createOneAttachmentResponse(lab.pdf) + + return LabDto.of(lab, attachmentResponse) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt index 4a1f64cf..15d7410e 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/service/AttachmentService.kt @@ -33,7 +33,7 @@ interface AttachmentService { contentEntityType: AttachmentContentEntityType, requestAttachments: List ): List - + fun createOneAttachmentResponse(attachment: AttachmentEntity?): AttachmentResponse? fun createAttachmentResponses(attachments: List?): List fun deleteAttachments(ids: List?) @@ -111,6 +111,23 @@ class AttachmentServiceImpl( return attachmentsList } + @Transactional + override fun createOneAttachmentResponse(attachment: AttachmentEntity?): AttachmentResponse? { + var attachmentDto: AttachmentResponse? = null + if (attachment != null) { + if (attachment.isDeleted == false) { + attachmentDto = AttachmentResponse( + id = attachment.id, + name = attachment.filename.substringAfter("_"), + url = "${endpointProperties.backend}/v1/file/${attachment.filename}", + bytes = attachment.size + ) + } + } + + return attachmentDto + } + @Transactional override fun createAttachmentResponses(attachments: List?): List { val list = mutableListOf() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt index bbf80373..fe744ccc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -1,8 +1,7 @@ package com.wafflestudio.csereal.core.seminar.api -import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.aop.AuthenticatedStaff -import com.wafflestudio.csereal.common.mockauth.CustomPrincipal +import com.wafflestudio.csereal.common.utils.getUsername import com.wafflestudio.csereal.core.seminar.dto.SeminarDto import com.wafflestudio.csereal.core.seminar.dto.SeminarSearchResponse import com.wafflestudio.csereal.core.seminar.service.SeminarService @@ -12,7 +11,6 @@ import jakarta.validation.Valid import org.springframework.data.domain.PageRequest import org.springframework.http.ResponseEntity import org.springframework.security.core.Authentication -import org.springframework.security.oauth2.core.oidc.user.OidcUser import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @@ -29,15 +27,9 @@ class SeminarController( @RequestParam(required = false, defaultValue = "10") pageSize: Int, authentication: Authentication? ): ResponseEntity { - val principal = authentication?.principal - - val isStaff = principal?.let { - val username = when (principal) { - is OidcUser -> principal.idToken.getClaim("username") - is CustomPrincipal -> principal.userEntity.username - else -> throw CserealException.Csereal401("Unsupported principal type") - } - val user = userRepository.findByUsername(username) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) user?.role == Role.ROLE_STAFF } ?: false @@ -61,9 +53,15 @@ class SeminarController( @GetMapping("/{seminarId}") fun readSeminar( - @PathVariable seminarId: Long + @PathVariable seminarId: Long, + authentication: Authentication? ): ResponseEntity { - return ResponseEntity.ok(seminarService.readSeminar(seminarId)) + val username = getUsername(authentication) + val isStaff = username?.let { + val user = userRepository.findByUsername(it) + user?.role == Role.ROLE_STAFF + } ?: false + return ResponseEntity.ok(seminarService.readSeminar(seminarId, isStaff)) } @AuthenticatedStaff diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt index a3cccb30..d6237be8 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -22,7 +22,7 @@ interface SeminarService { ): SeminarSearchResponse fun createSeminar(request: SeminarDto, mainImage: MultipartFile?, attachments: List?): SeminarDto - fun readSeminar(seminarId: Long): SeminarDto + fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto fun updateSeminar( seminarId: Long, request: SeminarDto, @@ -73,12 +73,14 @@ class SeminarServiceImpl( } @Transactional(readOnly = true) - override fun readSeminar(seminarId: Long): SeminarDto { + override fun readSeminar(seminarId: Long, isStaff: Boolean): SeminarDto { val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)") if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") + if (seminar.isPrivate && !isStaff) throw CserealException.Csereal401("접근 권한이 없습니다.") + val imageURL = mainImageService.createImageURL(seminar.mainImage) val attachmentResponses = attachmentService.createAttachmentResponses(seminar.attachments) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1817e8e4..654feeca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -7,7 +7,7 @@ spring: multipart: enabled: true max-request-size: 100MB - max-file-size: 10MB + max-file-size: 100MB server: servlet: diff --git a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt index a54c0d31..f65f03bf 100644 --- a/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt +++ b/src/test/kotlin/com/wafflestudio/csereal/common/util/UtilsTest.kt @@ -1,7 +1,7 @@ package com.wafflestudio.csereal.common.util import com.wafflestudio.csereal.common.utils.cleanTextFromHtml -import com.wafflestudio.csereal.common.utils.exchangePageNum +import com.wafflestudio.csereal.common.utils.exchangeValidPageNum import com.wafflestudio.csereal.common.utils.substringAroundKeyword import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec @@ -112,13 +112,13 @@ class UtilsTest : BehaviorSpec({ Then("should throw AssertionError") { shouldThrow { - exchangePageNum(totalMinus.first, totalMinus.second, totalMinus.third) + exchangeValidPageNum(totalMinus.first, totalMinus.second, totalMinus.third) } shouldThrow { - exchangePageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) + exchangeValidPageNum(pageSizeZero.first, pageSizeZero.second, pageSizeZero.third) } shouldThrow { - exchangePageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) + exchangeValidPageNum(pageNumZero.first, pageNumZero.second, pageNumZero.third) } } } @@ -129,7 +129,7 @@ class UtilsTest : BehaviorSpec({ val pageNum = 3 Then("Should return pageNum itself") { - val resultPageNum = exchangePageNum(pageSize, pageNum, total) + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) resultPageNum shouldBe pageNum } } @@ -140,9 +140,20 @@ class UtilsTest : BehaviorSpec({ val pageNum = 15 Then("Should return last page number") { - val resultPageNum = exchangePageNum(pageSize, pageNum, total) + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) resultPageNum shouldBe 11 } } + + When("Given total count is zero") { + val pageSize = 10 + val total = 0L + val pageNum = 1 + + Then("Should return first page number") { + val resultPageNum = exchangeValidPageNum(pageSize, pageNum, total) + resultPageNum shouldBe 1 + } + } } }) 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 6e38fa5e..13256934 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 @@ -41,7 +41,8 @@ class ProfessorServiceTest( language = LanguageType.KO, name = "researchName", description = null, - postType = ResearchPostType.LABS + websiteURL = null, + postType = ResearchPostType.GROUPS ) var labEntity = LabEntity( language = LanguageType.KO, @@ -143,7 +144,8 @@ class ProfessorServiceTest( language = LanguageType.KO, name = "researchName", description = null, - postType = ResearchPostType.LABS + websiteURL = null, + postType = ResearchPostType.GROUPS ) val labEntity1 = LabEntity( language = LanguageType.KO, 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 76603b52..85d9be47 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 @@ -98,7 +98,8 @@ class ResearchSearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = null ) ) 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 c7e8e91d..cf631d44 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 @@ -50,6 +50,7 @@ class ResearchServiceTest( name = "name", postType = ResearchPostType.CENTERS, description = "description", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -101,6 +102,7 @@ class ResearchServiceTest( name = "name", postType = ResearchPostType.CENTERS, description = "description", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -121,6 +123,7 @@ class ResearchServiceTest( name = "name2", postType = ResearchPostType.GROUPS, description = "description2", + websiteURL = null, createdAt = null, modifiedAt = null, labs = null, @@ -198,7 +201,8 @@ class ResearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = null ) ) @@ -305,7 +309,8 @@ class ResearchServiceTest( language = LanguageType.KO, name = "research", postType = ResearchPostType.GROUPS, - description = null + description = null, + websiteURL = null ) )