From 5778d98775df5a84b2a221d7ad23d4f77355a5f4 Mon Sep 17 00:00:00 2001 From: Junhyeong Kim Date: Wed, 4 Sep 2024 12:52:59 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=86=8C=EA=B0=9C=20=ED=83=AD=20CRUD?= =?UTF-8?q?=20(#314)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 소개 탭 CRUD * fix: 권한 체크 추가 * fix: 동아리 id 기준으로 로직 변경 * fix: 쿠키 same-site: strict * 리뷰 반영 * 리뷰 반영 --- .../core/about/api/req/CreateClubReq.kt | 11 + .../core/about/api/req/CreateCompanyReq.kt | 7 + .../core/about/api/req/CreateFacReq.kt | 12 + .../core/about/api/req/UpdateAboutReq.kt | 12 + .../core/about/api/req/UpdateClubReq.kt | 9 + .../about/api/req/UpdateDescriptionReq.kt | 6 + .../core/about/api/req/UpdateFacReq.kt | 7 + .../about/api/{ => v1}/AboutController.kt | 75 +-- .../core/about/api/v2/AboutController.kt | 84 +++ .../core/about/database/AboutEntity.kt | 5 +- .../about/database/AboutLanguageEntity.kt | 15 + .../about/database/AboutLanguageRepository.kt | 8 + .../core/about/database/AboutRepository.kt | 1 + .../core/about/database/CompanyRepository.kt | 2 +- .../csereal/core/about/dto/AboutDto.kt | 2 - .../csereal/core/about/dto/GroupedClubDto.kt | 21 + .../csereal/core/about/dto/StudentClubDto.kt | 2 - .../core/about/service/AboutService.kt | 529 +++++++++--------- src/main/resources/application.yaml | 2 +- 19 files changed, 471 insertions(+), 339 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateClubReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateCompanyReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateFacReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateAboutReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateClubReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateDescriptionReq.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateFacReq.kt rename src/main/kotlin/com/wafflestudio/csereal/core/about/api/{ => v1}/AboutController.kt (56%) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/api/v2/AboutController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/about/dto/GroupedClubDto.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateClubReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateClubReq.kt new file mode 100644 index 00000000..eeb3af14 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateClubReq.kt @@ -0,0 +1,11 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class CreateClubReq( + val ko: ClubReqBody, + val en: ClubReqBody +) + +data class ClubReqBody( + val name: String, + val description: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateCompanyReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateCompanyReq.kt new file mode 100644 index 00000000..c8663d07 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateCompanyReq.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class CreateCompanyReq( + val name: String, + val url: String?, + val year: Int +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateFacReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateFacReq.kt new file mode 100644 index 00000000..d9ead5e0 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/CreateFacReq.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class CreateFacReq( + val ko: FacDto, + val en: FacDto +) + +data class FacDto( + val name: String, + val description: String, + val locations: MutableList +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateAboutReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateAboutReq.kt new file mode 100644 index 00000000..ad64c3d1 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateAboutReq.kt @@ -0,0 +1,12 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class UpdateAboutReq( + val ko: BasicAbout, + val en: BasicAbout, + val removeImage: Boolean +) + +data class BasicAbout( + val description: String, + val deleteIds: List +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateClubReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateClubReq.kt new file mode 100644 index 00000000..bf8c8966 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateClubReq.kt @@ -0,0 +1,9 @@ +package com.wafflestudio.csereal.core.about.api.req + +import com.wafflestudio.csereal.core.about.dto.ClubDto + +data class UpdateClubReq( + val ko: ClubDto, + val en: ClubDto, + val removeImage: Boolean +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateDescriptionReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateDescriptionReq.kt new file mode 100644 index 00000000..0ea22f11 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateDescriptionReq.kt @@ -0,0 +1,6 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class UpdateDescriptionReq( + val koDescription: String, + val enDescription: String +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateFacReq.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateFacReq.kt new file mode 100644 index 00000000..045c5340 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/req/UpdateFacReq.kt @@ -0,0 +1,7 @@ +package com.wafflestudio.csereal.core.about.api.req + +data class UpdateFacReq( + val ko: FacDto, + val en: FacDto, + val removeImage: Boolean +) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/v1/AboutController.kt similarity index 56% rename from src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt rename to src/main/kotlin/com/wafflestudio/csereal/core/about/api/v1/AboutController.kt index c4f00fd8..756054ba 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/AboutController.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/v1/AboutController.kt @@ -1,21 +1,17 @@ -package com.wafflestudio.csereal.core.about.api +package com.wafflestudio.csereal.core.about.api.v1 -import com.wafflestudio.csereal.common.aop.AuthenticatedStaff import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.about.api.req.* import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.dto.* -import com.wafflestudio.csereal.core.about.dto.AboutRequest -import com.wafflestudio.csereal.core.about.dto.FutureCareersRequest import com.wafflestudio.csereal.core.about.service.AboutService import jakarta.validation.Valid import jakarta.validation.constraints.Positive -import org.springframework.context.annotation.Profile import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* -import org.springframework.web.multipart.MultipartFile @RequestMapping("/api/v1/about") -@RestController +@RestController("AboutControllerV1") class AboutController( private val aboutService: AboutService ) { @@ -24,18 +20,6 @@ class AboutController( // postType: directions / name -> by-public-transit, by-car, from-far-away // Todo: 학부장 인사말(greetings) signature - @AuthenticatedStaff - @PostMapping("/{postType}") - fun createAbout( - @PathVariable postType: String, - @Valid - @RequestPart("request") - request: AboutDto, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? - ): ResponseEntity { - return ResponseEntity.ok(aboutService.createAbout(postType, request, mainImage, attachments)) - } // read 목록이 하나 @GetMapping("/{postType}") @@ -46,6 +30,7 @@ class AboutController( return ResponseEntity.ok(aboutService.readAbout(language, postType)) } + @Deprecated("Use V2 API") @GetMapping("/student-clubs") fun readAllClubs( @RequestParam(required = false, defaultValue = "ko") language: String @@ -101,56 +86,4 @@ class AboutController( pageNum, amount ) - - @Profile("!prod") - @PostMapping("/migrate") - fun migrateAbout( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(aboutService.migrateAbout(requestList)) - } - - @Profile("!prod") - @PostMapping("/future-careers/migrate") - fun migrateFutureCareers( - @RequestBody request: FutureCareersRequest - ): ResponseEntity { - return ResponseEntity.ok(aboutService.migrateFutureCareers(request)) - } - - @Profile("!prod") - @PostMapping("/student-clubs/migrate") - fun migrateStudentClubs( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(aboutService.migrateStudentClubs(requestList)) - } - - @Profile("!prod") - @PostMapping("/facilities/migrate") - fun migrateFacilities( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(aboutService.migrateFacilities(requestList)) - } - - @Profile("!prod") - @PostMapping("/directions/migrate") - fun migrateDirections( - @RequestBody requestList: List - ): ResponseEntity> { - return ResponseEntity.ok(aboutService.migrateDirections(requestList)) - } - - @Profile("!prod") - @PatchMapping("/migrateImage/{aboutId}") - fun migrateAboutImageAndAttachment( - @PathVariable aboutId: Long, - @RequestPart("mainImage") mainImage: MultipartFile?, - @RequestPart("attachments") attachments: List? - ): ResponseEntity { - return ResponseEntity.ok( - aboutService.migrateAboutImageAndAttachments(aboutId, mainImage, attachments) - ) - } } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/api/v2/AboutController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/v2/AboutController.kt new file mode 100644 index 00000000..16ffb2ed --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/api/v2/AboutController.kt @@ -0,0 +1,84 @@ +package com.wafflestudio.csereal.core.about.api.v2 + +import com.wafflestudio.csereal.common.aop.AuthenticatedStaff +import com.wafflestudio.csereal.core.about.api.req.* +import com.wafflestudio.csereal.core.about.dto.GroupedClubDto +import com.wafflestudio.csereal.core.about.service.AboutService +import org.springframework.web.bind.annotation.* +import org.springframework.web.multipart.MultipartFile + +@RequestMapping("/api/v2/about") +@RestController +class AboutController( + private val aboutService: AboutService +) { + @GetMapping("/student-clubs") + fun readAllClubs(): List = aboutService.readAllGroupedClubs() + + @AuthenticatedStaff + @PostMapping("/student-clubs") + fun createClub( + @RequestPart request: CreateClubReq, + @RequestPart mainImage: MultipartFile? + ) = aboutService.createClub(request, mainImage) + + @AuthenticatedStaff + @PutMapping("/student-clubs") + fun updateClub( + @RequestPart request: UpdateClubReq, + @RequestPart newMainImage: MultipartFile? + ) = aboutService.updateClub(request, newMainImage) + + @AuthenticatedStaff + @DeleteMapping("/student-clubs/{id}") + fun deleteClub(@PathVariable id: Long) = aboutService.deleteClub(id) + + @AuthenticatedStaff + @PutMapping("/{postType}") + fun updateAbout( + @PathVariable postType: String, + @RequestPart request: UpdateAboutReq, + @RequestPart newMainImage: MultipartFile?, + @RequestPart newAttachments: List? + ) = aboutService.updateAbout(postType, request, newMainImage, newAttachments) + + @AuthenticatedStaff + @PostMapping("/facilities") + fun createFacilities(@RequestPart request: CreateFacReq, @RequestPart mainImage: MultipartFile?) = + aboutService.createFacilities(request, mainImage) + + @AuthenticatedStaff + @PutMapping("/facilities/{id}") + fun updateFacility( + @PathVariable id: Long, + @RequestPart request: UpdateFacReq, + @RequestPart newMainImage: MultipartFile? + ) = aboutService.updateFacility(id, request, newMainImage) + + @AuthenticatedStaff + @DeleteMapping("/facilities/{id}") + fun deleteFacility(@PathVariable id: Long) = aboutService.deleteFacility(id) + + @AuthenticatedStaff + @PutMapping("/directions/{id}") + fun updateDirection(@PathVariable id: Long, @RequestBody request: UpdateDescriptionReq) = + aboutService.updateDirection(id, request) + + @AuthenticatedStaff + @PutMapping("/future-careers") + fun updateFutureCareersPage(@RequestBody request: UpdateDescriptionReq) = + aboutService.updateFutureCareersPage(request) + + @AuthenticatedStaff + @PostMapping("/future-careers/company") + fun createCompany(@RequestBody request: CreateCompanyReq) = aboutService.createCompany(request) + + @AuthenticatedStaff + @PutMapping("/future-careers/company/{id}") + fun updateCompany(@PathVariable id: Long, @RequestBody request: CreateCompanyReq) = + aboutService.updateCompany(id, request) + + @AuthenticatedStaff + @DeleteMapping("/future-careers/company/{id}") + fun deleteCompany(@PathVariable id: Long) = aboutService.deleteCompany(id) +} diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt index 6aeeacb2..a604bc96 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutEntity.kt @@ -22,8 +22,6 @@ class AboutEntity( @Column(columnDefinition = "mediumText") var description: String, - var year: Int?, - @Column(columnDefinition = "TEXT") @Convert(converter = StringListConverter::class) var locations: MutableList = mutableListOf(), @@ -31,7 +29,7 @@ class AboutEntity( @OneToMany(mappedBy = "about", cascade = [CascadeType.ALL], orphanRemoval = true) var attachments: MutableList = mutableListOf(), - @OneToOne + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) var mainImage: MainImageEntity? = null, @Column(columnDefinition = "TEXT") @@ -52,7 +50,6 @@ class AboutEntity( language = languageType, name = aboutDto.name, description = aboutDto.description, - year = aboutDto.year, locations = aboutDto.locations?.toMutableList() ?: mutableListOf(), searchContent = "" ) diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageEntity.kt new file mode 100644 index 00000000..68e34520 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageEntity.kt @@ -0,0 +1,15 @@ +package com.wafflestudio.csereal.core.about.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import jakarta.persistence.* + +@Entity(name = "about_language") +class AboutLanguageEntity( + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) + @JoinColumn(name = "korean_id") + val koAbout: AboutEntity, + + @OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], orphanRemoval = true) + @JoinColumn(name = "english_id") + val enAbout: AboutEntity +) : BaseTimeEntity() diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageRepository.kt new file mode 100644 index 00000000..f4c06ed9 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/AboutLanguageRepository.kt @@ -0,0 +1,8 @@ +package com.wafflestudio.csereal.core.about.database + +import org.springframework.data.jpa.repository.JpaRepository + +interface AboutLanguageRepository : JpaRepository { + fun findByKoAbout(koAboutEntity: AboutEntity): AboutLanguageEntity? + fun findByEnAbout(enAboutEntity: AboutEntity): AboutLanguageEntity? +} 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 9470cd50..6572b8e9 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 @@ -14,6 +14,7 @@ interface AboutRepository : JpaRepository, AboutCustomReposit languageType: LanguageType, postType: AboutPostType ): List + fun findByLanguageAndPostType( languageType: LanguageType, postType: AboutPostType diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt index 321605c6..a4aef4c9 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/database/CompanyRepository.kt @@ -3,5 +3,5 @@ package com.wafflestudio.csereal.core.about.database import org.springframework.data.jpa.repository.JpaRepository interface CompanyRepository : JpaRepository { - fun findAllByOrderByYearDesc(): List + fun findAllByOrderByNameDesc(): List } diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt index fc35fbbc..5395e113 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/AboutDto.kt @@ -12,7 +12,6 @@ data class AboutDto( val language: String, val name: String?, val description: String, - val year: Int?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val locations: List?, @@ -30,7 +29,6 @@ data class AboutDto( language = LanguageType.makeLowercase(this.language), name = this.name, description = this.description, - year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, locations = this.locations, diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/GroupedClubDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/GroupedClubDto.kt new file mode 100644 index 00000000..4d9a977e --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/dto/GroupedClubDto.kt @@ -0,0 +1,21 @@ +package com.wafflestudio.csereal.core.about.dto + +import com.wafflestudio.csereal.core.about.database.AboutEntity + +data class GroupedClubDto( + val ko: ClubDto, + val en: ClubDto +) + +data class ClubDto( + val id: Long, + val name: String, + val description: String, + val imageURL: String? +) { + companion object { + fun of(aboutEntity: AboutEntity, imageURL: String?): ClubDto { + return ClubDto(aboutEntity.id, aboutEntity.name!!, aboutEntity.description, imageURL) + } + } +} 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 bf889423..43a471f7 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 @@ -13,7 +13,6 @@ data class StudentClubDto( val name: String, val engName: String, val description: String, - val year: Int?, val createdAt: LocalDateTime?, val modifiedAt: LocalDateTime?, val locations: List?, @@ -34,7 +33,6 @@ data class StudentClubDto( name = name, engName = engName, description = this.description, - year = this.year, createdAt = this.createdAt, modifiedAt = this.modifiedAt, locations = this.locations, 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 7c676e7d..db67a978 100644 --- a/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt +++ b/src/main/kotlin/com/wafflestudio/csereal/core/about/service/AboutService.kt @@ -2,6 +2,7 @@ package com.wafflestudio.csereal.core.about.service import com.wafflestudio.csereal.common.CserealException import com.wafflestudio.csereal.common.enums.LanguageType +import com.wafflestudio.csereal.core.about.api.req.* import com.wafflestudio.csereal.core.about.api.res.AboutSearchElementDto import com.wafflestudio.csereal.core.about.api.res.AboutSearchResBody import com.wafflestudio.csereal.core.about.database.* @@ -17,18 +18,31 @@ import org.springframework.transaction.annotation.Transactional import org.springframework.web.multipart.MultipartFile interface AboutService { - fun createAbout( + fun readAbout(language: String, postType: String): AboutDto + fun updateAbout( postType: String, - request: AboutDto, - mainImage: MultipartFile?, - attachments: List? - ): AboutDto + request: UpdateAboutReq, + newMainImage: MultipartFile?, + newAttachments: List? + ) + + fun createClub(request: CreateClubReq, mainImage: MultipartFile?) + fun updateClub(request: UpdateClubReq, newMainImage: MultipartFile?) + fun deleteClub(id: Long) - fun readAbout(language: String, postType: String): AboutDto fun readAllClubs(language: String): List + fun readAllGroupedClubs(): List + fun createFacilities(request: CreateFacReq, mainImage: MultipartFile?) + fun updateFacility(id: Long, request: UpdateFacReq, newMainImage: MultipartFile?) + fun deleteFacility(id: Long) fun readAllFacilities(language: String): List fun readAllDirections(language: String): List + fun updateDirection(id: Long, request: UpdateDescriptionReq) + fun updateFutureCareersPage(request: UpdateDescriptionReq) fun readFutureCareers(language: String): FutureCareersPage + fun createCompany(request: CreateCompanyReq) + fun updateCompany(id: Long, request: CreateCompanyReq) + fun deleteCompany(id: Long) fun searchTopAbout( keyword: String, @@ -44,17 +58,6 @@ interface AboutService { pageNum: Int, amount: Int ): AboutSearchResBody - - fun migrateAbout(requestList: List): List - fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage - fun migrateStudentClubs(requestList: List): List - fun migrateFacilities(requestList: List): List - fun migrateDirections(requestList: List): List - fun migrateAboutImageAndAttachments( - aboutId: Long, - mainImage: MultipartFile?, - attachments: List? - ): AboutDto } @Service @@ -63,47 +66,127 @@ class AboutServiceImpl( private val companyRepository: CompanyRepository, private val statRepository: StatRepository, private val mainImageService: MainImageService, - private val attachmentService: AttachmentService + private val attachmentService: AttachmentService, + private val aboutLanguageRepository: AboutLanguageRepository ) : AboutService { + + @Transactional(readOnly = true) + override fun readAbout(language: String, postType: String): AboutDto { + val languageType = LanguageType.makeStringToLanguageType(language) + val enumPostType = makeStringToEnum(postType) + val about = aboutRepository.findByLanguageAndPostType(languageType, enumPostType) + val imageURL = mainImageService.createImageURL(about.mainImage) + val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) + + return AboutDto.of(about, imageURL, attachmentResponses) + } + @Transactional - override fun createAbout( + override fun updateAbout( postType: String, - request: AboutDto, - mainImage: MultipartFile?, - attachments: List? - ): AboutDto { + request: UpdateAboutReq, + newMainImage: MultipartFile?, + newAttachments: List? + ) { val enumPostType = makeStringToEnum(postType) - val enumLanguageType = LanguageType.makeStringToLanguageType(request.language) - var newAbout = AboutEntity.of(enumPostType, enumLanguageType, request) + val languages = listOf(LanguageType.KO, LanguageType.EN) + val abouts = languages.map { lang -> + aboutRepository.findByLanguageAndPostType(lang, enumPostType).apply { + description = if (lang == LanguageType.KO) request.ko.description else request.en.description + } + } + + abouts.forEach { it.syncSearchContent() } + + if (newMainImage != null) { + abouts.forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + mainImageService.uploadMainImage(it, newMainImage) + } + } else if (request.removeImage) { + abouts.forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + it.mainImage = null + } + } + + attachmentService.deleteAttachments(request.ko.deleteIds + request.en.deleteIds) + + if (newAttachments != null) { + abouts.forEach { attachmentService.uploadAllAttachments(it, newAttachments) } + } + } + + @Transactional + override fun createClub(request: CreateClubReq, mainImage: MultipartFile?) { + val langToReq = listOf( + LanguageType.KO to request.ko, + LanguageType.EN to request.en + ) + + val clubs = langToReq.map { (lang, req) -> + AboutEntity( + AboutPostType.STUDENT_CLUBS, + lang, + req.name, + req.description, + searchContent = "" + ).apply { syncSearchContent() } + } if (mainImage != null) { - mainImageService.uploadMainImage(newAbout, mainImage) + clubs.forEach { mainImageService.uploadMainImage(it, mainImage) } } - if (attachments != null) { - attachmentService.uploadAllAttachments(newAbout, attachments) + aboutLanguageRepository.save(AboutLanguageEntity(clubs[0], clubs[1])) + } + + @Transactional + override fun updateClub(request: UpdateClubReq, newMainImage: MultipartFile?) { + val (ko, en) = listOf(request.ko.id, request.en.id).map { id -> + aboutRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("club not found") } - syncSearchOfAbout(newAbout) + if (ko.language != LanguageType.KO || en.language != LanguageType.EN) { + throw CserealException.Csereal400("language doesn't match") + } - newAbout = aboutRepository.save(newAbout) + listOf(ko to request.ko, en to request.en).forEach { (club, clubDto) -> + updateClubDetails(club, clubDto) + } - val imageURL = mainImageService.createImageURL(newAbout.mainImage) - val attachmentResponses = - attachmentService.createAttachmentResponses(newAbout.attachments) + if (newMainImage != null) { + listOf(ko, en).forEach { club -> + club.mainImage?.let { image -> mainImageService.removeImage(image) } + mainImageService.uploadMainImage(club, newMainImage) + } + } else if (request.removeImage) { + listOf(ko, en).forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + it.mainImage = null + } + } + } - return AboutDto.of(newAbout, imageURL, attachmentResponses) + private fun updateClubDetails(club: AboutEntity, clubDto: ClubDto) { + club.name = clubDto.name + club.description = clubDto.description + club.syncSearchContent() } - @Transactional(readOnly = true) - override fun readAbout(language: String, postType: String): AboutDto { - val languageType = LanguageType.makeStringToLanguageType(language) - val enumPostType = makeStringToEnum(postType) - val about = aboutRepository.findByLanguageAndPostType(languageType, enumPostType) - val imageURL = mainImageService.createImageURL(about.mainImage) - val attachmentResponses = attachmentService.createAttachmentResponses(about.attachments) + @Transactional + override fun deleteClub(id: Long) { + val club = aboutRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("club not found") + val aboutLanguage = when (club.language) { + LanguageType.KO -> aboutLanguageRepository.findByKoAbout(club) + LanguageType.EN -> aboutLanguageRepository.findByEnAbout(club) + } - return AboutDto.of(about, imageURL, attachmentResponses) + listOf(aboutLanguage!!.koAbout, aboutLanguage.enAbout).forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + } + + aboutLanguageRepository.delete(aboutLanguage) } @Transactional(readOnly = true) @@ -125,6 +208,96 @@ class AboutServiceImpl( return clubs } + @Transactional(readOnly = true) + override fun readAllGroupedClubs(): List { + val clubs = aboutLanguageRepository.findAll().filter { it.koAbout.postType == AboutPostType.STUDENT_CLUBS } + .sortedBy { it.koAbout.name } + return clubs.map { + val imageURL = mainImageService.createImageURL(it.koAbout.mainImage) + GroupedClubDto(ko = ClubDto.of(it.koAbout, imageURL), en = ClubDto.of(it.enAbout, imageURL)) + } + } + + @Transactional + override fun createFacilities(request: CreateFacReq, mainImage: MultipartFile?) { + val langToReq = listOf( + LanguageType.KO to request.ko, + LanguageType.EN to request.en + ) + + val facilities = langToReq.map { (lang, req) -> + AboutEntity( + AboutPostType.FACILITIES, + lang, + req.name, + req.description, + searchContent = "", + locations = req.locations + ).apply { syncSearchContent() } + } + + if (mainImage != null) { + facilities.forEach { mainImageService.uploadMainImage(it, mainImage) } + } + + aboutLanguageRepository.save(AboutLanguageEntity(facilities[0], facilities[1])) + } + + @Transactional + override fun updateFacility(id: Long, request: UpdateFacReq, newMainImage: MultipartFile?) { + val facility = aboutRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("id not found") + + val corresponding = when (facility.language) { + LanguageType.KO -> aboutLanguageRepository.findByKoAbout(facility)!!.enAbout + LanguageType.EN -> aboutLanguageRepository.findByEnAbout(facility)!!.koAbout + } + + when (facility.language) { + LanguageType.KO -> { + updateFacility(facility, request.ko) + updateFacility(corresponding, request.en) + } + + LanguageType.EN -> { + updateFacility(facility, request.en) + updateFacility(corresponding, request.ko) + } + } + + facility.syncSearchContent() + corresponding.syncSearchContent() + + if (newMainImage != null) { + listOf(facility, corresponding).forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + mainImageService.uploadMainImage(it, newMainImage) + } + } else if (request.removeImage) { + listOf(facility, corresponding).forEach { + it.mainImage?.let { image -> mainImageService.removeImage(image) } + it.mainImage = null + } + } + } + + private fun updateFacility(facility: AboutEntity, facDto: FacDto) { + facility.name = facDto.name + facility.description = facDto.description + facility.locations = facDto.locations + } + + @Transactional + override fun deleteFacility(id: Long) { + val facility = aboutRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("id not found") + + val facilityLanguage = when (facility.language) { + LanguageType.KO -> aboutLanguageRepository.findByKoAbout(facility) + LanguageType.EN -> aboutLanguageRepository.findByEnAbout(facility) + } + + aboutLanguageRepository.delete(facilityLanguage!!) + } + @Transactional(readOnly = true) override fun readAllFacilities(language: String): List { val languageType = LanguageType.makeStringToLanguageType(language) @@ -158,6 +331,43 @@ class AboutServiceImpl( return directions } + @Transactional + override fun updateDirection(id: Long, request: UpdateDescriptionReq) { + val direction = aboutRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("direction not found") + + val corresponding = when (direction.language) { + LanguageType.KO -> aboutLanguageRepository.findByKoAbout(direction)!!.enAbout + LanguageType.EN -> aboutLanguageRepository.findByEnAbout(direction)!!.koAbout + } + + when (direction.language) { + LanguageType.KO -> { + direction.description = request.koDescription + corresponding.description = request.enDescription + } + + LanguageType.EN -> { + direction.description = request.enDescription + corresponding.description = request.koDescription + } + } + + direction.syncSearchContent() + corresponding.syncSearchContent() + } + + @Transactional + override fun updateFutureCareersPage(request: UpdateDescriptionReq) { + val ko = aboutRepository.findByLanguageAndPostType(LanguageType.KO, AboutPostType.FUTURE_CAREERS) + val en = aboutRepository.findByLanguageAndPostType(LanguageType.EN, AboutPostType.FUTURE_CAREERS) + + ko.description = request.koDescription + en.description = request.enDescription + + ko.syncSearchContent() + en.syncSearchContent() + } + @Transactional override fun readFutureCareers(language: String): FutureCareersPage { val languageType = LanguageType.makeStringToLanguageType(language) @@ -183,12 +393,30 @@ class AboutServiceImpl( FutureCareersStatDto(i, bachelor, master, doctor) ) } - val companyList = companyRepository.findAllByOrderByYearDesc().map { + val companyList = companyRepository.findAllByOrderByNameDesc().map { FutureCareersCompanyDto.of(it) } return FutureCareersPage(description, statList, companyList) } + @Transactional + override fun createCompany(request: CreateCompanyReq) { + companyRepository.save(CompanyEntity(request.name, request.url, request.year)) + } + + @Transactional + override fun updateCompany(id: Long, request: CreateCompanyReq) { + val company = companyRepository.findByIdOrNull(id) ?: throw CserealException.Csereal404("company not found") + company.name = request.name + company.url = request.url + company.year = request.year + } + + @Transactional + override fun deleteCompany(id: Long) { + companyRepository.deleteById(id) + } + @Transactional(propagation = Propagation.REQUIRES_NEW) @EventListener fun refreshSearchListener(event: RefreshSearchEvent) { @@ -248,221 +476,6 @@ class AboutServiceImpl( ) } - @Transactional - override fun migrateAbout(requestList: List): List { - // Todo: add about migrate search - val list = mutableListOf() - - for (request in requestList) { - val language = request.language - val description = request.description - val enumPostType = makeStringToEnum(request.postType) - - val aboutDto = AboutDto( - id = null, - language = language, - name = null, - description = description, - year = null, - createdAt = null, - modifiedAt = null, - locations = null, - imageURL = null, - attachments = listOf() - ) - - val languageType = LanguageType.makeStringToLanguageType(language) - var newAbout = AboutEntity.of(enumPostType, languageType, aboutDto) - syncSearchOfAbout(newAbout) - - newAbout = aboutRepository.save(newAbout) - - list.add(AboutDto.of(newAbout, null, listOf())) - } - return list - } - - @Transactional - override fun migrateFutureCareers(request: FutureCareersRequest): FutureCareersPage { - // Todo: add about migrate search - val description = request.description - val language = request.language - val statList = mutableListOf() - val companyList = mutableListOf() - - val aboutDto = AboutDto( - id = null, - language = language, - name = null, - description = description, - year = null, - createdAt = null, - modifiedAt = null, - locations = null, - imageURL = null, - attachments = listOf() - ) - - val languageType = LanguageType.makeStringToLanguageType(language) - - var newAbout = AboutEntity.of(AboutPostType.FUTURE_CAREERS, languageType, aboutDto) - - for (stat in request.stat) { - val year = stat.year - val bachelorList = mutableListOf() - val masterList = mutableListOf() - val doctorList = mutableListOf() - - for (bachelor in stat.bachelor) { - val newBachelor = StatEntity.of(year, Degree.BACHELOR, bachelor) - statRepository.save(newBachelor) - - bachelorList.add(bachelor) - } - for (master in stat.master) { - val newMaster = StatEntity.of(year, Degree.MASTER, master) - statRepository.save(newMaster) - - masterList.add(master) - } - for (doctor in stat.doctor) { - val newDoctor = StatEntity.of(year, Degree.DOCTOR, doctor) - statRepository.save(newDoctor) - - doctorList.add(doctor) - } - } - - for (company in request.companies) { - val newCompany = CompanyEntity.of(company) - companyRepository.save(newCompany) - - companyList.add(company) - } - - syncSearchOfAbout(newAbout) - newAbout = aboutRepository.save(newAbout) - - return FutureCareersPage(description, statList.toList(), companyList.toList()) - } - - @Transactional - override fun migrateStudentClubs(requestList: List): List { - val list = mutableListOf() - - for (request in requestList) { - val language = request.language - val name = request.name.split("(")[0] - val engName = request.name.split("(")[1].replaceFirst(")", "") - - val aboutDto = AboutDto( - id = null, - language = language, - name = name, - description = request.description, - year = null, - createdAt = null, - modifiedAt = null, - locations = null, - imageURL = null, - attachments = listOf() - ) - val languageType = LanguageType.makeStringToLanguageType(language) - - var newAbout = AboutEntity.of(AboutPostType.STUDENT_CLUBS, languageType, aboutDto) - - syncSearchOfAbout(newAbout) - newAbout = aboutRepository.save(newAbout) - - list.add(StudentClubDto.of(newAbout, name, engName, null, listOf())) - } - return list - } - - @Transactional - override fun migrateFacilities(requestList: List): List = - // Todo: add about migrate search - requestList.map { - AboutDto( - id = null, - language = it.language, - name = it.name, - description = it.description, - year = null, - createdAt = null, - modifiedAt = null, - locations = it.locations, - imageURL = null, - attachments = listOf() - ).let { dto -> - AboutEntity.of( - AboutPostType.FACILITIES, - LanguageType.makeStringToLanguageType(it.language), - dto - ) - }.also { - syncSearchOfAbout(it) - } - }.let { - aboutRepository.saveAll(it) - }.map { - FacilityDto.of(it) - } - - @Transactional - override fun migrateDirections(requestList: List): List { - // Todo: add about migrate search - val list = mutableListOf() - - for (request in requestList) { - val language = request.language - val name = request.name - val description = request.description - - val aboutDto = AboutDto( - id = null, - language = language, - name = name, - description = description, - year = null, - createdAt = null, - modifiedAt = null, - locations = null, - imageURL = null, - attachments = listOf() - ) - - val languageType = LanguageType.makeStringToLanguageType(language) - var newAbout = AboutEntity.of(AboutPostType.DIRECTIONS, languageType, aboutDto) - syncSearchOfAbout(newAbout) - - newAbout = aboutRepository.save(newAbout) - - list.add(DirectionDto.of(newAbout)) - } - return list - } - - @Transactional - override fun migrateAboutImageAndAttachments( - aboutId: Long, - mainImage: MultipartFile?, - attachments: List? - ): AboutDto { - val about = aboutRepository.findByIdOrNull(aboutId) - ?: throw CserealException.Csereal404("해당 소개는 존재하지 않습니다.") - - if (mainImage != null) { - mainImageService.uploadMainImage(about, mainImage) - } - - val imageURL = mainImageService.createImageURL(about.mainImage) - val attachmentResponses = - attachmentService.createAttachmentResponses(about.attachments) - - return AboutDto.of(about, imageURL, attachmentResponses) - } - private fun makeStringToEnum(postType: String): AboutPostType { try { val upperPostType = postType.replace("-", "_").uppercase() diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 716b0cfb..d88b3fdb 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -27,7 +27,7 @@ server: session: timeout: 32400 # 9시간 cookie: - same-site: lax + same-site: strict secure: true springdoc: