From 7d970d48f5d8a6047f09aabe2ade94bc29dc329f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9A=B0=ED=98=81=EC=A4=80=20=28Logan=29?= Date: Sat, 7 Sep 2024 00:24:57 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20Research=20Group=20&=20Center=20?= =?UTF-8?q?=EC=96=B8=EC=96=B4=ED=8F=AC=ED=95=A8=20CRUD=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(#315)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: deprecated v1 controller * refactor: extract types in seperate package, remove unused attachment from research * feat: make lab's research as nullable * feat: add research language entity & repository * feat: add language & sealed dtos * feat: remove migrate methods & seperate update index methods * feat: add CRUD language considered methods * feat: add v2 reserach controller * fix: fix test compiling * test: fix professerservice test * refactor: fix ktlint formatting * review: research -> fetch lazy --- .../api/req/CreateResearchLanguageReqBody.kt | 44 ++ .../api/req/ModifyResearchLanguageReqBody.kt | 41 ++ .../api/res/ResearchSearchResElement.kt | 14 +- .../api/{ => v1}/ResearchController.kt | 78 +-- .../research/api/v2/ResearchController.kt | 147 +++++ .../core/research/database/LabEntity.kt | 2 +- .../core/research/database/ResearchEntity.kt | 25 +- .../database/ResearchLanguageEntity.kt | 21 + .../database/ResearchLanguageRepository.kt | 54 ++ .../research/database/ResearchPostType.kt | 8 - .../research/database/ResearchRepository.kt | 3 +- .../research/database/ResearchSearchEntity.kt | 9 +- .../csereal/core/research/dto/LabDto.kt | 2 +- .../csereal/core/research/dto/ResearchDto.kt | 4 +- .../core/research/dto/ResearchLanguageDto.kt | 71 +++ .../core/research/service/ResearchService.kt | 405 +++++++------- .../core/research/type/ResearchType.kt | 38 ++ .../attachment/database/AttachmentEntity.kt | 5 - .../attachment/service/AttachmentService.kt | 10 +- .../member/service/ProfessorServiceTest.kt | 71 +-- .../service/ResearchSearchServiceTest.kt | 3 +- .../reseach/service/ResearchServiceTest.kt | 512 +++++++----------- 22 files changed, 881 insertions(+), 686 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/CreateResearchLanguageReqBody.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/ModifyResearchLanguageReqBody.kt rename src/main/kotlin/com/wafflestudio/csereal/core/research/api/{ => v1}/ResearchController.kt (59%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/api/v2/ResearchController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageRepository.kt delete mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLanguageDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/research/type/ResearchType.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/CreateResearchLanguageReqBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/CreateResearchLanguageReqBody.kt new file mode 100644 index 00000000..327b8054 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/CreateResearchLanguageReqBody.kt @@ -0,0 +1,44 @@ +package com.wafflestudio.csereal.core.research.api.req + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.wafflestudio.csereal.core.research.type.ResearchType + +data class CreateResearchLanguageReqBody( + val ko: CreateResearchSealedReqBody, + val en: CreateResearchSealedReqBody +) { + fun valid() = ko.type == en.type + fun valid(type: ResearchType) = ko.valid(type) && en.valid(type) +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = CreateResearchGroupReqBody::class, names = ["GROUPS", "groups"]), + JsonSubTypes.Type(value = CreateResearchCenterReqBody::class, names = ["CENTERS", "centers"]) +) +sealed class CreateResearchSealedReqBody( + val type: ResearchType, + open val name: String, + open val description: String, + open val mainImageUrl: String? +) { + fun valid(type: ResearchType) = this.type == type +} + +data class CreateResearchGroupReqBody( + override val name: String, + override val description: String, + override val mainImageUrl: String? +) : CreateResearchSealedReqBody(ResearchType.GROUPS, name, description, mainImageUrl) + +data class CreateResearchCenterReqBody( + override val name: String, + override val description: String, + override val mainImageUrl: String?, + val websiteURL: String? +) : CreateResearchSealedReqBody(ResearchType.CENTERS, name, description, mainImageUrl) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/ModifyResearchLanguageReqBody.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/ModifyResearchLanguageReqBody.kt new file mode 100644 index 00000000..a0f1d760 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/req/ModifyResearchLanguageReqBody.kt @@ -0,0 +1,41 @@ +package com.wafflestudio.csereal.core.research.api.req + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.wafflestudio.csereal.core.research.type.ResearchType + +data class ModifyResearchLanguageReqBody( + val ko: ModifyResearchSealedReqBody, + val en: ModifyResearchSealedReqBody +) { + fun valid() = ko.type == en.type +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type" +) +@JsonSubTypes( + JsonSubTypes.Type(value = ModifyResearchGroupReqBody::class, names = ["GROUPS", "groups"]), + JsonSubTypes.Type(value = ModifyResearchCenterReqBody::class, names = ["CENTERS", "centers"]) +) +sealed class ModifyResearchSealedReqBody( + val type: ResearchType, + open val name: String, + open val description: String, + open val removeImage: Boolean +) + +data class ModifyResearchGroupReqBody( + override val name: String, + override val description: String, + override val removeImage: Boolean +) : ModifyResearchSealedReqBody(ResearchType.GROUPS, name, description, removeImage) + +data class ModifyResearchCenterReqBody( + override val name: String, + override val description: String, + override val removeImage: Boolean, + val websiteURL: String? +) : ModifyResearchSealedReqBody(ResearchType.CENTERS, name, description, removeImage) 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 a5874bfa..98e89c92 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,15 +3,14 @@ package com.wafflestudio.csereal.core.research.api.res import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.enums.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 +import com.wafflestudio.csereal.core.research.type.ResearchRelatedType data class ResearchSearchResElement( val id: Long, val language: String, val name: String, - val researchType: ResearchSearchType, + val researchType: ResearchRelatedType, val partialDescription: String, val boldStartIdx: Int, val boldEndIdx: Int @@ -36,10 +35,7 @@ data class ResearchSearchResElement( id = it.id, name = it.name, language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, - researchType = when (it.postType) { - ResearchPostType.GROUPS -> ResearchSearchType.RESEARCH_GROUP - ResearchPostType.CENTERS -> ResearchSearchType.RESEARCH_CENTER - }, + researchType = it.postType.ofResearchRelatedType(), partialDescription = partialDesc, boldStartIdx = startIdx ?: 0, boldEndIdx = startIdx?.plus(keyword.length) ?: 0 @@ -59,7 +55,7 @@ data class ResearchSearchResElement( id = it.id, name = it.name, language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, - researchType = ResearchSearchType.LAB, + researchType = ResearchRelatedType.LAB, partialDescription = partialDesc, boldStartIdx = startIdx ?: 0, boldEndIdx = startIdx?.plus(keyword.length) ?: 0 @@ -79,7 +75,7 @@ data class ResearchSearchResElement( id = it.id, name = it.name, language = it.language.let { ln -> LanguageType.makeLowercase(ln) }, - researchType = ResearchSearchType.CONFERENCE, + researchType = ResearchRelatedType.CONFERENCE, partialDescription = partialDesc, boldStartIdx = startIdx ?: 0, boldEndIdx = startIdx?.plus(keyword.length) ?: 0 diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/v1/ResearchController.kt similarity index 59% rename from src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/research/api/v1/ResearchController.kt index cc3b82fd..dca37d51 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/ResearchController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/v1/ResearchController.kt @@ -1,4 +1,4 @@ -package com.wafflestudio.csereal.core.research.api +package com.wafflestudio.csereal.core.research.api.v1 import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.common.enums.LanguageType @@ -10,54 +10,29 @@ 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.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/research") -@RestController +@RestController("ResearchControllerV1") +@Deprecated(message = "Use V2 API") class ResearchController( private val researchService: ResearchService, private val researchSearchService: ResearchSearchService ) { - @AuthenticatedStaff - @PostMapping - fun createResearchDetail( - @Valid - @RequestPart("request") - request: ResearchDto, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? - ): ResponseEntity { - return ResponseEntity.ok(researchService.createResearchDetail(request, mainImage, attachments)) - } - @GetMapping("/groups") fun readAllResearchGroups( @RequestParam(required = false, defaultValue = "ko") language: String ): ResponseEntity { - return ResponseEntity.ok(researchService.readAllResearchGroups(language)) + return ResponseEntity.ok(researchService.readAllResearchGroupsDeprecated(language)) } @GetMapping("/centers") fun readAllResearchCenters( @RequestParam(required = false, defaultValue = "ko") language: String ): ResponseEntity> { - return ResponseEntity.ok(researchService.readAllResearchCenters(language)) - } - - @AuthenticatedStaff - @PatchMapping("/{researchId}") - fun updateResearchDetail( - @PathVariable researchId: Long, - @Valid - @RequestPart("request") - request: ResearchDto, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? - ): ResponseEntity { - return ResponseEntity.ok(researchService.updateResearchDetail(researchId, request, mainImage, attachments)) + return ResponseEntity.ok(researchService.readAllResearchCentersDeprecated(language)) } @AuthenticatedStaff @@ -100,49 +75,6 @@ class ResearchController( return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) } - @Profile("!prod") - @PostMapping("/migrate") - fun migrateResearchDetail( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(researchService.migrateResearchDetail(requestList)) - } - - @Profile("!prod") - @PostMapping("/lab/migrate") - fun migrateLabs( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(researchService.migrateLabs(requestList)) - } - - @Profile("!prod") - @PatchMapping("/migrateImageAndAttachments/{researchId}") - fun migrateResearchDetailImageAndAttachments( - @PathVariable researchId: Long, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? - ): ResponseEntity { - return ResponseEntity.ok( - researchService.migrateResearchDetailImageAndAttachments( - researchId, - mainImage, - attachments - ) - ) - } - - @Profile("!prod") - @PatchMapping("/lab/migratePdf/{labId}") - fun migrateLabPdf( - @PathVariable labId: Long, - @RequestPart("pdf") pdf: MultipartFile? - ): ResponseEntity { - return ResponseEntity.ok( - researchService.migrateLabPdf(labId, pdf) - ) - } - @GetMapping("/search/top") fun searchTop( @RequestParam(required = true) keyword: String, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/api/v2/ResearchController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/v2/ResearchController.kt new file mode 100644 index 00000000..1b16fcca --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/api/v2/ResearchController.kt @@ -0,0 +1,147 @@ +package com.wafflestudio.csereal.core.research.api.v2 + +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.research.api.req.CreateResearchLanguageReqBody +import com.wafflestudio.csereal.core.research.api.req.ModifyResearchLanguageReqBody +import com.wafflestudio.csereal.core.research.dto.LabDto +import com.wafflestudio.csereal.core.research.dto.ResearchLanguageDto +import com.wafflestudio.csereal.core.research.dto.ResearchSealedDto +import com.wafflestudio.csereal.core.research.service.ResearchSearchService +import com.wafflestudio.csereal.core.research.service.ResearchService +import com.wafflestudio.csereal.core.research.type.ResearchType +import io.swagger.v3.oas.annotations.Parameter +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 + +@RequestMapping("/api/v2/research") +@RestController +class ResearchController( + private val researchService: ResearchService, + private val researchSearchService: ResearchSearchService +) { + @GetMapping("/{researchId:[0-9]+}") + fun readResearch( + @Positive + @PathVariable(required = true) + researchId: Long + ): ResearchLanguageDto { + return researchService.readResearchLanguage(researchId) + } + + @GetMapping("/{researchType:[a-z A-Z]+}") + fun readAllResearch( + @PathVariable(required = true) researchType: String, + @RequestParam(required = false, defaultValue = "ko") language: String + ): List { + val researchTypeEnum = ResearchType.fromJsonValue(researchType) + val languageEnum = LanguageType.makeStringToLanguageType(language) + return researchService.readAllResearch(languageEnum, researchTypeEnum) + } + + @AuthenticatedStaff + @PostMapping(consumes = ["multipart/form-data"]) + fun createResearchGroup( + @RequestPart("request") request: CreateResearchLanguageReqBody, + @RequestPart("mainImage") mainImage: MultipartFile? + ): ResearchLanguageDto = researchService.createResearchLanguage(request, mainImage) + + @AuthenticatedStaff + @PutMapping("/{koreanId}/{englishId}", consumes = ["multipart/form-data"]) + fun updateResearch( + @PathVariable @Positive + koreanId: Long, + @PathVariable @Positive + englishId: Long, + @RequestPart("request") request: ModifyResearchLanguageReqBody, + + @Parameter(description = "image 교체할 경우 업로드. Request Body의 removeImage 관계없이 변경됨.") + @RequestPart("newImage") + newImage: MultipartFile? + ): ResearchLanguageDto { + return researchService.updateResearchLanguage(koreanId, englishId, request, newImage) + } + + @AuthenticatedStaff + @DeleteMapping("/{koreanId}/{englishId}") + fun deleteResearch( + @PathVariable @Positive + koreanId: Long, + @PathVariable @Positive + englishId: Long + ) { + researchService.deleteResearchLanguage(koreanId, englishId) + } + + // TODO: Change to Language Unified API + @GetMapping("/labs") + fun readAllLabs( + @RequestParam(required = false, defaultValue = "ko") language: String + ): ResponseEntity> { + return ResponseEntity.ok(researchService.readAllLabs(language)) + } + + // TODO: Change to Language Unified API + @GetMapping("/lab/{labId}") + fun readLab( + @PathVariable labId: Long + ): ResponseEntity { + return ResponseEntity.ok(researchService.readLab(labId)) + } + +// @AuthenticatedStaff +// @PostMapping("/lab") +// fun createLab( +// @Valid +// @RequestPart("request") +// request: LabDto, +// @RequestPart("pdf") pdf: MultipartFile? +// ): ResponseEntity { +// return ResponseEntity.ok(researchService.createLab(request, pdf)) +// } +// +// +// // TODO: Change to Language Unified API +// @AuthenticatedStaff +// @PatchMapping("/lab/{labId}") +// fun updateLab( +// @PathVariable labId: Long, +// @Valid +// @RequestPart("request") +// request: LabUpdateRequest, +// @RequestPart("pdf") pdf: MultipartFile? +// ): ResponseEntity { +// return ResponseEntity.ok(researchService.updateLab(labId, request, pdf)) +// } + + @GetMapping("/search/top") + fun searchTop( + @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 + ) = 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, + @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/database/LabEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/LabEntity.kt index 6a77b365..b15ef8e7 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 @@ -27,7 +27,7 @@ class LabEntity( @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "research_id") - var research: ResearchEntity, + var research: ResearchEntity? = null, @Column(columnDefinition = "mediumText") var description: String?, 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 e7911d6f..40a18422 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 @@ -1,43 +1,38 @@ 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.enums.LanguageType import com.wafflestudio.csereal.core.research.dto.ResearchDto -import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity +import com.wafflestudio.csereal.core.research.type.ResearchType import com.wafflestudio.csereal.core.resource.mainImage.database.MainImageEntity import jakarta.persistence.* @Entity(name = "research") class ResearchEntity( @Enumerated(EnumType.STRING) - var postType: ResearchPostType, + val postType: ResearchType, @Enumerated(EnumType.STRING) - var language: LanguageType, + val language: LanguageType, var name: String, @Column(columnDefinition = "mediumText") var description: String?, - var websiteURL: String?, + var websiteURL: String? = null, - @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) - var labs: MutableList = mutableListOf(), + @OneToMany(mappedBy = "research", cascade = [CascadeType.PERSIST]) + var labs: MutableSet = mutableSetOf(), @OneToOne var mainImage: MainImageEntity? = null, - @OneToMany(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) - var attachments: MutableList = mutableListOf(), - @OneToOne(mappedBy = "research", cascade = [CascadeType.ALL], orphanRemoval = true) var researchSearch: ResearchSearchEntity? = null -) : BaseTimeEntity(), MainImageContentEntityType, AttachmentContentEntityType { +) : BaseTimeEntity(), MainImageContentEntityType { override fun bringMainImage() = mainImage - override fun bringAttachments() = attachments companion object { fun of(languageType: LanguageType, researchDto: ResearchDto): ResearchEntity { @@ -50,10 +45,4 @@ class ResearchEntity( ) } } - - fun updateWithoutLabImageAttachment(researchDto: ResearchDto) { - this.postType = researchDto.postType - this.name = researchDto.name - this.description = researchDto.description - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageEntity.kt new file mode 100644 index 00000000..3c26c5d2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageEntity.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.research.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.research.type.ResearchRelatedType +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated + +@Entity(name = "research_language") +class ResearchLanguageEntity( + @Column(nullable = false, unique = true) + val koreanId: Long, + + @Column(nullable = false, unique = true) + val englishId: Long, + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + val type: ResearchRelatedType +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageRepository.kt new file mode 100644 index 00000000..3f6017df --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchLanguageRepository.kt @@ -0,0 +1,54 @@ +package com.wafflestudio.csereal.core.research.database + +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.research.database.QResearchLanguageEntity.researchLanguageEntity +import com.wafflestudio.csereal.core.research.type.ResearchRelatedType +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +interface ResearchLanguageRepository : JpaRepository, ResearchLanguageCustomRepository { + fun existsByKoreanIdAndEnglishIdAndType(koreanId: Long, englishId: Long, type: ResearchRelatedType): Boolean + fun findByKoreanIdAndEnglishIdAndType( + koreanId: Long, + englishId: Long, + type: ResearchRelatedType + ): ResearchLanguageEntity? +} + +interface ResearchLanguageCustomRepository { + fun findResearchPairById(id: Long): Map? +} + +@Repository +class ResearchLanguageCustomRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : ResearchLanguageCustomRepository { + override fun findResearchPairById(id: Long): Map? { + val ko = QResearchEntity("ko") + val en = QResearchEntity("en") + + val tuple = queryFactory.select(ko, en) + .from(researchLanguageEntity) + .join(ko).on(researchLanguageEntity.koreanId.eq(ko.id)) + .join(en).on(researchLanguageEntity.englishId.eq(en.id)) + .where( + researchLanguageEntity.type.`in`( + listOf( + ResearchRelatedType.RESEARCH_GROUP, + ResearchRelatedType.RESEARCH_CENTER + ) + ), + researchLanguageEntity.koreanId.eq(id).or( + researchLanguageEntity.englishId.eq(id) + ) + ).fetchOne() + + return tuple?.let { + mapOf( + LanguageType.KO to it[ko]!!, + LanguageType.EN to it[en]!! + ) + } + } +} 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 deleted file mode 100644 index 4e5d6447..00000000 --- a/src/main/kotlin/com/wafflestudio/csereal/core/research/database/ResearchPostType.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.wafflestudio.csereal.core.research.database - -enum class ResearchPostType( - val krName: String -) { - GROUPS("연구 그룹"), - CENTERS("연구 센터"); -} 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 6a22d921..76038943 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,12 +1,13 @@ package com.wafflestudio.csereal.core.research.database import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.research.type.ResearchType import org.springframework.data.jpa.repository.JpaRepository interface ResearchRepository : JpaRepository { fun findByName(name: String): ResearchEntity? fun findAllByPostTypeAndLanguageOrderByName( - postType: ResearchPostType, + postType: ResearchType, 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 397c73c5..cf9cd6bb 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 @@ -68,7 +68,7 @@ class ResearchSearchEntity( lab.tel?.let { appendLine(it) } lab.acronym?.let { appendLine(it) } lab.youtube?.let { appendLine(it) } - appendLine(lab.research.name) + lab.research?.let { appendLine(it.name) } lab.description?.let { appendLine(cleanTextFromHtml(it)) } @@ -106,10 +106,3 @@ class ResearchSearchEntity( this.content = createContent(conference) } } - -enum class ResearchSearchType { - RESEARCH_GROUP, - RESEARCH_CENTER, - LAB, - CONFERENCE; -} 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 74ed33d7..7b69654b 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 @@ -30,7 +30,7 @@ data class LabDto( acronym = this.acronym, pdf = pdf, youtube = this.youtube, - group = this.research.name, + group = this.research?.name ?: "", description = this.description, websiteURL = this.websiteURL ) 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 77e6ffb4..352aef0f 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 @@ -2,13 +2,13 @@ package com.wafflestudio.csereal.core.research.dto import com.wafflestudio.csereal.common.enums.LanguageType import com.wafflestudio.csereal.core.research.database.ResearchEntity -import com.wafflestudio.csereal.core.research.database.ResearchPostType +import com.wafflestudio.csereal.core.research.type.ResearchType import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentResponse import java.time.LocalDateTime data class ResearchDto( val id: Long, - val postType: ResearchPostType, + val postType: ResearchType, val language: String, val name: String, val description: String?, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLanguageDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLanguageDto.kt new file mode 100644 index 00000000..3043258e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/dto/ResearchLanguageDto.kt @@ -0,0 +1,71 @@ +package com.wafflestudio.csereal.core.research.dto + +import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.research.database.ResearchEntity +import com.wafflestudio.csereal.core.research.type.ResearchType + +data class ResearchLanguageDto( + val ko: ResearchSealedDto, + val en: ResearchSealedDto +) { + fun valid() = ko.type == en.type + fun valid(researchType: ResearchType) = ko.valid(researchType) && en.valid(researchType) +} + +sealed class ResearchSealedDto( + val type: ResearchType, + open val id: Long, + open val language: LanguageType, + open val name: String, + open val description: String, + open val mainImageUrl: String? +) { + fun valid(researchType: ResearchType) = this.type == researchType + + companion object { + fun of(entity: ResearchEntity, imageUrl: String?) = when (entity.postType) { + ResearchType.GROUPS -> ResearchGroupDto.of(entity, imageUrl) + ResearchType.CENTERS -> ResearchCenterDto.of(entity, imageUrl) + } + } +} + +data class ResearchGroupDto( + override val id: Long, + override val language: LanguageType, + override val name: String, + override val description: String, + override val mainImageUrl: String?, + val labs: List +) : ResearchSealedDto(ResearchType.GROUPS, id, language, name, description, mainImageUrl) { + companion object { + fun of(entity: ResearchEntity, imageUrl: String?) = ResearchGroupDto( + id = entity.id, + language = entity.language, + name = entity.name, + description = entity.description!!, + mainImageUrl = imageUrl, + labs = entity.labs.map { ResearchLabResponse(it.id, it.name) } + ) + } +} + +data class ResearchCenterDto( + override val id: Long, + override val language: LanguageType, + override val name: String, + override val description: String, + override val mainImageUrl: String?, + val websiteURL: String? +) : ResearchSealedDto(ResearchType.CENTERS, id, language, name, description, mainImageUrl) { + companion object { + fun of(entity: ResearchEntity, imageUrl: String?) = ResearchCenterDto( + id = entity.id, + language = entity.language, + name = entity.name, + description = entity.description!!, + mainImageUrl = imageUrl, + websiteURL = entity.websiteURL + ) + } +} 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 64167802..f80cf6a9 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 @@ -1,12 +1,15 @@ 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.enums.LanguageType +import com.wafflestudio.csereal.common.properties.EndpointProperties import com.wafflestudio.csereal.common.utils.startsWithEnglish import com.wafflestudio.csereal.core.member.database.ProfessorRepository +import com.wafflestudio.csereal.core.research.api.req.* import com.wafflestudio.csereal.core.research.database.* import com.wafflestudio.csereal.core.research.dto.* +import com.wafflestudio.csereal.core.research.type.ResearchRelatedType +import com.wafflestudio.csereal.core.research.type.ResearchType import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.attachment.service.AttachmentService import com.wafflestudio.csereal.core.resource.mainImage.service.MainImageService @@ -16,39 +19,45 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface ResearchService { - fun createResearchDetail( - request: ResearchDto, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto - - fun readAllResearchGroups(language: String): ResearchGroupResponse - fun readAllResearchCenters(language: String): List - fun updateResearchDetail( + fun createResearchLanguage(req: CreateResearchLanguageReqBody, mainImage: MultipartFile?): ResearchLanguageDto + fun createResearch( + language: LanguageType, + request: CreateResearchSealedReqBody, + mainImage: MultipartFile? + ): ResearchSealedDto + + fun updateResearchLanguage( + koreanId: Long, + englishId: Long, + req: ModifyResearchLanguageReqBody, + updateImage: MultipartFile? + ): ResearchLanguageDto + + fun updateResearch( researchId: Long, - request: ResearchDto, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto + request: ModifyResearchSealedReqBody, + updateImage: MultipartFile? + ): ResearchSealedDto + + fun deleteResearchLanguage(koreanId: Long, englishId: Long) + fun deleteResearch(researchId: Long) + + fun readResearchLanguage(id: Long): ResearchLanguageDto + fun readAllResearch(language: LanguageType, type: ResearchType): List + + fun readAllResearchGroupsDeprecated(language: String): ResearchGroupResponse + fun readAllResearchCentersDeprecated(language: String): List fun createLab(request: LabDto, pdf: MultipartFile?): LabDto fun readAllLabs(language: String): List fun readLab(labId: Long): LabDto fun updateLab(labId: Long, request: LabUpdateRequest, pdf: MultipartFile?): LabDto - fun migrateResearchDetail(requestList: List): List - fun migrateLabs(requestList: List): List - fun migrateResearchDetailImageAndAttachments( - researchId: Long, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto - - fun migrateLabPdf(labId: Long, pdf: MultipartFile?): LabDto } @Service class ResearchServiceImpl( private val researchRepository: ResearchRepository, + private val researchLanguageRepository: ResearchLanguageRepository, private val labRepository: LabRepository, private val professorRepository: ProfessorRepository, private val mainImageService: MainImageService, @@ -56,44 +65,196 @@ class ResearchServiceImpl( private val endpointProperties: EndpointProperties ) : ResearchService { @Transactional - override fun createResearchDetail( - request: ResearchDto, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto { - val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newResearch = ResearchEntity.of(enumLanguageType, request) - - if (request.labs != null) { - for (lab in request.labs) { - val labEntity = labRepository.findByIdOrNull(lab.id) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") - newResearch.labs.add(labEntity) - labEntity.research = newResearch - } + override fun createResearchLanguage( + req: CreateResearchLanguageReqBody, + mainImage: MultipartFile? + ): ResearchLanguageDto { + if (!req.valid()) { + throw CserealException.Csereal400("두 언어의 research type이 일치하지 않습니다.") + } + + val ko = createResearch(LanguageType.KO, req.ko, mainImage) + val en = createResearch(LanguageType.EN, req.en, mainImage) + researchLanguageRepository.save( + ResearchLanguageEntity( + koreanId = ko.id, + englishId = en.id, + type = req.ko.type.ofResearchRelatedType() + ) + ) + + return ResearchLanguageDto(ko, en) + } + + @Transactional + override fun createResearch( + language: LanguageType, + request: CreateResearchSealedReqBody, + mainImage: MultipartFile? + ): ResearchSealedDto { + // Common fields + val newResearch = ResearchEntity( + postType = request.type, + language = language, + name = request.name, + description = request.description + ) + + // Type specific fields + when (request) { + is CreateResearchGroupReqBody -> {} + is CreateResearchCenterReqBody -> newResearch.websiteURL = request.websiteURL } + // Create Research Search Index + upsertResearchSearchIndex(newResearch) + + // Main Image if (mainImage != null) { mainImageService.uploadMainImage(newResearch, mainImage) } + val imageURL = mainImageService.createImageURL(newResearch.mainImage) + + return ResearchSealedDto.of( + researchRepository.save(newResearch), + imageURL + ) + } - if (attachments != null) { - attachmentService.uploadAllAttachments(newResearch, attachments) + @Transactional + override fun updateResearchLanguage( + koreanId: Long, + englishId: Long, + req: ModifyResearchLanguageReqBody, + updateImage: MultipartFile? + ): ResearchLanguageDto { + if (!req.valid()) { + throw CserealException.Csereal404("두 언어의 research type이 일치하지 않습니다.") } - newResearch.researchSearch = ResearchSearchEntity.create(newResearch) + val type = req.ko.type + if (!researchLanguageRepository.existsByKoreanIdAndEnglishIdAndType( + koreanId, + englishId, + type.ofResearchRelatedType() + ) + ) { + throw CserealException.Csereal404("해당 Research 언어 쌍을 찾을 수 없습니다.") + } - researchRepository.save(newResearch) + val koreanUpdatedDto = updateResearch(koreanId, req.ko, updateImage) + val englishUpdatedDto = updateResearch(englishId, req.en, updateImage) - val imageURL = mainImageService.createImageURL(newResearch.mainImage) - val attachmentResponses = - attachmentService.createAttachmentResponses(newResearch.attachments) + return ResearchLanguageDto(koreanUpdatedDto, englishUpdatedDto) + } + + @Transactional + override fun updateResearch( + researchId: Long, + request: ModifyResearchSealedReqBody, + updateImage: MultipartFile? + ): ResearchSealedDto { + val research = researchRepository.findByIdOrNull(researchId) + ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") + val originalName = research.name + + // Update common fields + research.apply { + name = request.name + description = request.description + } - return ResearchDto.of(newResearch, imageURL, attachmentResponses) + // Update type specific fields + when (request) { + is ModifyResearchGroupReqBody -> {} + is ModifyResearchCenterReqBody -> { + research.websiteURL = request.websiteURL + } + } + + // Update image + // remove old image + if (research.mainImage != null && (request.removeImage || updateImage != null)) { + mainImageService.removeImage(research.mainImage!!) + research.mainImage = null + } + // upload new image + updateImage?.let { + mainImageService.uploadMainImage(research, it) + } + val imageURL = mainImageService.createImageURL(research.mainImage) + + // update search index + upsertResearchSearchIndex(research) + + // upsert labs in research group if name changed + if (originalName != research.name) { + research.labs.forEach { + upsertLabSearchIndex(it) + } + } + + return ResearchSealedDto.of(research, imageURL) + } + + @Transactional + override fun deleteResearchLanguage(koreanId: Long, englishId: Long) { + val researchLanguage = researchLanguageRepository.findByKoreanIdAndEnglishIdAndType( + koreanId, + englishId, + ResearchRelatedType.RESEARCH_GROUP + ) ?: researchLanguageRepository.findByKoreanIdAndEnglishIdAndType( + koreanId, + englishId, + ResearchRelatedType.RESEARCH_CENTER + ) ?: throw CserealException.Csereal404("해당 Research 언어 쌍을 찾을 수 없습니다.") + + deleteResearch(koreanId) + deleteResearch(englishId) + researchLanguageRepository.delete(researchLanguage) + } + + @Transactional + override fun deleteResearch(researchId: Long) { + val research = researchRepository.findByIdOrNull(researchId) + ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") + + research.mainImage?.let { + mainImageService.removeImage(it) + } + + research.labs.forEach { + it.research = null + } + + // update search index to remove research + research.labs.forEach { + upsertLabSearchIndex(it) + } + + researchRepository.delete(research) } @Transactional(readOnly = true) - override fun readAllResearchGroups(language: String): ResearchGroupResponse { + override fun readResearchLanguage(id: Long): ResearchLanguageDto { + val researchMap = researchLanguageRepository.findResearchPairById(id) + ?: throw CserealException.Csereal404("해당 Research 언어 쌍을 찾을 수 없습니다.(id=$id)") + + val ko = researchMap[LanguageType.KO]!! + val en = researchMap[LanguageType.EN]!! + return ResearchLanguageDto( + ResearchSealedDto.of(ko, mainImageService.createImageURL(ko.mainImage)), + ResearchSealedDto.of(en, mainImageService.createImageURL(en.mainImage)) + ) + } + + @Transactional(readOnly = true) + override fun readAllResearch(language: LanguageType, type: ResearchType): List = + researchRepository.findAllByPostTypeAndLanguageOrderByName(type, language) + .map { ResearchSealedDto.of(it, mainImageService.createImageURL(it.mainImage)) } + + @Transactional(readOnly = true) + override fun readAllResearchGroupsDeprecated(language: String): ResearchGroupResponse { // Todo: description 수정 필요 val description = "세계가 주목하는 컴퓨터공학부의 많은 교수들은 ACM, IEEE 등 " + "세계적인 컴퓨터관련 주요 학회에서 국제학술지 편집위원, 국제학술회의 위원장, " + @@ -104,97 +265,37 @@ class ResearchServiceImpl( val enumLanguageType = LanguageType.makeStringToLanguageType(language) val researchGroups = researchRepository.findAllByPostTypeAndLanguageOrderByName( - ResearchPostType.GROUPS, + ResearchType.GROUPS, enumLanguageType ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) + ResearchDto.of(it, imageURL, emptyList()) } return ResearchGroupResponse(description, researchGroups) } @Transactional(readOnly = true) - override fun readAllResearchCenters(language: String): List { + override fun readAllResearchCentersDeprecated(language: String): List { val enumLanguageType = LanguageType.makeStringToLanguageType(language) val researchCenters = researchRepository.findAllByPostTypeAndLanguageOrderByName( - ResearchPostType.CENTERS, + ResearchType.CENTERS, enumLanguageType ).map { val imageURL = mainImageService.createImageURL(it.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(it.attachments) - - ResearchDto.of(it, imageURL, attachmentResponses) + ResearchDto.of(it, imageURL, emptyList()) } return researchCenters } - @Transactional - override fun updateResearchDetail( - researchId: Long, - request: ResearchDto, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto { - val research = researchRepository.findByIdOrNull(researchId) - ?: throw CserealException.Csereal404("해당 게시글을 찾을 수 없습니다.(researchId=$researchId)") - - if (request.labs != null) { - for (lab in request.labs) { - val labEntity = labRepository.findByIdOrNull(lab.id) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.(labId=${lab.id})") - } - - val oldLabs = research.labs.map { it.id } - - val labsToRemove = oldLabs - request.labs.map { it.id } - val labsToAdd = request.labs.map { it.id } - oldLabs - - research.labs.removeIf { it.id in labsToRemove } - - for (labsToAddId in labsToAdd) { - val lab = labRepository.findByIdOrNull(labsToAddId)!! - research.labs.add(lab) - lab.research = research - } - } - - if (mainImage != null) { - mainImageService.uploadMainImage(research, mainImage) - } else { - research.mainImage = null - } - - if (attachments != null) { - research.attachments.clear() - attachmentService.uploadAllAttachments(research, attachments) - } else { - research.attachments.clear() - } - - val imageURL = mainImageService.createImageURL(research.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(research.attachments) - - research.updateWithoutLabImageAttachment(request) - - research.researchSearch?.update(research) - ?: let { - research.researchSearch = ResearchSearchEntity.create(research) - } - - return ResearchDto.of(research, imageURL, attachmentResponses) - } - @Transactional override fun createLab(request: LabDto, pdf: MultipartFile?): LabDto { - val researchGroup = researchRepository.findByName(request.group!!) + val researchGroup = researchRepository.findByName(request.group) ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupId = ${request.group})") - if (researchGroup.postType != ResearchPostType.GROUPS) { + if (researchGroup.postType != ResearchType.GROUPS) { throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") } @@ -310,80 +411,16 @@ class ResearchServiceImpl( } @Transactional - override fun migrateResearchDetail(requestList: List): List { - val list = mutableListOf() - for (request in requestList) { - val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newResearch = ResearchEntity.of(enumLanguageType, request) - - newResearch.researchSearch = ResearchSearchEntity.create(newResearch) - - researchRepository.save(newResearch) - - list.add(ResearchDto.of(newResearch, null, listOf())) - } - - return list - } - - @Transactional - override fun migrateLabs(requestList: List): List { - val list = mutableListOf() - for (request in requestList) { - val researchGroup = researchRepository.findByName(request.group) - ?: throw CserealException.Csereal404("해당 연구그룹을 찾을 수 없습니다.(researchGroupName = ${request.group})") - - if (researchGroup.postType != ResearchPostType.GROUPS) { - throw CserealException.Csereal404("해당 게시글은 연구그룹이어야 합니다.") - } - - val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - val newLab = LabEntity.of(enumLanguageType, request, researchGroup) - - newLab.researchSearch = ResearchSearchEntity.create(newLab) - - labRepository.save(newLab) - - list.add(LabDto.of(newLab, null)) + fun upsertResearchSearchIndex(research: ResearchEntity) { + research.researchSearch?.update(research) ?: let { + research.researchSearch = ResearchSearchEntity.create(research) } - return list } @Transactional - override fun migrateResearchDetailImageAndAttachments( - researchId: Long, - mainImage: MultipartFile?, - attachments: List? - ): ResearchDto { - val researchDetail = researchRepository.findByIdOrNull(researchId) - ?: throw CserealException.Csereal404("해당 연구내용을 찾을 수 없습니다.") - - if (mainImage != null) { - mainImageService.uploadMainImage(researchDetail, mainImage) + fun upsertLabSearchIndex(lab: LabEntity) { + lab.researchSearch?.update(lab) ?: let { + lab.researchSearch = ResearchSearchEntity.create(lab) } - - if (attachments != null) { - attachmentService.uploadAllAttachments(researchDetail, attachments) - } - - val imageURL = mainImageService.createImageURL(researchDetail.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(researchDetail.attachments) - - return ResearchDto.of(researchDetail, imageURL, attachmentResponses) - } - - @Transactional - override fun migrateLabPdf(labId: Long, pdf: MultipartFile?): LabDto { - val lab = labRepository.findByIdOrNull(labId) - ?: throw CserealException.Csereal404("해당 연구실을 찾을 수 없습니다.") - - if (pdf != null) { - val attachmentDto = attachmentService.uploadAttachmentInLabEntity(lab, pdf) - } - - val attachmentResponse = - attachmentService.createOneAttachmentResponse(lab.pdf) - - return LabDto.of(lab, attachmentResponse) } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/research/type/ResearchType.kt b/src/main/kotlin/com/wafflestudio/csereal/core/research/type/ResearchType.kt new file mode 100644 index 00000000..866a6cd2 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/research/type/ResearchType.kt @@ -0,0 +1,38 @@ +package com.wafflestudio.csereal.core.research.type + +import com.wafflestudio.csereal.common.CserealException + +enum class ResearchRelatedType { + RESEARCH_GROUP, + RESEARCH_CENTER, + LAB, + CONFERENCE; + + fun ofResearchType() = when (this) { + RESEARCH_GROUP -> ResearchType.GROUPS + RESEARCH_CENTER -> ResearchType.CENTERS + else -> throw IllegalArgumentException("ResearchRelatedType $this does not have corresponding ResearchType") + } +} + +enum class ResearchType( + val krName: String +) { + GROUPS("연구 그룹"), + CENTERS("연구 센터"); + + fun ofResearchRelatedType() = when (this) { + GROUPS -> ResearchRelatedType.RESEARCH_GROUP + CENTERS -> ResearchRelatedType.RESEARCH_CENTER + } + + companion object { + fun fromJsonValue(value: String) = try { + ResearchType.valueOf( + value.uppercase().replace('-', '_') + ) + } catch (e: Exception) { + throw CserealException.Csereal404("잘못된 Research Type이 주어졌습니다.") + } + } +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt index 567e0462..b0169dcc 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/resource/attachment/database/AttachmentEntity.kt @@ -8,7 +8,6 @@ import com.wafflestudio.csereal.core.academics.database.ScholarshipEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity -import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.seminar.database.SeminarEntity import jakarta.persistence.* @@ -50,10 +49,6 @@ class AttachmentEntity( @JoinColumn(name = "lab_id") var lab: LabEntity? = null, - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "research_id") - var research: ResearchEntity? = null, - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "scholarship_id") var scholarship: ScholarshipEntity? = null 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 d39fc1d6..3e676eac 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 @@ -8,7 +8,6 @@ import com.wafflestudio.csereal.core.academics.database.AcademicsEntity import com.wafflestudio.csereal.core.news.database.NewsEntity import com.wafflestudio.csereal.core.notice.database.NoticeEntity import com.wafflestudio.csereal.core.research.database.LabEntity -import com.wafflestudio.csereal.core.research.database.ResearchEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentEntity import com.wafflestudio.csereal.core.resource.attachment.database.AttachmentRepository import com.wafflestudio.csereal.core.resource.attachment.dto.AttachmentDto @@ -80,7 +79,7 @@ class AttachmentServiceImpl( @Transactional override fun uploadAllAttachments( - contentEntity: AttachmentContentEntityType, + contentEntityType: AttachmentContentEntityType, requestAttachments: List ): List { Files.createDirectories(Paths.get(path)) @@ -101,7 +100,7 @@ class AttachmentServiceImpl( size = requestAttachment.size ) - connectAttachmentToEntity(contentEntity, attachment) + connectAttachmentToEntity(contentEntityType, attachment) //Todo: update에서도 uploadAllAttachments 사용, 이에 따른 attachmentsOrder에 대한 조정 필요 attachmentRepository.save(attachment) @@ -212,11 +211,6 @@ class AttachmentServiceImpl( contentEntity.attachments.add(attachment) attachment.academics = contentEntity } - - is ResearchEntity -> { - contentEntity.attachments.add(attachment) - attachment.research = contentEntity - } } } } 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 34da731b..be4442d5 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 @@ -6,7 +6,8 @@ import com.wafflestudio.csereal.core.member.api.req.ModifyProfessorReqBody import com.wafflestudio.csereal.core.member.database.MemberSearchRepository import com.wafflestudio.csereal.core.member.database.ProfessorRepository import com.wafflestudio.csereal.core.member.database.ProfessorStatus -import com.wafflestudio.csereal.core.research.database.* +import com.wafflestudio.csereal.core.research.database.LabEntity +import com.wafflestudio.csereal.core.research.database.LabRepository import io.kotest.core.spec.style.BehaviorSpec import io.kotest.extensions.spring.SpringTestExtension import io.kotest.extensions.spring.SpringTestLifecycleMode @@ -25,26 +26,18 @@ class ProfessorServiceTest( private val professorService: ProfessorService, private val professorRepository: ProfessorRepository, private val labRepository: LabRepository, - private val memberSearchRepository: MemberSearchRepository, - private val researchRepository: ResearchRepository + private val memberSearchRepository: MemberSearchRepository ) : BehaviorSpec({ extensions(SpringTestExtension(SpringTestLifecycleMode.Root)) afterContainer { professorRepository.deleteAll() - researchRepository.deleteAll() + labRepository.deleteAll() } Given("이미지 없는 교수를 생성하려고 할 때") { val date = LocalDate.now() - val researchEntity = ResearchEntity( - language = LanguageType.KO, - name = "researchName", - description = null, - websiteURL = null, - postType = ResearchPostType.GROUPS - ) var labEntity = LabEntity( language = LanguageType.KO, name = "labName", @@ -53,11 +46,8 @@ class ProfessorServiceTest( acronym = null, youtube = null, description = null, - websiteURL = null, - research = researchEntity + websiteURL = null ) - researchEntity.labs.add(labEntity) - researchRepository.save(researchEntity) labEntity = labRepository.save(labEntity) val professorCreateReq = CreateProfessorReqBody( @@ -139,37 +129,30 @@ class ProfessorServiceTest( Given("생성되어 있는 간단한 교수에 대하여") { val date = LocalDate.now() - val researchEntity = ResearchEntity( - language = LanguageType.KO, - name = "researchName", - description = null, - websiteURL = null, - postType = ResearchPostType.GROUPS - ) - val labEntity1 = LabEntity( - language = LanguageType.KO, - name = "labName1", - location = null, - tel = null, - acronym = null, - youtube = null, - description = null, - websiteURL = null, - research = researchEntity + val labEntity1 = labRepository.save( + LabEntity( + language = LanguageType.KO, + name = "labName1", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null + ) ) - val labEntity2 = LabEntity( - language = LanguageType.KO, - name = "labName2", - location = null, - tel = null, - acronym = null, - youtube = null, - description = null, - websiteURL = null, - research = researchEntity + val labEntity2 = labRepository.save( + LabEntity( + language = LanguageType.KO, + name = "labName2", + location = null, + tel = null, + acronym = null, + youtube = null, + description = null, + websiteURL = null + ) ) - researchEntity.labs.addAll(listOf(labEntity1, labEntity2)) - researchRepository.save(researchEntity) val createdProfessorDto = professorService.createProfessor( 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 2229fd99..8f024dcd 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 @@ -11,6 +11,7 @@ import com.wafflestudio.csereal.core.research.dto.LabDto import com.wafflestudio.csereal.core.research.dto.LabProfessorResponse import com.wafflestudio.csereal.core.research.service.ResearchSearchService import com.wafflestudio.csereal.core.research.service.ResearchService +import com.wafflestudio.csereal.core.research.type.ResearchType import io.kotest.core.spec.style.BehaviorSpec import io.kotest.extensions.spring.SpringTestExtension import io.kotest.extensions.spring.SpringTestLifecycleMode @@ -96,7 +97,7 @@ class ResearchSearchServiceTest( ResearchEntity( language = LanguageType.KO, name = "research", - postType = ResearchPostType.GROUPS, + postType = ResearchType.GROUPS, 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 171650a1..c941b4fc 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,20 +1,19 @@ package com.wafflestudio.csereal.core.reseach.service -import com.wafflestudio.csereal.common.enums.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 -import com.wafflestudio.csereal.core.research.database.* -import com.wafflestudio.csereal.core.research.dto.LabDto -import com.wafflestudio.csereal.core.research.dto.LabProfessorResponse -import com.wafflestudio.csereal.core.research.dto.LabUpdateRequest -import com.wafflestudio.csereal.core.research.dto.ResearchDto +import com.wafflestudio.csereal.core.research.api.req.* +import com.wafflestudio.csereal.core.research.database.LabRepository +import com.wafflestudio.csereal.core.research.database.ResearchLanguageRepository +import com.wafflestudio.csereal.core.research.database.ResearchRepository +import com.wafflestudio.csereal.core.research.database.ResearchSearchRepository +import com.wafflestudio.csereal.core.research.dto.ResearchLanguageDto +import com.wafflestudio.csereal.core.research.dto.ResearchSealedDto import com.wafflestudio.csereal.core.research.service.ResearchService +import com.wafflestudio.csereal.core.research.type.ResearchRelatedType import io.kotest.core.spec.style.BehaviorSpec import io.kotest.extensions.spring.SpringTestExtension import io.kotest.extensions.spring.SpringTestLifecycleMode 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 @@ -25,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional @Transactional class ResearchServiceTest( private val researchService: ResearchService, + private val researchLanguageRepository: ResearchLanguageRepository, private val professorRepository: ProfessorRepository, private val labRepository: LabRepository, private val researchRepository: ResearchRepository, @@ -37,368 +37,234 @@ class ResearchServiceTest( afterSpec { professorRepository.deleteAll() + researchLanguageRepository.deleteAll() researchRepository.deleteAll() labRepository.deleteAll() researchSearchRepository.deleteAll() } - // Research - Given("간단한 Research를 생성하려고 할 때") { - val researchDto = ResearchDto( - id = -1, - language = "ko", - name = "name", - postType = ResearchPostType.CENTERS, - description = "description", - websiteURL = null, - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null + // TODO: Add edge test cases + // TODO: Add search index test cases + Given("Create Research Center Request Body") { + val koCreateResearchCenterReqBody = CreateResearchCenterReqBody( + name = "한국어 연구소", + description = "한국어 연구소입니다.", + mainImageUrl = null, + websiteURL = "https://www.koreanlab.com" ) - When("Research를 생성한다면") { - val createdResearchDto = researchService.createResearchDetail( - researchDto, - null, - null - ) - - Then("Research가 생성되어야 한다") { - val research = researchRepository.findByIdOrNull(createdResearchDto.id) - research shouldNotBe null - researchRepository.count() shouldBe 1 - } + val enCreateResearchCenterReqBody = CreateResearchCenterReqBody( + name = "English Research Center", + description = "This is English Research Center.", + mainImageUrl = null, + websiteURL = "https://www.englishlab.com" + ) - Then("생성된 Research의 내용이 Dto와 동일해야 한다.") { - val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! - research.name shouldBe researchDto.name - research.postType shouldBe researchDto.postType - research.description shouldBe researchDto.description - } + val createResearchCenterReqBody = CreateResearchLanguageReqBody( + ko = koCreateResearchCenterReqBody, + en = enCreateResearchCenterReqBody + ) - Then("검색 엔티티가 생성되어야 한다.") { - val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! - val researchSearch = research.researchSearch - researchSearch shouldNotBe null - researchSearch!!.language shouldBe LanguageType.KO - - researchSearch!!.content shouldBe - """ - name - 연구 센터 - description - - """.trimIndent() + When("Create Research Center") { + val researchCenter = researchService.createResearchLanguage(createResearchCenterReqBody, null) + + Then("Research Center should be created") { + val pair = researchLanguageRepository.findAll() + .also { it.size shouldBe 1 } + pair[0].type shouldBe ResearchRelatedType.RESEARCH_CENTER + + val (koId, enId) = pair[0].koreanId to pair[0].englishId + val koResearchCenter = researchRepository.findByIdOrNull(koId)!! + val enResearchCenter = researchRepository.findByIdOrNull(enId)!! + ResearchLanguageDto( + ko = ResearchSealedDto.of(koResearchCenter, null), + en = ResearchSealedDto.of(enResearchCenter, null) + ) shouldBe researchCenter } } } - Given("간단한 Research를 수정하려고 할 때") { - val researchDto = ResearchDto( - id = -1, - language = "ko", - name = "name", - postType = ResearchPostType.CENTERS, - description = "description", - websiteURL = null, - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null + Given("Create Research Group Request Body") { + val koCreateResearchGroupReqBody = CreateResearchGroupReqBody( + name = "한국어 연구 그룹", + description = "한국어 연구 그룹입니다.", + mainImageUrl = null ) - val createdResearchDto = researchService.createResearchDetail( - researchDto, - null, - null + val enCreateResearchGroupReqBody = CreateResearchGroupReqBody( + name = "English Research Group", + description = "This is English Research Group.", + mainImageUrl = null ) - When("Research를 수정한다면") { - val researchUpdateRequest = ResearchDto( - id = createdResearchDto.id, - language = "ko", - name = "name2", - postType = ResearchPostType.GROUPS, - description = "description2", - websiteURL = null, - createdAt = null, - modifiedAt = null, - labs = null, - imageURL = null, - attachments = null - ) - - researchService.updateResearchDetail( - createdResearchDto.id, - researchUpdateRequest, - null, - null - ) - - Then("Research가 수정되어야 한다") { - val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! - research.name shouldBe researchUpdateRequest.name - research.postType shouldBe researchUpdateRequest.postType - research.description shouldBe researchUpdateRequest.description - } + val createResearchGroupReqBody = CreateResearchLanguageReqBody( + ko = koCreateResearchGroupReqBody, + en = enCreateResearchGroupReqBody + ) - Then("검색 엔티티가 수정되어야 한다.") { - val research = researchRepository.findByIdOrNull(createdResearchDto.id)!! - val researchSearch = research.researchSearch - researchSearch shouldNotBe null - - researchSearch!!.content shouldBe - """ - name2 - 연구 그룹 - description2 - - """.trimIndent() + When("Create Research Group") { + val researchGroup = researchService.createResearchLanguage(createResearchGroupReqBody, null) + + Then("Research Group should be created") { + val pair = researchLanguageRepository.findAll() + .also { it.size shouldBe 1 } + pair[0].type shouldBe ResearchRelatedType.RESEARCH_GROUP + + val (koId, enId) = pair[0].koreanId to pair[0].englishId + val koResearchGroup = researchRepository.findByIdOrNull(koId)!! + val enResearchGroup = researchRepository.findByIdOrNull(enId)!! + ResearchLanguageDto( + ko = ResearchSealedDto.of(koResearchGroup, null), + en = ResearchSealedDto.of(enResearchGroup, null) + ) shouldBe researchGroup } } } - // Lab - Given("pdf 없는 Lab을 생성하려고 할 때") { - // Save professors - val professor1 = professorRepository.save( - ProfessorEntity( - language = LanguageType.KO, - name = "professor1", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null - ) - ) - val professor2 = professorRepository.save( - ProfessorEntity( - language = LanguageType.KO, - name = "professor2", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null - ) + Given("Research Center Exists") { + val koCreateResearchCenterReqBody = CreateResearchCenterReqBody( + name = "한국어 연구소", + description = "한국어 연구소입니다.", + mainImageUrl = null, + websiteURL = "https://www.koreanlab.com" ) - // Save research - val research = researchRepository.save( - ResearchEntity( - language = LanguageType.KO, - name = "research", - postType = ResearchPostType.GROUPS, - description = null, - websiteURL = null - ) + val enCreateResearchCenterReqBody = CreateResearchCenterReqBody( + name = "English Research Center", + description = "This is English Research Center.", + mainImageUrl = null, + websiteURL = "https://www.englishlab.com" ) - val labDto = LabDto( - id = -1, - language = "ko", - name = "name", - professors = listOf( - LabProfessorResponse(professor1.id, professor1.name), - LabProfessorResponse(professor2.id, professor2.name) - ), - acronym = "acronym", - description = "description", - group = "research", - pdf = null, - location = "location", - tel = "tel", - websiteURL = "websiteURL", - youtube = "youtube" + val createResearchCenterReqBody = CreateResearchLanguageReqBody( + ko = koCreateResearchCenterReqBody, + en = enCreateResearchCenterReqBody ) - When("Lab을 생성한다면") { - val createdLabDto = researchService.createLab(labDto, null) + val researchCenter = researchService.createResearchLanguage(createResearchCenterReqBody, null) - Then("Lab이 생성되어야 한다") { - val lab = labRepository.findByIdOrNull(createdLabDto.id) - lab shouldNotBe null - labRepository.count() shouldBe 1 - } + When("Update Research Center") { + val koUpdateResearchCenterReqBody = ModifyResearchCenterReqBody( + name = "한국어 연구소 수정", + description = "한국어 연구소입니다. 수정", + websiteURL = "https://www.koreanlabbbb.com", + removeImage = false + ) - Then("생성된 Lab의 내용이 Dto와 동일해야 한다.") { - val lab = labRepository.findByIdOrNull(createdLabDto.id)!! - lab.name shouldBe labDto.name - lab.acronym shouldBe labDto.acronym - lab.description shouldBe labDto.description - lab.location shouldBe labDto.location - lab.tel shouldBe labDto.tel - lab.websiteURL shouldBe labDto.websiteURL - lab.youtube shouldBe labDto.youtube - lab.research shouldBe research - lab.professors shouldBe mutableSetOf(professor1, professor2) + val enUpdateResearchCenterReqBody = ModifyResearchCenterReqBody( + name = "English Research Center Update", + description = "This is English Research Center. Update", + websiteURL = "https://www.englishlabbbb.com", + removeImage = false + ) + + val updateResearchCenterReqBody = ModifyResearchLanguageReqBody( + ko = koUpdateResearchCenterReqBody, + en = enUpdateResearchCenterReqBody + ) + + val modifiedResearchCenter = researchService.updateResearchLanguage( + researchCenter.ko.id, + researchCenter.en.id, + updateResearchCenterReqBody, + null + ) + + Then("Research Center should be updated") { + val pair = researchLanguageRepository.findAll() + .also { it.size shouldBe 1 } + pair[0].type shouldBe ResearchRelatedType.RESEARCH_CENTER + val (koId, enId) = pair[0].koreanId to pair[0].englishId + koId shouldBe researchCenter.ko.id + enId shouldBe researchCenter.en.id + + val koResearchCenter = researchRepository.findByIdOrNull(koId)!! + val enResearchCenter = researchRepository.findByIdOrNull(enId)!! + ResearchLanguageDto( + ko = ResearchSealedDto.of(koResearchCenter, null), + en = ResearchSealedDto.of(enResearchCenter, null) + ) shouldBe modifiedResearchCenter } + } + + When("Delete Research Center") { + researchService.deleteResearchLanguage(researchCenter.ko.id, researchCenter.en.id) - Then("검색 엔티티가 생성되어야 한다.") { - val lab = labRepository.findByIdOrNull(createdLabDto.id)!! - val researchSearch = lab.researchSearch - researchSearch shouldNotBe null - researchSearch!!.language shouldBe LanguageType.KO - - researchSearch!!.content shouldBe - """ - name - professor1 - professor2 - location - tel - acronym - youtube - research - description - websiteURL - - """.trimIndent() + Then("Research Center should be deleted") { + researchLanguageRepository.findAll() shouldBe emptyList() + researchRepository.findAll() shouldBe emptyList() } } } - Given("간단한 Lab을 수정할 경우") { - // Save professors - val professor1 = professorRepository.save( - ProfessorEntity( - language = LanguageType.KO, - name = "professor1", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null - ) - ) - val professor2 = professorRepository.save( - ProfessorEntity( - language = LanguageType.KO, - name = "professor2", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null - ) + Given("Research Group Exists") { + val koCreateResearchGroupReqBody = CreateResearchGroupReqBody( + name = "한국어 연구 그룹", + description = "한국어 연구 그룹입니다.", + mainImageUrl = null ) - // Save research - val research = researchRepository.save( - ResearchEntity( - language = LanguageType.KO, - name = "research", - postType = ResearchPostType.GROUPS, - description = null, - websiteURL = null - ) + val enCreateResearchGroupReqBody = CreateResearchGroupReqBody( + name = "English Research Group", + description = "This is English Research Group.", + mainImageUrl = null ) - // Save lab - val labDto = LabDto( - id = -1, - language = "ko", - name = "name", - professors = listOf( - LabProfessorResponse(professor1.id, professor1.name), - LabProfessorResponse(professor2.id, professor2.name) - ), - acronym = "acronym", - description = "description", - group = "research", - pdf = null, - location = "location", - tel = "tel", - websiteURL = "websiteURL", - youtube = "youtube" + val createResearchGroupReqBody = CreateResearchLanguageReqBody( + ko = koCreateResearchGroupReqBody, + en = enCreateResearchGroupReqBody ) - val createdLabDto = researchService.createLab(labDto, null) - val createdLab = labRepository.findByIdOrNull(createdLabDto.id)!! - - When("pdf를 제외하고 Lab을 수정한다면") { - val professor3 = professorRepository.save( - ProfessorEntity( - language = LanguageType.KO, - name = "professor3", - status = ProfessorStatus.ACTIVE, - academicRank = "professor", - email = null, - fax = null, - office = null, - phone = null, - website = null, - startDate = null, - endDate = null - ) + val researchGroup = researchService.createResearchLanguage(createResearchGroupReqBody, null) + + When("Update Research Group") { + val koUpdateResearchGroupReqBody = ModifyResearchGroupReqBody( + name = "한국어 연구 그룹 수정", + description = "한국어 연구 그룹입니다. 수정", + removeImage = false + ) + + val enUpdateResearchGroupReqBody = ModifyResearchGroupReqBody( + name = "English Research Group Update", + description = "This is English Research Group. Update", + removeImage = false ) - val labUpdateRequest = LabUpdateRequest( - name = "name2", - professorIds = listOf(professor1.id, professor3.id), - acronym = "acronym2", - description = "description2", - location = "location2", - tel = "tel2", - websiteURL = "websiteURL2", - youtube = "youtube2", - pdfModified = false + val updateResearchGroupReqBody = ModifyResearchLanguageReqBody( + ko = koUpdateResearchGroupReqBody, + en = enUpdateResearchGroupReqBody ) - researchService.updateLab(createdLab.id, labUpdateRequest, null) - - Then("Lab이 수정되어야 한다.") { - val lab = labRepository.findByIdOrNull(createdLab.id)!! - lab.name shouldBe labUpdateRequest.name - lab.acronym shouldBe labUpdateRequest.acronym - lab.description shouldBe labUpdateRequest.description - lab.location shouldBe labUpdateRequest.location - lab.tel shouldBe labUpdateRequest.tel - lab.websiteURL shouldBe labUpdateRequest.websiteURL - lab.youtube shouldBe labUpdateRequest.youtube - lab.research shouldBe research - lab.professors shouldBe mutableSetOf(professor1, professor3) + val modifiedResearchGroup = researchService.updateResearchLanguage( + researchGroup.ko.id, + researchGroup.en.id, + updateResearchGroupReqBody, + null + ) + + Then("Research Group should be updated") { + val pair = researchLanguageRepository.findAll() + .also { it.size shouldBe 1 } + pair[0].type shouldBe ResearchRelatedType.RESEARCH_GROUP + val (koId, enId) = pair[0].koreanId to pair[0].englishId + koId shouldBe researchGroup.ko.id + enId shouldBe researchGroup.en.id + + val koResearchGroup = researchRepository.findByIdOrNull(koId)!! + val enResearchGroup = researchRepository.findByIdOrNull(enId)!! + ResearchLanguageDto( + ko = ResearchSealedDto.of(koResearchGroup, null), + en = ResearchSealedDto.of(enResearchGroup, null) + ) shouldBe modifiedResearchGroup } + } + + When("Delete Research Group") { + researchService.deleteResearchLanguage(researchGroup.ko.id, researchGroup.en.id) - Then("검색 엔티티가 수정되어야 한다.") { - val lab = labRepository.findByIdOrNull(createdLab.id)!! - val researchSearch = lab.researchSearch - researchSearch shouldNotBe null - - researchSearch!!.content shouldBe - """ - name2 - professor1 - professor3 - location2 - tel2 - acronym2 - youtube2 - research - description2 - websiteURL2 - - """.trimIndent() + Then("Research Group should be deleted") { + researchLanguageRepository.findAll() shouldBe emptyList() + researchRepository.findAll() shouldBe emptyList() } } }