From 54ad2ff3f3f925b3d451b9dc0cec59379373942b Mon Sep 17 00:00:00 2001 From: "DESKTOP-USQPRVG\\gram" Date: Thu, 10 Aug 2023 16:16:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20createSeminar,=20readSeminar,=20updateS?= =?UTF-8?q?eminar=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/seminar/api/SeminarController.kt | 36 +++++++ .../core/seminar/database/SeminarEntity.kt | 96 +++++++++++++++++++ .../seminar/database/SeminarRepository.kt | 65 +++++++++++++ .../csereal/core/seminar/dto/SeminarDto.kt | 64 +++++++++++++ .../core/seminar/service/SeminarService.kt | 53 ++++++++++ 5 files changed, 314 insertions(+) create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt create mode 100644 src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt new file mode 100644 index 00000000..d8629df7 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/api/SeminarController.kt @@ -0,0 +1,36 @@ +package com.wafflestudio.csereal.core.seminar.api + +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import com.wafflestudio.csereal.core.seminar.service.SeminarService +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RequestMapping("/seminar") +@RestController +class SeminarController ( + private val seminarService: SeminarService, +) { + @PostMapping + fun createSeminar( + @Valid @RequestBody request: SeminarDto + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.createSeminar(request)) + } + + @GetMapping("/{seminarId}") + fun readSeminar( + @PathVariable seminarId: Long, + @RequestParam(required = false) keyword: String?, + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.readSeminar(seminarId, keyword)) + } + + @PatchMapping("/{seminarId}") + fun updateSeminar( + @PathVariable seminarId: Long, + @Valid @RequestBody request: SeminarDto, + ) : ResponseEntity { + return ResponseEntity.ok(seminarService.updateSeminar(seminarId, request)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt new file mode 100644 index 00000000..bc098643 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarEntity.kt @@ -0,0 +1,96 @@ +package com.wafflestudio.csereal.core.seminar.database + +import com.wafflestudio.csereal.common.config.BaseTimeEntity +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import jakarta.persistence.Column +import jakarta.persistence.Entity + +@Entity(name = "seminar") +class SeminarEntity( + + var isDeleted: Boolean = false, + + var title: String, + + @Column(columnDefinition = "text") + var description: String, + + @Column(columnDefinition = "text") + var introduction: String, + + var category: String, + + // 연사 정보 + var name: String, + var speakerUrl: String?, + var speakerTitle: String?, + var affiliation: String, + var affiliationUrl: String?, + + var startDate: String?, + var startTime: String?, + var endDate: String?, + var endTime: String?, + + var location: String, + + var host: String?, + + // var profileImage: File, + + // var seminarFile: File, + + var isPublic: Boolean, + + var isSlide: Boolean, + + var additionalNote: String? +): BaseTimeEntity() { + + companion object { + fun of(seminarDto: SeminarDto): SeminarEntity { + return SeminarEntity( + title = seminarDto.title, + description = seminarDto.description, + introduction = seminarDto.introduction, + category = seminarDto.category, + name = seminarDto.name, + speakerUrl = seminarDto.speakerUrl, + speakerTitle = seminarDto.speakerTitle, + affiliation = seminarDto.affiliation, + affiliationUrl = seminarDto.affiliationUrl, + startDate = seminarDto.startDate, + startTime = seminarDto.startTime, + endDate = seminarDto.endDate, + endTime = seminarDto.endTime, + location = seminarDto.location, + host = seminarDto.host, + additionalNote = seminarDto.additionalNote, + isPublic = seminarDto.isPublic, + isSlide = seminarDto.isSlide + ) + } + + } + + fun update(updateSeminarRequest: SeminarDto) { + title = updateSeminarRequest.title + description = updateSeminarRequest.description + introduction = updateSeminarRequest.introduction + category = updateSeminarRequest.category + name = updateSeminarRequest.name + speakerUrl = updateSeminarRequest.speakerUrl + speakerTitle = updateSeminarRequest.speakerTitle + affiliation = updateSeminarRequest.affiliation + affiliationUrl = updateSeminarRequest.affiliationUrl + startDate = updateSeminarRequest.startDate + startTime = updateSeminarRequest.startTime + endDate = updateSeminarRequest.endDate + endTime = updateSeminarRequest.endTime + location = updateSeminarRequest.location + host = updateSeminarRequest.host + additionalNote = updateSeminarRequest.additionalNote + isPublic = updateSeminarRequest.isPublic + isSlide = updateSeminarRequest.isSlide + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt new file mode 100644 index 00000000..e21cb346 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/database/SeminarRepository.kt @@ -0,0 +1,65 @@ +package com.wafflestudio.csereal.core.seminar.database + +import com.querydsl.core.BooleanBuilder +import com.querydsl.jpa.impl.JPAQueryFactory +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.seminar.database.QSeminarEntity.seminarEntity +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Component + +interface SeminarRepository : JpaRepository, CustomSeminarRepository { +} + +interface CustomSeminarRepository { + fun findPrevNextId(seminarId: Long, keyword: String?): Array? +} + +@Component +class SeminarRepositoryImpl( + private val queryFactory: JPAQueryFactory, +) : CustomSeminarRepository { + override fun findPrevNextId(seminarId: Long, keyword: String?): Array? { + val keywordBooleanBuilder = BooleanBuilder() + + if (!keyword.isNullOrEmpty()) { + val keywordList = keyword.split("[^a-zA-Z0-9가-힣]".toRegex()) + keywordList.forEach { + if (it.length == 1) { + throw CserealException.Csereal400("각각의 키워드는 한글자 이상이어야 합니다.") + } else { + keywordBooleanBuilder.and( + seminarEntity.title.contains(it) + .or(seminarEntity.description.contains(it)) + ) + } + } + } + val seminarSearchDtoList = queryFactory.select(seminarEntity).from(seminarEntity) + .where(seminarEntity.isDeleted.eq(false), seminarEntity.isPublic.eq(true)) + .where(keywordBooleanBuilder) + .orderBy(seminarEntity.createdAt.desc()) + .distinct() + .fetch() + + val findingId = seminarSearchDtoList.indexOfFirst { it.id == seminarId } + + val prevNext: Array? + + if (findingId == -1) { + return null + } else if (findingId != 0 && findingId != seminarSearchDtoList.size - 1) { + prevNext = arrayOf(seminarSearchDtoList[findingId + 1], seminarSearchDtoList[findingId - 1]) + } else if (findingId == 0) { + if (seminarSearchDtoList.size == 1) { + prevNext = arrayOf(null, null) + } else { + prevNext = arrayOf(seminarSearchDtoList[1], null) + } + } else { + prevNext = arrayOf(null, seminarSearchDtoList[seminarSearchDtoList.size - 2]) + } + + return prevNext + + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt new file mode 100644 index 00000000..b2563385 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/dto/SeminarDto.kt @@ -0,0 +1,64 @@ +package com.wafflestudio.csereal.core.seminar.dto + +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity + +data class SeminarDto( + val id: Long, + val title: String, + val description: String, + val introduction: String, + val category: String, + val name: String, + val speakerUrl: String?, + val speakerTitle: String?, + val affiliation: String, + val affiliationUrl: String?, + val startDate: String?, + val startTime: String?, + val endDate: String?, + val endTime: String?, + val location: String, + val host: String?, + // val profileImage: File, + // val seminarFile: File, + val additionalNote: String?, + val isPublic: Boolean, + val isSlide: Boolean, + val prevId: Long?, + val prevTitle: String?, + val nextId: Long?, + val nextTitle: String? +) { + + companion object { + fun of(entity: SeminarEntity, prevNext: Array?): SeminarDto = entity.run { + SeminarDto( + id = this.id, + title = this.title, + description = this.description, + introduction = this.introduction, + category = this.category, + name = this.name, + speakerUrl = this.speakerUrl, + speakerTitle = this.speakerTitle, + affiliation = this.affiliation, + affiliationUrl = this.affiliationUrl, + startDate = this.startDate, + startTime = this.startTime, + endDate = this.endDate, + endTime = this.endTime, + location = this.location, + host = this.host, + additionalNote = this.additionalNote, + isPublic = this.isPublic, + isSlide = this.isSlide, + prevId = prevNext?.get(0)?.id, + prevTitle = prevNext?.get(0)?.title, + nextId = prevNext?.get(1)?.id, + nextTitle = prevNext?.get(1)?.title + ) + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt new file mode 100644 index 00000000..ffcabf19 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/csereal/core/seminar/service/SeminarService.kt @@ -0,0 +1,53 @@ +package com.wafflestudio.csereal.core.seminar.service + +import com.wafflestudio.csereal.common.CserealException +import com.wafflestudio.csereal.core.seminar.database.SeminarEntity +import com.wafflestudio.csereal.core.seminar.database.SeminarRepository +import com.wafflestudio.csereal.core.seminar.dto.SeminarDto +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +interface SeminarService { + fun createSeminar(request: SeminarDto): SeminarDto + fun readSeminar(seminarId: Long, keyword: String?): SeminarDto + fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto +} + +@Service +class SeminarServiceImpl( + private val seminarRepository: SeminarRepository +) : SeminarService { + + @Transactional + override fun createSeminar(request: SeminarDto): SeminarDto { + val newSeminar = SeminarEntity.of(request) + + seminarRepository.save(newSeminar) + + return SeminarDto.of(newSeminar, null) + } + + @Transactional(readOnly = true) + override fun readSeminar(seminarId: Long, keyword: String?): SeminarDto { + val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) + ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다.(seminarId: $seminarId)") + + if (seminar.isDeleted) throw CserealException.Csereal400("삭제된 세미나입니다. (seminarId: $seminarId)") + + val prevNext = seminarRepository.findPrevNextId(seminarId, keyword) + + return SeminarDto.of(seminar, prevNext) + } + + @Transactional + override fun updateSeminar(seminarId: Long, request: SeminarDto): SeminarDto { + val seminar: SeminarEntity = seminarRepository.findByIdOrNull(seminarId) + ?: throw CserealException.Csereal404("존재하지 않는 세미나입니다") + if(seminar.isDeleted) throw CserealException.Csereal404("삭제된 세미나입니다. (seminarId: $seminarId)") + + seminar.update(request) + + return SeminarDto.of(seminar, null) + } +} \ No newline at end of file