From e0565f7eed16d8bcdaba1da60e162468a4f8842e Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 18 Feb 2025 15:27:39 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=EB=B3=84=20=EC=B6=94=EC=B2=9C=20=EB=8B=A8=ED=8E=B8=20?= =?UTF-8?q?-=20=EB=93=B1=EB=A1=9D,=20=EC=88=98=EC=A0=95,=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=88=9C=EC=84=9C=EB=B3=80=EA=B2=BD=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...minContentHashTagCurationItemRepository.kt | 72 +++++++++++ .../tag/AdminHashTagCurationController.kt | 63 ++++++++++ .../tag/AdminHashTagCurationRepository.kt | 72 +++++++++++ .../tag/AdminHashTagCurationService.kt | 116 ++++++++++++++++++ .../tag/AudioContentHashTagCurationRequest.kt | 17 +++ .../GetAdminContentHashTagCurationResponse.kt | 9 ++ .../GetAdminHashTagCurationItemResponse.kt | 12 ++ .../curation/tag/ContentHashTagCuration.kt | 30 +++++ 8 files changed, 391 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt new file mode 100644 index 0000000..17173da --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminContentHashTagCurationItemRepository.kt @@ -0,0 +1,72 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCurationItem +import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCuration.contentHashTagCuration +import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCurationItem.contentHashTagCurationItem +import org.springframework.beans.factory.annotation.Value +import org.springframework.data.jpa.repository.JpaRepository + +interface AdminContentHashTagCurationItemRepository : + JpaRepository, + AdminContentHashTagCurationItemQueryRepository + +interface AdminContentHashTagCurationItemQueryRepository { + fun getContentHashTagCurationItemList(curationId: Long): List + fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): ContentHashTagCurationItem? + fun findByCurationIdAndItemId(curationId: Long, itemId: Long): ContentHashTagCurationItem? +} + +class AdminContentHashTagCurationItemQueryRepositoryImpl( + val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) : AdminContentHashTagCurationItemQueryRepository { + override fun getContentHashTagCurationItemList(curationId: Long): List { + return queryFactory + .select( + QGetAdminHashTagCurationItemResponse( + contentHashTagCurationItem.id, + audioContent.title, + audioContent.detail, + audioContent.coverImage.prepend("/").prepend(imageHost), + audioContent.member.nickname.coalesce(""), + audioContent.isAdult + ) + ) + .from(contentHashTagCurationItem) + .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) + .innerJoin(contentHashTagCurationItem.content, audioContent) + .where( + contentHashTagCuration.id.eq(curationId), + contentHashTagCurationItem.isActive.isTrue, + audioContent.isActive.isTrue + ) + .orderBy(contentHashTagCurationItem.orders.asc()) + .fetch() + } + + override fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): ContentHashTagCurationItem? { + return queryFactory + .selectFrom(contentHashTagCurationItem) + .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) + .innerJoin(contentHashTagCurationItem.content, audioContent) + .where( + contentHashTagCuration.id.eq(curationId), + audioContent.id.eq(contentId) + ) + .fetchFirst() + } + + override fun findByCurationIdAndItemId(curationId: Long, itemId: Long): ContentHashTagCurationItem? { + return queryFactory.selectFrom(contentHashTagCurationItem) + .innerJoin(contentHashTagCurationItem.curation, contentHashTagCuration) + .where( + contentHashTagCuration.id.eq(curationId), + contentHashTagCurationItem.id.eq(itemId) + ) + .fetchFirst() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt new file mode 100644 index 0000000..de905b7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationController.kt @@ -0,0 +1,63 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import kr.co.vividnext.sodalive.admin.content.curation.AddItemToCurationRequest +import kr.co.vividnext.sodalive.admin.content.curation.RemoveItemInCurationRequest +import kr.co.vividnext.sodalive.admin.content.curation.UpdateCurationItemOrdersRequest +import kr.co.vividnext.sodalive.common.ApiResponse +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.PutMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/admin/audio-content/tag/curation") +@PreAuthorize("hasRole('ADMIN')") +class AdminHashTagCurationController(private val service: AdminHashTagCurationService) { + @GetMapping + fun getContentHashTagCurationList() = ApiResponse.ok(service.getContentHashTagCurationList()) + + @PostMapping + fun createContentHashTagCuration( + @RequestBody request: CreateContentHashTagCurationRequest + ) = ApiResponse.ok(service.createContentHashTagCuration(request)) + + @PutMapping + fun updateContentHashTagCuration( + @RequestBody request: UpdateContentHashTagCurationRequest + ) = ApiResponse.ok(service.updateContentHashTagCuration(request)) + + @PutMapping("/orders") + fun updateContentHashTagCurationOrders( + @RequestBody request: UpdateContentHashTagCurationOrderRequest + ) = ApiResponse.ok(service.updateContentHashTagCurationOrders(request.ids), "수정되었습니다.") + + @GetMapping("/items") + fun getHashTagCurationItemList( + @RequestParam curationId: Long + ) = ApiResponse.ok(service.getHashTagCurationItemList(curationId = curationId)) + + @GetMapping("/search/content") + fun searchHashTagCurationContentItem( + @RequestParam curationId: Long, + @RequestParam searchWord: String + ) = ApiResponse.ok(service.searchHashTagCurationContentItem(curationId, searchWord)) + + @PostMapping("/add/item") + fun addItemToHashTagCuration( + @RequestBody request: AddItemToCurationRequest + ) = ApiResponse.ok(service.addItemToHashTagCuration(request), "큐레이션 아이템을 등록했습니다.") + + @PutMapping("/remove/item") + fun removeItemInHashTagCuration( + @RequestBody request: RemoveItemInCurationRequest + ) = ApiResponse.ok(service.removeItemInHashTagCuration(request), "큐레이션 아이템을 제거했습니다.") + + @PutMapping("/orders/item") + fun updateItemInHashTagCurationOrders( + @RequestBody request: UpdateCurationItemOrdersRequest + ) = ApiResponse.ok(service.updateItemInHashTagCurationOrders(request), "수정되었습니다.") +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt new file mode 100644 index 0000000..d58329c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationRepository.kt @@ -0,0 +1,72 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.admin.content.curation.QSearchCurationItemResponse +import kr.co.vividnext.sodalive.admin.content.curation.SearchCurationItemResponse +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCuration +import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCuration.contentHashTagCuration +import kr.co.vividnext.sodalive.content.main.curation.tag.QContentHashTagCurationItem.contentHashTagCurationItem +import org.springframework.beans.factory.annotation.Value +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +interface AdminHashTagCurationRepository : + JpaRepository, + AdminHashTagCurationQueryRepository + +interface AdminHashTagCurationQueryRepository { + fun getContentHashTagCurationList(): List + fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List +} + +@Repository +class AdminHashTagCurationQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) : AdminHashTagCurationQueryRepository { + override fun getContentHashTagCurationList(): List { + return queryFactory + .select( + QGetAdminContentHashTagCurationResponse( + contentHashTagCuration.id, + contentHashTagCuration.tag, + contentHashTagCuration.isAdult + ) + ) + .from(contentHashTagCuration) + .where(contentHashTagCuration.isActive.isTrue) + .orderBy(contentHashTagCuration.orders.asc()) + .fetch() + } + + override fun searchHashTagCurationContentItem( + curationId: Long, + searchWord: String + ): List { + return queryFactory + .select( + QSearchCurationItemResponse( + audioContent.id, + audioContent.title, + audioContent.coverImage.prepend("/").prepend(imageHost) + ) + ) + .from(audioContent) + .leftJoin(contentHashTagCurationItem) + .on( + audioContent.id.eq(contentHashTagCurationItem.content.id) + .and(contentHashTagCurationItem.curation.id.eq(curationId)) + ) + .where( + audioContent.duration.isNotNull + .and(audioContent.member.isNotNull) + .and(audioContent.isActive.isTrue) + .and(audioContent.title.contains(searchWord)) + .and(contentHashTagCurationItem.id.isNull) + ) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt new file mode 100644 index 0000000..741d07d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AdminHashTagCurationService.kt @@ -0,0 +1,116 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import kr.co.vividnext.sodalive.admin.content.curation.AddItemToCurationRequest +import kr.co.vividnext.sodalive.admin.content.curation.RemoveItemInCurationRequest +import kr.co.vividnext.sodalive.admin.content.curation.SearchCurationItemResponse +import kr.co.vividnext.sodalive.admin.content.curation.UpdateCurationItemOrdersRequest +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCuration +import kr.co.vividnext.sodalive.content.main.curation.tag.ContentHashTagCurationItem +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class AdminHashTagCurationService( + private val repository: AdminHashTagCurationRepository, + private val itemRepository: AdminContentHashTagCurationItemRepository, + private val audioContentRepository: AudioContentRepository +) { + @Transactional + fun createContentHashTagCuration(request: CreateContentHashTagCurationRequest) { + repository.save( + ContentHashTagCuration( + tag = request.tag, + isAdult = request.isAdult + ) + ) + } + + @Transactional + fun updateContentHashTagCuration(request: UpdateContentHashTagCurationRequest) { + val hashTagCuration = repository.findByIdOrNull(id = request.id) + ?: throw SodaException("잘못된 요청입니다.") + + if (request.tag != null) { + hashTagCuration.tag = request.tag + } + + if (request.isAdult != null) { + hashTagCuration.isAdult = request.isAdult + } + + if (request.isActive != null) { + hashTagCuration.isActive = request.isActive + } + } + + @Transactional + fun updateContentHashTagCurationOrders(ids: List) { + for (index in ids.indices) { + val contentHashTagCuration = repository.findByIdOrNull(ids[index]) + + if (contentHashTagCuration != null) { + contentHashTagCuration.orders = index + 1 + } + } + } + + fun getContentHashTagCurationList(): List { + return repository.getContentHashTagCurationList() + } + + fun getHashTagCurationItemList(curationId: Long): List { + return itemRepository.getContentHashTagCurationItemList(curationId) + } + + fun searchHashTagCurationContentItem(curationId: Long, searchWord: String): List { + return repository.searchHashTagCurationContentItem(curationId, searchWord) + } + + @Transactional + fun addItemToHashTagCuration(request: AddItemToCurationRequest) { + val curation = repository.findByIdOrNull(id = request.curationId) + ?: throw SodaException("잘못된 요청입니다.") + + request.itemIdList.forEach { contentId -> + val audioContent = audioContentRepository.findByIdAndActive(contentId) + + if (audioContent != null) { + val item = itemRepository.findByCurationIdAndContentId( + curationId = request.curationId, + contentId = audioContent.id + ) ?: ContentHashTagCurationItem() + item.curation = curation + item.content = audioContent + itemRepository.save(item) + } + } + } + + @Transactional + fun removeItemInHashTagCuration(request: RemoveItemInCurationRequest) { + val item = itemRepository.findByCurationIdAndItemId( + curationId = request.curationId, + itemId = request.itemId + ) + + item?.isActive = false + } + + @Transactional + fun updateItemInHashTagCurationOrders(request: UpdateCurationItemOrdersRequest) { + val ids = request.itemIds + for (index in ids.indices) { + val item = itemRepository.findByCurationIdAndItemId( + curationId = request.curationId, + itemId = ids[index] + ) + + if (item != null) { + item.orders = index + 1 + } + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt new file mode 100644 index 0000000..22970b6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/AudioContentHashTagCurationRequest.kt @@ -0,0 +1,17 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +data class CreateContentHashTagCurationRequest( + val tag: String, + val isAdult: Boolean +) + +data class UpdateContentHashTagCurationRequest( + val id: Long, + val tag: String?, + val isAdult: Boolean?, + val isActive: Boolean? +) + +data class UpdateContentHashTagCurationOrderRequest( + val ids: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt new file mode 100644 index 0000000..c41daf6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminContentHashTagCurationResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import com.querydsl.core.annotations.QueryProjection + +data class GetAdminContentHashTagCurationResponse @QueryProjection constructor( + val id: Long, + val tag: String, + val isAdult: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt new file mode 100644 index 0000000..b42d042 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/tag/GetAdminHashTagCurationItemResponse.kt @@ -0,0 +1,12 @@ +package kr.co.vividnext.sodalive.admin.content.curation.tag + +import com.querydsl.core.annotations.QueryProjection + +data class GetAdminHashTagCurationItemResponse @QueryProjection constructor( + val id: Long, + val title: String, + val desc: String, + val coverImageUrl: String, + val creatorNickname: String, + val isAdult: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt new file mode 100644 index 0000000..f1acaf2 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/tag/ContentHashTagCuration.kt @@ -0,0 +1,30 @@ +package kr.co.vividnext.sodalive.content.main.curation.tag + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.content.AudioContent +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +@Entity +data class ContentHashTagCuration( + var tag: String, + var orders: Int = 1, + var isAdult: Boolean = false, + var isActive: Boolean = true +) : BaseEntity() + +@Entity +data class ContentHashTagCurationItem( + var orders: Int = 1, + var isActive: Boolean = true +) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "curation_id", nullable = false) + var curation: ContentHashTagCuration? = null + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "content_id", nullable = false) + var content: AudioContent? = null +}