From 705459ee907bfdb64cca5bec6e32239124777821 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 31 Jan 2025 21:58:31 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=ED=81=90?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=EC=95=84=EC=9D=B4=ED=85=9C=20-?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C,=20=EC=B6=94=EA=B0=80,=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=BD=98=ED=85=90=EC=B8=A0=20=EA=B2=80=EC=83=89,?= =?UTF-8?q?=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=EA=B2=80=EC=83=89=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 --- .../admin/content/AdminContentRepository.kt | 11 +++ .../curation/AddItemToCurationRequest.kt | 6 ++ .../AdminContentCurationController.kt | 27 +++++ .../AdminContentCurationItemRepository.kt | 46 +++++++++ .../AdminContentCurationRepository.kt | 99 ++++++++++++++++++- .../curation/AdminContentCurationService.kt | 69 ++++++++++++- .../curation/GetCurationItemResponse.kt | 12 +++ .../curation/RemoveItemInCurationRequest.kt | 6 ++ .../curation/SearchCurationItemResponse.kt | 9 ++ .../series/AdminContentSeriesRepository.kt | 11 +++ .../main/curation/AudioContentCurationItem.kt | 33 +++++++ 11 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AddItemToCurationRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/GetCurationItemResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/RemoveItemInCurationRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/SearchCurationItemResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationItem.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt index d01218d..3c402c2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/AdminContentRepository.kt @@ -32,6 +32,7 @@ interface AdminAudioContentQueryRepository { ): List fun getHashTagList(audioContentId: Long): List + fun findByIdAndActiveTrue(audioContentId: Long): AudioContent? } class AdminAudioContentQueryRepositoryImpl( @@ -143,6 +144,16 @@ class AdminAudioContentQueryRepositoryImpl( .fetch() } + override fun findByIdAndActiveTrue(audioContentId: Long): AudioContent? { + return queryFactory + .select(audioContent) + .where( + audioContent.id.eq(audioContentId), + audioContent.isActive.isTrue + ) + .fetchFirst() + } + private fun formattedDateExpression( dateTime: DateTimePath, format: String = "%Y-%m-%d" diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AddItemToCurationRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AddItemToCurationRequest.kt new file mode 100644 index 0000000..d344da7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AddItemToCurationRequest.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +data class AddItemToCurationRequest( + val curationId: Long, + val contentIdList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt index 639aa3d..aeb1863 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationController.kt @@ -33,4 +33,31 @@ class AdminContentCurationController(private val service: AdminContentCurationSe fun getContentCurationList( @RequestParam tabId: Long ) = ApiResponse.ok(service.getContentCurationList(tabId = tabId)) + + @GetMapping("/items") + fun getCurationItems( + @RequestParam curationId: Long + ) = ApiResponse.ok(service.getCurationItem(curationId = curationId)) + + @GetMapping("/search/content") + fun searchCurationContentItem( + @RequestParam curationId: Long, + @RequestParam searchWord: String + ) = ApiResponse.ok(service.searchCurationContentItem(curationId, searchWord)) + + @GetMapping("/search/series") + fun searchCurationSeriesItem( + @RequestParam curationId: Long, + @RequestParam searchWord: String + ) = ApiResponse.ok(service.searchCurationSeriesItem(curationId, searchWord)) + + @PostMapping("/add/item") + fun addItemToCuration(@RequestBody request: AddItemToCurationRequest) { + ApiResponse.ok(service.addItemToCuration(request), "큐레이션 아이템을 등록했습니다.") + } + + @PostMapping("/remove/item") + fun removeItemInCuration(@RequestBody request: RemoveItemInCurationRequest) { + ApiResponse.ok(service.removeItemInCuration(request), "큐레이션 아이템을 제거했습니다.") + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt new file mode 100644 index 0000000..bcfe675 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationItemRepository.kt @@ -0,0 +1,46 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationItem +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCurationItem.audioContentCurationItem +import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series +import org.springframework.data.jpa.repository.JpaRepository + +interface AdminContentCurationItemRepository : + JpaRepository, + AdminContentCurationItemQueryRepository + +interface AdminContentCurationItemQueryRepository { + fun findByCurationIdAndSeriesId(curationId: Long, seriesId: Long?): AudioContentCurationItem? + fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): AudioContentCurationItem? +} + +class AdminContentCurationItemQueryRepositoryImpl( + val queryFactory: JPAQueryFactory +) : AdminContentCurationItemQueryRepository { + override fun findByCurationIdAndSeriesId(curationId: Long, seriesId: Long?): AudioContentCurationItem? { + return queryFactory + .selectFrom(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .innerJoin(audioContentCurationItem.series, series) + .where( + audioContentCurationItem.curation.id.eq(curationId), + audioContentCurationItem.series.id.eq(seriesId) + ) + .fetchFirst() + } + + override fun findByCurationIdAndContentId(curationId: Long, contentId: Long?): AudioContentCurationItem? { + return queryFactory + .selectFrom(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .innerJoin(audioContentCurationItem.content, audioContent) + .where( + audioContentCurationItem.curation.id.eq(curationId), + audioContentCurationItem.content.id.eq(contentId) + ) + .fetchFirst() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationRepository.kt index f6dc40b..d0277eb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationRepository.kt @@ -1,9 +1,14 @@ package kr.co.vividnext.sodalive.admin.content.curation import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationItem import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCurationItem.audioContentCurationItem import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab +import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series +import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -15,11 +20,18 @@ interface AdminContentCurationRepository : interface AdminContentCurationQueryRepository { fun getAudioContentCurationList(tabId: Long): List fun findByIdAndActive(id: Long): AudioContentCuration? + fun findByCurationIdAndItemId(curationId: Long, itemId: Long): AudioContentCurationItem? + fun getAudioContentCurationItemList(curationId: Long): List + fun searchCurationContentItem(curationId: Long, searchWord: String): List + fun searchCurationSeriesItem(curationId: Long, searchWord: String): List } @Repository class AdminContentCurationQueryRepositoryImpl( - private val queryFactory: JPAQueryFactory + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String ) : AdminContentCurationQueryRepository { override fun getAudioContentCurationList(tabId: Long): List { return queryFactory @@ -52,4 +64,89 @@ class AdminContentCurationQueryRepositoryImpl( ) .fetchFirst() } + + override fun findByCurationIdAndItemId(curationId: Long, itemId: Long): AudioContentCurationItem? { + return queryFactory.selectFrom(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .where(audioContentCuration.id.eq(curationId), audioContentCurationItem.id.eq(itemId)) + .fetchFirst() + } + + override fun getAudioContentCurationItemList(curationId: Long): List { + return queryFactory + .select( + QGetCurationItemResponse( + audioContentCurationItem.id, + audioContent.title.coalesce(series.title), + audioContent.detail.coalesce(series.introduction), + audioContent.coverImage.coalesce(series.coverImage).prepend("/").prepend(imageHost), + audioContent.member.nickname.coalesce(series.member.nickname).coalesce(""), + audioContent.isAdult.coalesce(series.isAdult) + ) + ) + .from(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .leftJoin(audioContentCurationItem.series, series) + .leftJoin(audioContentCurationItem.content, audioContent) + .where( + audioContentCuration.id.eq(curationId), + audioContentCurationItem.isActive.isTrue + ) + .fetch() + } + + override fun searchCurationContentItem( + curationId: Long, + searchWord: String + ): List { + return queryFactory + .select( + QSearchCurationItemResponse( + audioContent.id, + audioContent.title, + audioContent.coverImage.prepend("/").prepend(imageHost) + ) + ) + .from(audioContent) + .leftJoin(audioContentCurationItem) + .on( + audioContent.id.eq(audioContentCurationItem.content.id) + .and(audioContentCurationItem.curation.id.eq(curationId)) + ) + .where( + audioContent.duration.isNotNull + .and(audioContent.member.isNotNull) + .and(audioContent.isActive.isTrue) + .and(audioContent.title.contains(searchWord)) + .and(audioContentCurationItem.id.isNull) + ) + .fetch() + } + + override fun searchCurationSeriesItem( + curationId: Long, + searchWord: String + ): List { + return queryFactory + .select( + QSearchCurationItemResponse( + series.id, + series.title, + series.coverImage.prepend("/").prepend(imageHost) + ) + ) + .from(series) + .leftJoin(audioContentCurationItem) + .on( + series.id.eq(audioContentCurationItem.series.id) + .and(audioContentCurationItem.curation.id.eq(curationId)) + ) + .where( + series.isActive.isTrue + .and(series.member.isNotNull) + .and(series.title.contains(searchWord)) + .and(audioContentCurationItem.id.isNull) + ) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt index e914f08..40a5404 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/AdminContentCurationService.kt @@ -1,8 +1,11 @@ package kr.co.vividnext.sodalive.admin.content.curation +import kr.co.vividnext.sodalive.admin.content.AdminContentRepository +import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationItem import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -10,7 +13,10 @@ import org.springframework.transaction.annotation.Transactional @Service class AdminContentCurationService( private val repository: AdminContentCurationRepository, - private val contentMainTabRepository: AdminContentMainTabRepository + private val contentMainTabRepository: AdminContentMainTabRepository, + private val seriesRepository: AdminContentSeriesRepository, + private val contentRepository: AdminContentRepository, + private val contentCurationItemRepository: AdminContentCurationItemRepository ) { @Transactional fun createContentCuration(request: CreateContentCurationRequest) { @@ -76,4 +82,65 @@ class AdminContentCurationService( fun getContentCurationList(tabId: Long): List { return repository.getAudioContentCurationList(tabId = tabId) } + + fun getCurationItem(curationId: Long): List { + return repository.getAudioContentCurationItemList(curationId) + } + + fun searchCurationContentItem(curationId: Long, searchWord: String): List { + return repository.searchCurationContentItem(curationId, searchWord) + } + + fun searchCurationSeriesItem(curationId: Long, searchWord: String): List { + return repository.searchCurationSeriesItem(curationId, searchWord) + } + + @Transactional + fun addItemToCuration(request: AddItemToCurationRequest) { + // 큐레이션 조회 + val audioContentCuration = repository.findByIdOrNull(id = request.curationId) + ?: throw SodaException("잘못된 요청입니다.") + + if (audioContentCuration.isSeries) { + request.contentIdList.forEach { seriesId -> + val series = seriesRepository.findByIdAndActiveTrue(seriesId) + + if (series != null) { + val item = contentCurationItemRepository.findByCurationIdAndSeriesId( + curationId = request.curationId, + seriesId = series.id + ) ?: AudioContentCurationItem() + item.curation = audioContentCuration + item.series = series + item.isActive = true + contentCurationItemRepository.save(item) + } + } + } else { + request.contentIdList.forEach { contentId -> + val audioContent = contentRepository.findByIdAndActiveTrue(contentId) + + if (audioContent != null) { + val item = contentCurationItemRepository.findByCurationIdAndContentId( + curationId = request.curationId, + contentId = audioContent.id + ) ?: AudioContentCurationItem() + item.curation = audioContentCuration + item.content = audioContent + item.isActive = true + contentCurationItemRepository.save(item) + } + } + } + } + + @Transactional + fun removeItemInCuration(request: RemoveItemInCurationRequest) { + val audioContentCurationItem = repository.findByCurationIdAndItemId( + curationId = request.curationId, + itemId = request.itemId + ) + + audioContentCurationItem?.isActive = false + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/GetCurationItemResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/GetCurationItemResponse.kt new file mode 100644 index 0000000..b293643 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/GetCurationItemResponse.kt @@ -0,0 +1,12 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +import com.querydsl.core.annotations.QueryProjection + +data class GetCurationItemResponse @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/admin/content/curation/RemoveItemInCurationRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/RemoveItemInCurationRequest.kt new file mode 100644 index 0000000..c9b108f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/RemoveItemInCurationRequest.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +data class RemoveItemInCurationRequest( + val curationId: Long, + val itemId: Long +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/SearchCurationItemResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/SearchCurationItemResponse.kt new file mode 100644 index 0000000..f373893 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/curation/SearchCurationItemResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.admin.content.curation + +import com.querydsl.core.annotations.QueryProjection + +data class SearchCurationItemResponse @QueryProjection constructor( + val id: Long, + val title: String, + val coverImageUrl: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/AdminContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/AdminContentSeriesRepository.kt index c251e5b..3e4b7ea 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/AdminContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/AdminContentSeriesRepository.kt @@ -22,6 +22,7 @@ interface AdminContentSeriesQueryRepository { ): List fun searchSeriesList(searchWord: String): List + fun findByIdAndActiveTrue(seriesId: Long): Series? } class AdminContentSeriesQueryRepositoryImpl( @@ -97,4 +98,14 @@ class AdminContentSeriesQueryRepositoryImpl( .orderBy(series.id.desc()) .fetch() } + + override fun findByIdAndActiveTrue(seriesId: Long): Series? { + return queryFactory + .selectFrom(series) + .where( + series.id.eq(seriesId), + series.isActive.isTrue + ) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationItem.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationItem.kt new file mode 100644 index 0000000..7eddaa4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationItem.kt @@ -0,0 +1,33 @@ +package kr.co.vividnext.sodalive.content.main.curation + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.content.AudioContent +import kr.co.vividnext.sodalive.creator.admin.content.series.Series +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne +import javax.persistence.Table + +@Entity +@Table(name = "content_curation_item") +data class AudioContentCurationItem( + @Column(nullable = false) + var orders: Int = 1, + + @Column(nullable = false) + var isActive: Boolean = true +) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "curation_id", nullable = false) + var curation: AudioContentCuration? = null + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "content_id", nullable = true) + var content: AudioContent? = null + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "series_id", nullable = true) + var series: Series? = null +}