| @@ -41,8 +41,8 @@ class AdminCalculateQueryRepository(private val queryFactory: JPAQueryFactory) { | ||||
|             .on(member.id.eq(creatorSettlementRatio.member.id)) | ||||
|             .where( | ||||
|                 useCan.isRefund.isFalse | ||||
|                     .and(liveRoom.beginDateTime.goe(startDate)) | ||||
|                     .and(liveRoom.beginDateTime.loe(endDate)) | ||||
|                     .and(useCan.createdAt.goe(startDate)) | ||||
|                     .and(useCan.createdAt.loe(endDate)) | ||||
|             ) | ||||
|             .groupBy(liveRoom.id, useCan.canUsage, creatorSettlementRatio.liveSettlementRatio) | ||||
|             .orderBy(member.nickname.desc(), liveRoom.id.desc(), useCan.canUsage.desc(), formattedDate.desc()) | ||||
|   | ||||
| @@ -42,6 +42,9 @@ class AdminContentController(private val service: AdminContentService) { | ||||
|     fun modifyAudioContent( | ||||
|         @RequestBody request: UpdateAdminContentRequest | ||||
|     ) = ApiResponse.ok(service.updateAudioContent(request)) | ||||
|  | ||||
|     @GetMapping("/main/tab") | ||||
|     fun getContentMainTabList() = ApiResponse.ok(service.getContentMainTabList()) | ||||
| } | ||||
|  | ||||
| enum class ContentReleaseStatus { | ||||
|   | ||||
| @@ -32,6 +32,7 @@ interface AdminAudioContentQueryRepository { | ||||
|     ): List<GetAdminContentListItem> | ||||
|  | ||||
|     fun getHashTagList(audioContentId: Long): List<String> | ||||
|     fun findByIdAndActiveTrue(audioContentId: Long): AudioContent? | ||||
| } | ||||
|  | ||||
| class AdminAudioContentQueryRepositoryImpl( | ||||
| @@ -143,6 +144,16 @@ class AdminAudioContentQueryRepositoryImpl( | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun findByIdAndActiveTrue(audioContentId: Long): AudioContent? { | ||||
|         return queryFactory | ||||
|             .selectFrom(audioContent) | ||||
|             .where( | ||||
|                 audioContent.id.eq(audioContentId), | ||||
|                 audioContent.isActive.isTrue | ||||
|             ) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     private fun formattedDateExpression( | ||||
|         dateTime: DateTimePath<LocalDateTime>, | ||||
|         format: String = "%Y-%m-%d" | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content | ||||
|  | ||||
| import kr.co.vividnext.sodalive.admin.content.curation.AdminContentCurationRepository | ||||
| import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository | ||||
| import kr.co.vividnext.sodalive.admin.content.theme.AdminContentThemeRepository | ||||
| import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentMainTabItem | ||||
| import org.springframework.data.domain.Pageable | ||||
| import org.springframework.data.repository.findByIdOrNull | ||||
| import org.springframework.stereotype.Service | ||||
| @@ -14,7 +16,8 @@ class AdminContentService( | ||||
|     private val repository: AdminContentRepository, | ||||
|     private val themeRepository: AdminContentThemeRepository, | ||||
|     private val audioContentCloudFront: AudioContentCloudFront, | ||||
|     private val curationRepository: AdminContentCurationRepository | ||||
|     private val curationRepository: AdminContentCurationRepository, | ||||
|     private val contentMainTabRepository: AdminContentMainTabRepository | ||||
| ) { | ||||
|     fun getAudioContentList(status: ContentReleaseStatus, pageable: Pageable): GetAdminContentListResponse { | ||||
|         val totalCount = repository.getAudioContentTotalCount(status = status) | ||||
| @@ -118,4 +121,8 @@ class AdminContentService( | ||||
|             audioContent.theme = theme | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getContentMainTabList(): List<GetContentMainTabItem> { | ||||
|         return contentMainTabRepository.findAllByActiveIsTrue() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,7 @@ 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.RequestPart | ||||
| import org.springframework.web.bind.annotation.RestController | ||||
| import org.springframework.web.multipart.MultipartFile | ||||
| @@ -33,5 +34,7 @@ class AdminContentBannerController(private val service: AdminContentBannerServic | ||||
|     ) = ApiResponse.ok(service.updateBannerOrders(request.ids), "수정되었습니다.") | ||||
|  | ||||
|     @GetMapping | ||||
|     fun getAudioContentMainBannerList() = ApiResponse.ok(service.getAudioContentMainBannerList()) | ||||
|     fun getAudioContentMainBannerList( | ||||
|         @RequestParam(value = "tabId", required = false) tabId: Long? = null | ||||
|     ) = ApiResponse.ok(service.getAudioContentMainBannerList(tabId = tabId)) | ||||
| } | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.admin.content.banner | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | ||||
| import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner | ||||
| import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||
| import kr.co.vividnext.sodalive.event.QEvent.event | ||||
| import kr.co.vividnext.sodalive.member.QMember.member | ||||
| @@ -14,7 +15,7 @@ import org.springframework.stereotype.Repository | ||||
| interface AdminContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AdminContentBannerQueryRepository | ||||
|  | ||||
| interface AdminContentBannerQueryRepository { | ||||
|     fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> | ||||
|     fun getAudioContentMainBannerList(tabId: Long = 1): List<GetAdminContentBannerResponse> | ||||
| } | ||||
|  | ||||
| class AdminContentBannerQueryRepositoryImpl( | ||||
| @@ -22,11 +23,20 @@ class AdminContentBannerQueryRepositoryImpl( | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val cloudFrontHost: String | ||||
| ) : AdminContentBannerQueryRepository { | ||||
|     override fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> { | ||||
|     override fun getAudioContentMainBannerList(tabId: Long): List<GetAdminContentBannerResponse> { | ||||
|         var where = audioContentBanner.isActive.isTrue | ||||
|  | ||||
|         where = if (tabId <= 1L) { | ||||
|             where.and(audioContentMainTab.id.isNull) | ||||
|         } else { | ||||
|             where.and(audioContentMainTab.id.eq(tabId)) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminContentBannerResponse( | ||||
|                     audioContentBanner.id, | ||||
|                     audioContentBanner.tab.id.coalesce(1), | ||||
|                     audioContentBanner.type, | ||||
|                     audioContentBanner.thumbnailImage.prepend("/").prepend(cloudFrontHost), | ||||
|                     audioContentBanner.event.id, | ||||
| @@ -43,7 +53,8 @@ class AdminContentBannerQueryRepositoryImpl( | ||||
|             .leftJoin(audioContentBanner.event, event) | ||||
|             .leftJoin(audioContentBanner.creator, member) | ||||
|             .leftJoin(audioContentBanner.series, series) | ||||
|             .where(audioContentBanner.isActive.isTrue) | ||||
|             .leftJoin(audioContentBanner.tab, audioContentMainTab) | ||||
|             .where(where) | ||||
|             .orderBy(audioContentBanner.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.admin.content.banner | ||||
|  | ||||
| import com.fasterxml.jackson.databind.ObjectMapper | ||||
| import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository | ||||
| import kr.co.vividnext.sodalive.admin.content.tab.AdminContentMainTabRepository | ||||
| import kr.co.vividnext.sodalive.aws.s3.S3Uploader | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | ||||
| @@ -22,6 +23,7 @@ class AdminContentBannerService( | ||||
|     private val memberRepository: MemberRepository, | ||||
|     private val seriesRepository: AdminContentSeriesRepository, | ||||
|     private val eventRepository: EventRepository, | ||||
|     private val contentMainTabRepository: AdminContentMainTabRepository, | ||||
|     private val objectMapper: ObjectMapper, | ||||
|  | ||||
|     @Value("\${cloud.aws.s3.bucket}") | ||||
| @@ -64,12 +66,19 @@ class AdminContentBannerService( | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         val tab = if (request.tabId !== null) { | ||||
|             contentMainTabRepository.findByIdOrNull(request.tabId) | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|  | ||||
|         val audioContentBanner = AudioContentBanner(type = request.type) | ||||
|         audioContentBanner.link = request.link | ||||
|         audioContentBanner.isAdult = request.isAdult | ||||
|         audioContentBanner.event = event | ||||
|         audioContentBanner.creator = creator | ||||
|         audioContentBanner.series = series | ||||
|         audioContentBanner.tab = tab | ||||
|         repository.save(audioContentBanner) | ||||
|  | ||||
|         val fileName = generateFileName() | ||||
| @@ -156,6 +165,10 @@ class AdminContentBannerService( | ||||
|  | ||||
|             audioContentBanner.type = request.type | ||||
|         } | ||||
|  | ||||
|         if (request.tabId !== null) { | ||||
|             audioContentBanner.tab = contentMainTabRepository.findByIdOrNull(request.tabId) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
| @@ -169,7 +182,7 @@ class AdminContentBannerService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> { | ||||
|         return repository.getAudioContentMainBannerList() | ||||
|     fun getAudioContentMainBannerList(tabId: Long?): List<GetAdminContentBannerResponse> { | ||||
|         return repository.getAudioContentMainBannerList(tabId = tabId ?: 1) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType | ||||
|  | ||||
| data class CreateContentBannerRequest( | ||||
|     val type: AudioContentBannerType, | ||||
|     val tabId: Long?, | ||||
|     val eventId: Long?, | ||||
|     val creatorId: Long?, | ||||
|     val seriesId: Long?, | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType | ||||
|  | ||||
| data class GetAdminContentBannerResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val tabId: Long?, | ||||
|     val type: AudioContentBannerType, | ||||
|     val thumbnailImageUrl: String, | ||||
|     val eventId: Long?, | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType | ||||
| data class UpdateContentBannerRequest( | ||||
|     val id: Long, | ||||
|     val type: AudioContentBannerType?, | ||||
|     val tabId: Long?, | ||||
|     val eventId: Long?, | ||||
|     val creatorId: Long?, | ||||
|     val seriesId: Long?, | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation | ||||
|  | ||||
| data class AddItemToCurationRequest( | ||||
|     val curationId: Long, | ||||
|     val itemIdList: List<Long> | ||||
| ) | ||||
| @@ -7,6 +7,7 @@ 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 | ||||
| @@ -29,5 +30,34 @@ class AdminContentCurationController(private val service: AdminContentCurationSe | ||||
|     ) = ApiResponse.ok(service.updateContentCurationOrders(request.ids), "수정되었습니다.") | ||||
|  | ||||
|     @GetMapping | ||||
|     fun getContentCurationList() = ApiResponse.ok(service.getContentCurationList()) | ||||
|     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), "큐레이션 아이템을 제거했습니다.") | ||||
| } | ||||
|   | ||||
| @@ -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<AudioContentCurationItem, Long>, | ||||
|     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() | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +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 | ||||
|  | ||||
| @@ -12,26 +18,39 @@ interface AdminContentCurationRepository : | ||||
|     AdminContentCurationQueryRepository | ||||
|  | ||||
| interface AdminContentCurationQueryRepository { | ||||
|     fun getAudioContentCurationList(): List<GetAdminContentCurationResponse> | ||||
|     fun getAudioContentCurationList(tabId: Long): List<GetAdminContentCurationResponse> | ||||
|     fun findByIdAndActive(id: Long): AudioContentCuration? | ||||
|     fun findByCurationIdAndItemId(curationId: Long, itemId: Long): AudioContentCurationItem? | ||||
|     fun getAudioContentCurationItemList(curationId: Long): List<GetCurationItemResponse> | ||||
|     fun searchCurationContentItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> | ||||
|     fun searchCurationSeriesItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> | ||||
| } | ||||
|  | ||||
| @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(): List<GetAdminContentCurationResponse> { | ||||
|     override fun getAudioContentCurationList(tabId: Long): List<GetAdminContentCurationResponse> { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAdminContentCurationResponse( | ||||
|                     audioContentCuration.id, | ||||
|                     audioContentMainTab.id, | ||||
|                     audioContentCuration.title, | ||||
|                     audioContentCuration.description, | ||||
|                     audioContentCuration.isAdult | ||||
|                     audioContentCuration.isAdult, | ||||
|                     audioContentCuration.isSeries | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContentCuration) | ||||
|             .where(audioContentCuration.isActive.isTrue) | ||||
|             .innerJoin(audioContentCuration.tab, audioContentMainTab) | ||||
|             .where( | ||||
|                 audioContentCuration.isActive.isTrue, | ||||
|                 audioContentMainTab.id.eq(tabId) | ||||
|             ) | ||||
|             .orderBy(audioContentCuration.orders.asc()) | ||||
|             .fetch() | ||||
|     } | ||||
| @@ -45,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<GetCurationItemResponse> { | ||||
|         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<SearchCurationItemResponse> { | ||||
|         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<SearchCurationItemResponse> { | ||||
|         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() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,24 +1,37 @@ | ||||
| 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 | ||||
|  | ||||
| @Service | ||||
| class AdminContentCurationService( | ||||
|     private val repository: AdminContentCurationRepository | ||||
|     private val repository: AdminContentCurationRepository, | ||||
|     private val contentMainTabRepository: AdminContentMainTabRepository, | ||||
|     private val seriesRepository: AdminContentSeriesRepository, | ||||
|     private val contentRepository: AdminContentRepository, | ||||
|     private val contentCurationItemRepository: AdminContentCurationItemRepository | ||||
| ) { | ||||
|     @Transactional | ||||
|     fun createContentCuration(request: CreateContentCurationRequest) { | ||||
|         repository.save( | ||||
|             AudioContentCuration( | ||||
|         val tab = contentMainTabRepository.findByIdOrNull(request.tabId) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         val curation = AudioContentCuration( | ||||
|             title = request.title, | ||||
|             description = request.description, | ||||
|                 isAdult = request.isAdult | ||||
|             ) | ||||
|             isAdult = request.isAdult, | ||||
|             isSeries = request.isSeries | ||||
|         ) | ||||
|         curation.tab = tab | ||||
|  | ||||
|         repository.save(curation) | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
| @@ -41,6 +54,18 @@ class AdminContentCurationService( | ||||
|         if (request.isActive != null) { | ||||
|             audioContentCuration.isActive = request.isActive | ||||
|         } | ||||
|  | ||||
|         if (request.isSeries != null) { | ||||
|             audioContentCuration.isSeries = request.isSeries | ||||
|         } | ||||
|  | ||||
|         if (request.tabId != null) { | ||||
|             val tab = contentMainTabRepository.findByIdOrNull(request.tabId) | ||||
|  | ||||
|             if (tab != null) { | ||||
|                 audioContentCuration.tab = tab | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
| @@ -54,7 +79,68 @@ class AdminContentCurationService( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getContentCurationList(): List<GetAdminContentCurationResponse> { | ||||
|         return repository.getAudioContentCurationList() | ||||
|     fun getContentCurationList(tabId: Long): List<GetAdminContentCurationResponse> { | ||||
|         return repository.getAudioContentCurationList(tabId = tabId) | ||||
|     } | ||||
|  | ||||
|     fun getCurationItem(curationId: Long): List<GetCurationItemResponse> { | ||||
|         return repository.getAudioContentCurationItemList(curationId) | ||||
|     } | ||||
|  | ||||
|     fun searchCurationContentItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> { | ||||
|         return repository.searchCurationContentItem(curationId, searchWord) | ||||
|     } | ||||
|  | ||||
|     fun searchCurationSeriesItem(curationId: Long, searchWord: String): List<SearchCurationItemResponse> { | ||||
|         return repository.searchCurationSeriesItem(curationId, searchWord) | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun addItemToCuration(request: AddItemToCurationRequest) { | ||||
|         // 큐레이션 조회 | ||||
|         val audioContentCuration = repository.findByIdOrNull(id = request.curationId) | ||||
|             ?: throw SodaException("잘못된 요청입니다.") | ||||
|  | ||||
|         if (audioContentCuration.isSeries) { | ||||
|             request.itemIdList.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.itemIdList.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 | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,16 +1,20 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation | ||||
|  | ||||
| data class CreateContentCurationRequest( | ||||
|     val tabId: Long, | ||||
|     val title: String, | ||||
|     val description: String, | ||||
|     val isAdult: Boolean | ||||
|     val isAdult: Boolean, | ||||
|     val isSeries: Boolean | ||||
| ) | ||||
|  | ||||
| data class UpdateContentCurationRequest( | ||||
|     val id: Long, | ||||
|     val tabId: Long?, | ||||
|     val title: String?, | ||||
|     val description: String?, | ||||
|     val isAdult: Boolean?, | ||||
|     val isSeries: Boolean?, | ||||
|     val isActive: Boolean? | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,9 @@ import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAdminContentCurationResponse @QueryProjection constructor( | ||||
|     val id: Long, | ||||
|     val tabId: Long, | ||||
|     val title: String, | ||||
|     val description: String, | ||||
|     val isAdult: Boolean | ||||
|     val isAdult: Boolean, | ||||
|     val isSeries: Boolean | ||||
| ) | ||||
|   | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -0,0 +1,6 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.curation | ||||
|  | ||||
| data class RemoveItemInCurationRequest( | ||||
|     val curationId: Long, | ||||
|     val itemId: Long | ||||
| ) | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -22,6 +22,7 @@ interface AdminContentSeriesQueryRepository { | ||||
|     ): List<GetAdminSeriesListItem> | ||||
|  | ||||
|     fun searchSeriesList(searchWord: String): List<GetAdminSearchSeriesListItem> | ||||
|     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() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,31 @@ | ||||
| package kr.co.vividnext.sodalive.admin.content.tab | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab | ||||
| import kr.co.vividnext.sodalive.content.main.tab.GetContentMainTabItem | ||||
| import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||
| import kr.co.vividnext.sodalive.content.main.tab.QGetContentMainTabItem | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
|  | ||||
| interface AdminContentMainTabRepository : JpaRepository<AudioContentMainTab, Long>, AdminContentMainTabQueryRepository | ||||
|  | ||||
| interface AdminContentMainTabQueryRepository { | ||||
|     fun findAllByActiveIsTrue(): List<GetContentMainTabItem> | ||||
| } | ||||
|  | ||||
| class AdminContentMainTabQueryRepositoryImpl( | ||||
|     private val queryFactory: JPAQueryFactory | ||||
| ) : AdminContentMainTabQueryRepository { | ||||
|     override fun findAllByActiveIsTrue(): List<GetContentMainTabItem> { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetContentMainTabItem( | ||||
|                     audioContentMainTab.id, | ||||
|                     audioContentMainTab.title | ||||
|                 ) | ||||
|             ) | ||||
|             .from(audioContentMainTab) | ||||
|             .where(audioContentMainTab.isActive.isTrue) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
| @@ -15,6 +15,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner | ||||
| import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner | ||||
| import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration | ||||
| import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration | ||||
| import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab | ||||
| import kr.co.vividnext.sodalive.content.order.QOrder.order | ||||
| import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent | ||||
| import kr.co.vividnext.sodalive.content.playlist.AudioContentPlaylistContent | ||||
| @@ -755,7 +756,8 @@ class AudioContentQueryRepositoryImpl( | ||||
|  | ||||
|         return queryFactory | ||||
|             .selectFrom(audioContentCuration) | ||||
|             .where(where) | ||||
|             .leftJoin(audioContentCuration.tab, audioContentMainTab) | ||||
|             .where(where, audioContentMainTab.id.isNull) | ||||
|             .offset(offset) | ||||
|             .limit(limit) | ||||
|             .orderBy(audioContentCuration.orders.asc()) | ||||
|   | ||||
| @@ -1,8 +1,12 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.curation | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.BaseEntity | ||||
| import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTab | ||||
| import javax.persistence.Column | ||||
| import javax.persistence.Entity | ||||
| import javax.persistence.FetchType | ||||
| import javax.persistence.JoinColumn | ||||
| import javax.persistence.OneToOne | ||||
| import javax.persistence.Table | ||||
|  | ||||
| @Entity | ||||
| @@ -17,5 +21,11 @@ data class AudioContentCuration( | ||||
|     @Column(nullable = false) | ||||
|     var isActive: Boolean = true, | ||||
|     @Column(nullable = false) | ||||
|     var orders: Int = 1 | ||||
| ) : BaseEntity() | ||||
|     var orders: Int = 1, | ||||
|     @Column(nullable = false) | ||||
|     var isSeries: Boolean = false | ||||
| ) : BaseEntity() { | ||||
|     @OneToOne(fetch = FetchType.EAGER) | ||||
|     @JoinColumn(name = "tab_id", nullable = true) | ||||
|     var tab: AudioContentMainTab? = null | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package kr.co.vividnext.sodalive.content.main.tab | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetContentMainTabItem @QueryProjection constructor( | ||||
|     val tabId: Long, | ||||
|     val title: String | ||||
| ) | ||||
| @@ -55,8 +55,8 @@ class CreatorAdminCalculateQueryRepository(private val queryFactory: JPAQueryFac | ||||
|             .on(member.id.eq(creatorSettlementRatio.member.id)) | ||||
|             .where( | ||||
|                 useCan.isRefund.isFalse | ||||
|                     .and(liveRoom.beginDateTime.goe(startDate)) | ||||
|                     .and(liveRoom.beginDateTime.loe(endDate)) | ||||
|                     .and(useCan.createdAt.goe(startDate)) | ||||
|                     .and(useCan.createdAt.loe(endDate)) | ||||
|                     .and(liveRoom.member.id.eq(memberId)) | ||||
|             ) | ||||
|             .groupBy(liveRoom.id, useCan.canUsage, creatorSettlementRatio.liveSettlementRatio) | ||||
|   | ||||
| @@ -43,6 +43,7 @@ data class Series( | ||||
|     @CollectionTable(name = "series_published_days_of_week", joinColumns = [JoinColumn(name = "series_id")]) | ||||
|     val publishedDaysOfWeek: MutableSet<SeriesPublishedDaysOfWeek> = mutableSetOf(), | ||||
|     var isAdult: Boolean = false, | ||||
|     var isOriginal: Boolean = false, | ||||
|     var isActive: Boolean = true, | ||||
|     var orders: Int = 1 | ||||
| ) : BaseEntity() { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user