| @@ -0,0 +1,67 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.data.domain.Pageable | ||||||
|  | import org.springframework.security.core.annotation.AuthenticationPrincipal | ||||||
|  | import org.springframework.web.bind.annotation.GetMapping | ||||||
|  | import org.springframework.web.bind.annotation.PathVariable | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/audio-content/series") | ||||||
|  | class ContentSeriesController(private val service: ContentSeriesService) { | ||||||
|  |     @GetMapping | ||||||
|  |     fun getSeriesList( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @RequestParam("sortType", required = false) sortType: SeriesSortType = SeriesSortType.NEWEST, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getSeriesList( | ||||||
|  |                 creatorId = creatorId, | ||||||
|  |                 sortType = sortType, | ||||||
|  |                 member = member, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/{id}") | ||||||
|  |     fun getSeriesDetail( | ||||||
|  |         @PathVariable id: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getSeriesDetail(seriesId = id, member = member) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/{id}/content") | ||||||
|  |     fun getSeriesContentList( | ||||||
|  |         @PathVariable id: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, | ||||||
|  |         pageable: Pageable | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.getSeriesContentList( | ||||||
|  |                 seriesId = id, | ||||||
|  |                 member = member, | ||||||
|  |                 offset = pageable.offset, | ||||||
|  |                 limit = pageable.pageSize.toLong() | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,110 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
|  | import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
|  | interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository | ||||||
|  |  | ||||||
|  | interface ContentSeriesQueryRepository { | ||||||
|  |     fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int | ||||||
|  |     fun getSeriesList( | ||||||
|  |         imageHost: String, | ||||||
|  |         creatorId: Long, | ||||||
|  |         isAuth: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<Series> | ||||||
|  |  | ||||||
|  |     fun getSeriesDetail(seriesId: Long, isAuth: Boolean): Series? | ||||||
|  |     fun getKeywordList(seriesId: Long): List<String> | ||||||
|  |     fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ContentSeriesQueryRepositoryImpl( | ||||||
|  |     private val queryFactory: JPAQueryFactory | ||||||
|  | ) : ContentSeriesQueryRepository { | ||||||
|  |     override fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int { | ||||||
|  |         var where = series.member.id.eq(creatorId) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAuth) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(series.id) | ||||||
|  |             .from(series) | ||||||
|  |             .where(where) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getSeriesList( | ||||||
|  |         imageHost: String, | ||||||
|  |         creatorId: Long, | ||||||
|  |         isAuth: Boolean, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<Series> { | ||||||
|  |         var where = series.member.id.eq(creatorId) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAuth) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(series) | ||||||
|  |             .where(where) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getSeriesDetail(seriesId: Long, isAuth: Boolean): Series? { | ||||||
|  |         var where = series.id.eq(seriesId) | ||||||
|  |             .and(series.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAuth) { | ||||||
|  |             where = where.and(series.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .selectFrom(series) | ||||||
|  |             .where(where) | ||||||
|  |             .fetchFirst() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getKeywordList(seriesId: Long): List<String> { | ||||||
|  |         return queryFactory | ||||||
|  |             .select(hashTag.tag) | ||||||
|  |             .from(series) | ||||||
|  |             .innerJoin(series.keywordList, seriesKeyword) | ||||||
|  |             .innerJoin(seriesKeyword.keyword, hashTag) | ||||||
|  |             .where(series.id.eq(seriesId)) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse { | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetSeriesContentMinMaxPriceResponse( | ||||||
|  |                     audioContent.price.min(), | ||||||
|  |                     audioContent.price.max() | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(series) | ||||||
|  |             .innerJoin(series.contentList, seriesContent) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .where(series.id.eq(seriesId)) | ||||||
|  |             .fetchFirst() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,192 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.content.order.OrderRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.order.OrderType | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentListResponse | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState | ||||||
|  | import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import org.springframework.beans.factory.annotation.Value | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import java.time.LocalDateTime | ||||||
|  | import java.time.ZoneId | ||||||
|  | import java.time.format.DateTimeFormatter | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class ContentSeriesService( | ||||||
|  |     private val repository: ContentSeriesRepository, | ||||||
|  |     private val orderRepository: OrderRepository, | ||||||
|  |     private val explorerQueryRepository: ExplorerQueryRepository, | ||||||
|  |     private val seriesContentRepository: ContentSeriesContentRepository, | ||||||
|  |  | ||||||
|  |     @Value("\${cloud.aws.cloud-front.host}") | ||||||
|  |     private val coverImageHost: String | ||||||
|  | ) { | ||||||
|  |     fun getSeriesList( | ||||||
|  |         creatorId: Long, | ||||||
|  |         member: Member, | ||||||
|  |         sortType: SeriesSortType = SeriesSortType.NEWEST, | ||||||
|  |         offset: Long = 0, | ||||||
|  |         limit: Long = 10 | ||||||
|  |     ): GetSeriesListResponse { | ||||||
|  |         val totalCount = repository.getSeriesTotalCount(creatorId = creatorId, isAuth = member.auth != null) | ||||||
|  |         val rawItems = repository.getSeriesList( | ||||||
|  |             imageHost = coverImageHost, | ||||||
|  |             creatorId = creatorId, | ||||||
|  |             isAuth = member.auth != null, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val items = rawItems | ||||||
|  |             .map { | ||||||
|  |                 GetSeriesListResponse.SeriesListItem( | ||||||
|  |                     seriesId = it.id!!, | ||||||
|  |                     title = it.title, | ||||||
|  |                     coverImage = "$coverImageHost/${it.coverImage!!}", | ||||||
|  |                     publishedDaysOfWeek = publishedDaysOfWeekText(it.publishedDaysOfWeek), | ||||||
|  |                     isComplete = it.state == SeriesState.COMPLETE, | ||||||
|  |                     creator = GetSeriesListResponse.SeriesListItemCreator( | ||||||
|  |                         creatorId = it.member!!.id!!, | ||||||
|  |                         nickname = it.member!!.nickname, | ||||||
|  |                         profileImage = "$coverImageHost/${it.member!!.profileImage!!}" | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             .map { | ||||||
|  |                 it.numberOfContent = seriesContentRepository.getContentCount( | ||||||
|  |                     seriesId = it.seriesId, | ||||||
|  |                     isAdult = member.auth != null | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |             .map { | ||||||
|  |                 val nowDateTime = LocalDateTime.now() | ||||||
|  |  | ||||||
|  |                 it.isNew = seriesContentRepository.isNewContent( | ||||||
|  |                     seriesId = it.seriesId, | ||||||
|  |                     isAdult = member.auth == null, | ||||||
|  |                     fromDate = nowDateTime.minusDays(7), | ||||||
|  |                     nowDate = nowDateTime | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetSeriesListResponse(totalCount, items) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesDetail(seriesId: Long, member: Member): GetSeriesDetailResponse { | ||||||
|  |         val series = repository.getSeriesDetail( | ||||||
|  |             seriesId = seriesId, | ||||||
|  |             isAuth = member.auth != null | ||||||
|  |         ) ?: throw SodaException("잘못된 시리즈 입니다.\n다시 시도해 주세요") | ||||||
|  |  | ||||||
|  |         val isFollow = explorerQueryRepository.isFollow( | ||||||
|  |             creatorId = series.member!!.id!!, | ||||||
|  |             memberId = member.id!! | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         val keywordList = repository.getKeywordList(seriesId = seriesId) | ||||||
|  |             .filter { it.isNotBlank() } | ||||||
|  |  | ||||||
|  |         val minMaxPrice = repository.getSeriesContentMinMaxPrice(seriesId = seriesId) | ||||||
|  |         val minPrice = minMaxPrice.minPrice | ||||||
|  |         val maxPrice = minMaxPrice.maxPrice | ||||||
|  |         val rentalMinPrice = (minMaxPrice.minPrice * 0.7).toInt() | ||||||
|  |         val rentalMaxPrice = (minMaxPrice.maxPrice * 0.7).toInt() | ||||||
|  |  | ||||||
|  |         val seriesContentList = getSeriesContentList(seriesId = seriesId, member = member, offset = 0, limit = 5) | ||||||
|  |  | ||||||
|  |         val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd") | ||||||
|  |         return GetSeriesDetailResponse( | ||||||
|  |             seriesId = seriesId, | ||||||
|  |             title = series.title, | ||||||
|  |             coverImage = "$coverImageHost/${series.coverImage}", | ||||||
|  |             introduction = series.introduction, | ||||||
|  |             genre = series.genre!!.genre, | ||||||
|  |             isAdult = series.isAdult, | ||||||
|  |             writer = series.writer, | ||||||
|  |             studio = series.studio, | ||||||
|  |             publishedDate = series.createdAt!! | ||||||
|  |                 .atZone(ZoneId.of("UTC")) | ||||||
|  |                 .withZoneSameInstant(ZoneId.of("Asia/Seoul")) | ||||||
|  |                 .toLocalDateTime() | ||||||
|  |                 .format(dateTimeFormatter), | ||||||
|  |             creator = GetSeriesDetailResponse.GetSeriesDetailCreator( | ||||||
|  |                 creatorId = series.member!!.id!!, | ||||||
|  |                 nickname = series.member!!.nickname, | ||||||
|  |                 profileImage = "$coverImageHost/${series.member!!.profileImage}", | ||||||
|  |                 isFollow = isFollow | ||||||
|  |             ), | ||||||
|  |             rentalMinPrice = rentalMinPrice, | ||||||
|  |             rentalMaxPrice = rentalMaxPrice, | ||||||
|  |             rentalPeriod = 15, | ||||||
|  |             minPrice = minPrice, | ||||||
|  |             maxPrice = maxPrice, | ||||||
|  |             keywordList = keywordList, | ||||||
|  |             publishedDaysOfWeek = publishedDaysOfWeekText(series.publishedDaysOfWeek), | ||||||
|  |             contentList = seriesContentList.items, | ||||||
|  |             contentCount = seriesContentList.totalCount | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getSeriesContentList(seriesId: Long, member: Member, offset: Long, limit: Long): GetSeriesContentListResponse { | ||||||
|  |         val isAdult = member.auth != null | ||||||
|  |  | ||||||
|  |         val totalCount = seriesContentRepository.getContentCount(seriesId, isAdult = isAdult) | ||||||
|  |         val contentList = seriesContentRepository.getContentList( | ||||||
|  |             seriesId = seriesId, | ||||||
|  |             isAdult = isAdult, | ||||||
|  |             imageHost = coverImageHost, | ||||||
|  |             offset = offset, | ||||||
|  |             limit = limit | ||||||
|  |         ) | ||||||
|  |             .map { | ||||||
|  |                 val (isExistsAudioContent, orderType) = orderRepository.isExistOrderedAndOrderType( | ||||||
|  |                     memberId = member.id!!, | ||||||
|  |                     contentId = it.contentId | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |                 if (isExistsAudioContent) { | ||||||
|  |                     if (orderType == OrderType.RENTAL) { | ||||||
|  |                         it.isRented = true | ||||||
|  |                     } else { | ||||||
|  |                         it.isOwned = true | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         return GetSeriesContentListResponse(totalCount, contentList) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>): String { | ||||||
|  |         val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal } | ||||||
|  |             .map { | ||||||
|  |                 when (it) { | ||||||
|  |                     SeriesPublishedDaysOfWeek.SUN -> "일" | ||||||
|  |                     SeriesPublishedDaysOfWeek.MON -> "월" | ||||||
|  |                     SeriesPublishedDaysOfWeek.TUE -> "화" | ||||||
|  |                     SeriesPublishedDaysOfWeek.WED -> "수" | ||||||
|  |                     SeriesPublishedDaysOfWeek.THU -> "목" | ||||||
|  |                     SeriesPublishedDaysOfWeek.FRI -> "금" | ||||||
|  |                     SeriesPublishedDaysOfWeek.SAT -> "토" | ||||||
|  |                     SeriesPublishedDaysOfWeek.RANDOM -> "랜덤" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .joinToString(", ") { it } | ||||||
|  |  | ||||||
|  |         return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) { | ||||||
|  |             dayOfWeekText | ||||||
|  |         } else { | ||||||
|  |             "매주 $dayOfWeekText" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentListItem | ||||||
|  |  | ||||||
|  | data class GetSeriesDetailResponse( | ||||||
|  |     val seriesId: Long, | ||||||
|  |     val title: String, | ||||||
|  |     val coverImage: String, | ||||||
|  |     val introduction: String, | ||||||
|  |     val genre: String, | ||||||
|  |     val isAdult: Boolean, | ||||||
|  |     val writer: String?, | ||||||
|  |     val studio: String?, | ||||||
|  |     val publishedDate: String, | ||||||
|  |     val creator: GetSeriesDetailCreator, | ||||||
|  |     var rentalMinPrice: Int, | ||||||
|  |     var rentalMaxPrice: Int, | ||||||
|  |     val rentalPeriod: Int, | ||||||
|  |     val minPrice: Int, | ||||||
|  |     val maxPrice: Int, | ||||||
|  |     val keywordList: List<String>, | ||||||
|  |     val publishedDaysOfWeek: String, | ||||||
|  |     val contentList: List<GetSeriesContentListItem>, | ||||||
|  |     val contentCount: Int | ||||||
|  | ) { | ||||||
|  |     data class GetSeriesDetailCreator( | ||||||
|  |         val creatorId: Long, | ||||||
|  |         val nickname: String, | ||||||
|  |         val profileImage: String, | ||||||
|  |         val isFollow: Boolean | ||||||
|  |     ) | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series | ||||||
|  |  | ||||||
|  | data class GetSeriesListResponse( | ||||||
|  |     val totalCount: Int, | ||||||
|  |     val items: List<SeriesListItem> | ||||||
|  | ) { | ||||||
|  |     data class SeriesListItem( | ||||||
|  |         val seriesId: Long, | ||||||
|  |         val title: String, | ||||||
|  |         val coverImage: String, | ||||||
|  |         val publishedDaysOfWeek: String, | ||||||
|  |         val isComplete: Boolean = false, | ||||||
|  |         val creator: SeriesListItemCreator, | ||||||
|  |         var numberOfContent: Int = 0, | ||||||
|  |         var isNew: Boolean = false, | ||||||
|  |         var isPopular: Boolean = false | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     data class SeriesListItemCreator( | ||||||
|  |         val creatorId: Long, | ||||||
|  |         val nickname: String, | ||||||
|  |         val profileImage: String | ||||||
|  |     ) | ||||||
|  | } | ||||||
| @@ -0,0 +1,123 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series.content | ||||||
|  |  | ||||||
|  | import com.querydsl.core.types.dsl.Expressions | ||||||
|  | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
|  | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesContent | ||||||
|  | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
|  | interface ContentSeriesContentRepository : JpaRepository<SeriesContent, Long>, ContentSeriesContentQueryRepository | ||||||
|  |  | ||||||
|  | interface ContentSeriesContentQueryRepository { | ||||||
|  |     fun getContentCount(seriesId: Long, isAdult: Boolean): Int | ||||||
|  |     fun getContentList( | ||||||
|  |         seriesId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         imageHost: String, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetSeriesContentListItem> | ||||||
|  |  | ||||||
|  |     fun isNewContent(seriesId: Long, isAdult: Boolean, fromDate: LocalDateTime, nowDate: LocalDateTime): Boolean | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class ContentSeriesContentQueryRepositoryImpl( | ||||||
|  |     private val queryFactory: JPAQueryFactory | ||||||
|  | ) : ContentSeriesContentQueryRepository { | ||||||
|  |     override fun getContentCount(seriesId: Long, isAdult: Boolean): Int { | ||||||
|  |         var where = seriesContent.series.id.eq(seriesId) | ||||||
|  |             .and(seriesContent.content.isActive.isTrue) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(seriesContent.content.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select(seriesContent.id) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .where(where) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun getContentList( | ||||||
|  |         seriesId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         imageHost: String, | ||||||
|  |         offset: Long, | ||||||
|  |         limit: Long | ||||||
|  |     ): List<GetSeriesContentListItem> { | ||||||
|  |         var where = series.id.eq(seriesId) | ||||||
|  |             .and(audioContent.isActive.isTrue) | ||||||
|  |             .and(audioContent.duration.isNotNull) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(audioContent.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val formattedDate = Expressions.stringTemplate( | ||||||
|  |             "DATE_FORMAT({0}, {1})", | ||||||
|  |             Expressions.dateTimeTemplate( | ||||||
|  |                 LocalDateTime::class.java, | ||||||
|  |                 "CONVERT_TZ({0},{1},{2})", | ||||||
|  |                 audioContent.releaseDate, | ||||||
|  |                 "UTC", | ||||||
|  |                 "Asia/Seoul" | ||||||
|  |             ), | ||||||
|  |             "%y.%m.%d" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QGetSeriesContentListItem( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.title, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost), | ||||||
|  |                     formattedDate, | ||||||
|  |                     audioContent.duration, | ||||||
|  |                     audioContent.price, | ||||||
|  |                     Expressions.asBoolean(false), | ||||||
|  |                     Expressions.asBoolean(false) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .where(where) | ||||||
|  |             .offset(offset) | ||||||
|  |             .limit(limit) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isNewContent( | ||||||
|  |         seriesId: Long, | ||||||
|  |         isAdult: Boolean, | ||||||
|  |         fromDate: LocalDateTime, | ||||||
|  |         nowDate: LocalDateTime | ||||||
|  |     ): Boolean { | ||||||
|  |         var where = seriesContent.series.id.eq(seriesId) | ||||||
|  |             .and(seriesContent.content.isActive.isTrue) | ||||||
|  |             .and(seriesContent.content.releaseDate.after(fromDate)) | ||||||
|  |             .and(seriesContent.content.releaseDate.before(nowDate)) | ||||||
|  |  | ||||||
|  |         if (!isAdult) { | ||||||
|  |             where = where.and(seriesContent.content.isAdult.isFalse) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val itemCount = queryFactory | ||||||
|  |             .select(seriesContent.id) | ||||||
|  |             .from(seriesContent) | ||||||
|  |             .innerJoin(seriesContent.series, series) | ||||||
|  |             .innerJoin(seriesContent.content, audioContent) | ||||||
|  |             .where(where) | ||||||
|  |             .fetch() | ||||||
|  |             .size | ||||||
|  |  | ||||||
|  |         return itemCount > 0 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series.content | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetSeriesContentListResponse( | ||||||
|  |     val totalCount: Int, | ||||||
|  |     val items: List<GetSeriesContentListItem> | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | data class GetSeriesContentListItem @QueryProjection constructor( | ||||||
|  |     val contentId: Long, | ||||||
|  |     val title: String, | ||||||
|  |     val coverImage: String, | ||||||
|  |     val releaseDate: String, | ||||||
|  |     val duration: String, | ||||||
|  |     val price: Int, | ||||||
|  |     var isRented: Boolean, | ||||||
|  |     var isOwned: Boolean | ||||||
|  | ) | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package kr.co.vividnext.sodalive.content.series.content | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class GetSeriesContentMinMaxPriceResponse @QueryProjection constructor( | ||||||
|  |     val minPrice: Int, | ||||||
|  |     val maxPrice: Int | ||||||
|  | ) | ||||||
| @@ -5,7 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException | |||||||
| data class CreateSeriesRequest( | data class CreateSeriesRequest( | ||||||
|     val title: String, |     val title: String, | ||||||
|     val introduction: String, |     val introduction: String, | ||||||
|     val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>, |     val publishedDaysOfWeek: MutableSet<SeriesPublishedDaysOfWeek>, | ||||||
|     val keyword: String, |     val keyword: String, | ||||||
|     val genreId: Long = 0, |     val genreId: Long = 0, | ||||||
|     val isAdult: Boolean = false, |     val isAdult: Boolean = false, | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.PostMapping | |||||||
| import org.springframework.web.bind.annotation.PutMapping | import org.springframework.web.bind.annotation.PutMapping | ||||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||||
| import org.springframework.web.bind.annotation.RequestMapping | 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.RequestPart | ||||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||||
| import org.springframework.web.multipart.MultipartFile | import org.springframework.web.multipart.MultipartFile | ||||||
| @@ -116,4 +117,21 @@ class CreatorAdminContentSeriesController(private val service: CreatorAdminConte | |||||||
|             "콘텐츠를 삭제하였습니다." |             "콘텐츠를 삭제하였습니다." | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @GetMapping("/content/search") | ||||||
|  |     fun searchContentNotInSeries( | ||||||
|  |         @RequestParam(value = "series_id") seriesId: Long, | ||||||
|  |         @RequestParam(value = "search_word") searchWord: String, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok( | ||||||
|  |             service.searchContentNotInSeries( | ||||||
|  |                 seriesId = seriesId, | ||||||
|  |                 searchWord = searchWord, | ||||||
|  |                 memberId = member.id!! | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ import com.querydsl.jpa.impl.JPAQueryFactory | |||||||
| import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | import kr.co.vividnext.sodalive.content.QAudioContent.audioContent | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.content.QSearchContentNotInSeriesResponse | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.content.SearchContentNotInSeriesResponse | ||||||
| import org.springframework.data.jpa.repository.JpaRepository | import org.springframework.data.jpa.repository.JpaRepository | ||||||
|  |  | ||||||
| interface CreatorAdminContentSeriesRepository : JpaRepository<Series, Long>, CreatorAdminContentSeriesQueryRepository | interface CreatorAdminContentSeriesRepository : JpaRepository<Series, Long>, CreatorAdminContentSeriesQueryRepository | ||||||
| @@ -14,17 +16,24 @@ interface CreatorAdminContentSeriesQueryRepository { | |||||||
|     fun getSeriesList( |     fun getSeriesList( | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long, |         limit: Long, | ||||||
|         creatorId: Long, |         creatorId: Long | ||||||
|         imageHost: String |     ): List<Series> | ||||||
|     ): List<GetCreatorAdminContentSeriesListItem> |  | ||||||
|  |  | ||||||
|     fun getSeriesContentCount(creatorId: Long): Int |     fun getSeriesContentCount(creatorId: Long): Int | ||||||
|     fun getSeriesContentList( |     fun getSeriesContentList( | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long, |         limit: Long, | ||||||
|  |         seriesId: Long, | ||||||
|         creatorId: Long, |         creatorId: Long, | ||||||
|         imageHost: String |         imageHost: String | ||||||
|     ): List<GetCreatorAdminContentSeriesContentItem> |     ): List<GetCreatorAdminContentSeriesContentItem> | ||||||
|  |  | ||||||
|  |     fun searchContentNotInSeries( | ||||||
|  |         seriesId: Long, | ||||||
|  |         searchWord: String, | ||||||
|  |         memberId: Long, | ||||||
|  |         imageHost: String | ||||||
|  |     ): List<SearchContentNotInSeriesResponse> | ||||||
| } | } | ||||||
|  |  | ||||||
| class CreatorAdminContentSeriesQueryRepositoryImpl( | class CreatorAdminContentSeriesQueryRepositoryImpl( | ||||||
| @@ -55,18 +64,10 @@ class CreatorAdminContentSeriesQueryRepositoryImpl( | |||||||
|     override fun getSeriesList( |     override fun getSeriesList( | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long, |         limit: Long, | ||||||
|         creatorId: Long, |         creatorId: Long | ||||||
|         imageHost: String |     ): List<Series> { | ||||||
|     ): List<GetCreatorAdminContentSeriesListItem> { |  | ||||||
|         return queryFactory |         return queryFactory | ||||||
|             .select( |             .selectFrom(series) | ||||||
|                 QGetCreatorAdminContentSeriesListItem( |  | ||||||
|                     series.id, |  | ||||||
|                     series.title, |  | ||||||
|                     series.coverImage.prepend("/").prepend(imageHost) |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|             .from(series) |  | ||||||
|             .where( |             .where( | ||||||
|                 series.member.id.eq(creatorId) |                 series.member.id.eq(creatorId) | ||||||
|                     .and(series.isActive.isTrue) |                     .and(series.isActive.isTrue) | ||||||
| @@ -95,6 +96,7 @@ class CreatorAdminContentSeriesQueryRepositoryImpl( | |||||||
|     override fun getSeriesContentList( |     override fun getSeriesContentList( | ||||||
|         offset: Long, |         offset: Long, | ||||||
|         limit: Long, |         limit: Long, | ||||||
|  |         seriesId: Long, | ||||||
|         creatorId: Long, |         creatorId: Long, | ||||||
|         imageHost: String |         imageHost: String | ||||||
|     ): List<GetCreatorAdminContentSeriesContentItem> { |     ): List<GetCreatorAdminContentSeriesContentItem> { | ||||||
| @@ -112,6 +114,7 @@ class CreatorAdminContentSeriesQueryRepositoryImpl( | |||||||
|             .innerJoin(seriesContent.content, audioContent) |             .innerJoin(seriesContent.content, audioContent) | ||||||
|             .where( |             .where( | ||||||
|                 series.member.id.eq(creatorId) |                 series.member.id.eq(creatorId) | ||||||
|  |                     .and(series.id.eq(seriesId)) | ||||||
|                     .and(audioContent.member.id.eq(creatorId)) |                     .and(audioContent.member.id.eq(creatorId)) | ||||||
|                     .and(series.isActive.isTrue) |                     .and(series.isActive.isTrue) | ||||||
|             ) |             ) | ||||||
| @@ -119,4 +122,35 @@ class CreatorAdminContentSeriesQueryRepositoryImpl( | |||||||
|             .limit(limit) |             .limit(limit) | ||||||
|             .fetch() |             .fetch() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun searchContentNotInSeries( | ||||||
|  |         seriesId: Long, | ||||||
|  |         searchWord: String, | ||||||
|  |         memberId: Long, | ||||||
|  |         imageHost: String | ||||||
|  |     ): List<SearchContentNotInSeriesResponse> { | ||||||
|  |         return queryFactory | ||||||
|  |             .select( | ||||||
|  |                 QSearchContentNotInSeriesResponse( | ||||||
|  |                     audioContent.id, | ||||||
|  |                     audioContent.title, | ||||||
|  |                     audioContent.coverImage.prepend("/").prepend(imageHost) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |             .from(audioContent) | ||||||
|  |             .leftJoin(seriesContent) | ||||||
|  |             .on( | ||||||
|  |                 audioContent.id.eq(seriesContent.content.id) | ||||||
|  |                     .and(seriesContent.series.id.eq(seriesId)) | ||||||
|  |             ) | ||||||
|  |             .where( | ||||||
|  |                 audioContent.duration.isNotNull | ||||||
|  |                     .and(audioContent.member.isNotNull) | ||||||
|  |                     .and(audioContent.member.id.eq(memberId)) | ||||||
|  |                     .and(audioContent.isActive.isTrue.or(audioContent.releaseDate.isNotNull)) | ||||||
|  |                     .and(audioContent.title.contains(searchWord)) | ||||||
|  |                     .and(seriesContent.id.isNull) | ||||||
|  |             ) | ||||||
|  |             .fetch() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import kr.co.vividnext.sodalive.content.hashtag.HashTag | |||||||
| import kr.co.vividnext.sodalive.content.hashtag.HashTagRepository | import kr.co.vividnext.sodalive.content.hashtag.HashTagRepository | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.content.AddingContentToTheSeriesRequest | import kr.co.vividnext.sodalive.creator.admin.content.series.content.AddingContentToTheSeriesRequest | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.content.RemoveContentToTheSeriesRequest | import kr.co.vividnext.sodalive.creator.admin.content.series.content.RemoveContentToTheSeriesRequest | ||||||
|  | import kr.co.vividnext.sodalive.creator.admin.content.series.content.SearchContentNotInSeriesResponse | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.genre.CreatorAdminContentSeriesGenreRepository | import kr.co.vividnext.sodalive.creator.admin.content.series.genre.CreatorAdminContentSeriesGenreRepository | ||||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.SeriesKeyword | import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.SeriesKeyword | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| @@ -48,17 +49,20 @@ class CreatorAdminContentSeriesService( | |||||||
|         val keywords = request.keyword |         val keywords = request.keyword | ||||||
|             .replace("#", " #") |             .replace("#", " #") | ||||||
|             .split(" ") |             .split(" ") | ||||||
|  |             .asSequence() | ||||||
|             .map { it.trim() } |             .map { it.trim() } | ||||||
|             .filter { it.isNotBlank() } |             .filter { it.isNotBlank() } | ||||||
|             .map { |             .map { | ||||||
|                 val tag = if (!it.startsWith("#")) { |                 if (!it.startsWith("#")) { | ||||||
|                     "#$it" |                     "#$it" | ||||||
|                 } else { |                 } else { | ||||||
|                     it |                     it | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|                 val hashTag = hashTagRepository.findByTag(tag) |             .toSet() | ||||||
|                     ?: hashTagRepository.save(HashTag(tag)) |             .map { | ||||||
|  |                 val hashTag = hashTagRepository.findByTag(it) | ||||||
|  |                     ?: hashTagRepository.save(HashTag(it)) | ||||||
|  |  | ||||||
|                 val seriesKeyword = SeriesKeyword() |                 val seriesKeyword = SeriesKeyword() | ||||||
|                 seriesKeyword.series = series |                 seriesKeyword.series = series | ||||||
| @@ -66,6 +70,7 @@ class CreatorAdminContentSeriesService( | |||||||
|  |  | ||||||
|                 seriesKeyword |                 seriesKeyword | ||||||
|             } |             } | ||||||
|  |             .toList() | ||||||
|  |  | ||||||
|         series.keywordList.addAll(keywords) |         series.keywordList.addAll(keywords) | ||||||
|  |  | ||||||
| @@ -96,8 +101,10 @@ class CreatorAdminContentSeriesService( | |||||||
|             request.publishedDaysOfWeek == null && |             request.publishedDaysOfWeek == null && | ||||||
|             request.genreId == null && |             request.genreId == null && | ||||||
|             request.isAdult == null && |             request.isAdult == null && | ||||||
|  |             request.state == null && | ||||||
|             request.writer == null && |             request.writer == null && | ||||||
|             request.studio == null |             request.studio == null && | ||||||
|  |             request.isActive == null | ||||||
|         ) { |         ) { | ||||||
|             throw SodaException("변경사항이 없습니다.") |             throw SodaException("변경사항이 없습니다.") | ||||||
|         } |         } | ||||||
| @@ -138,8 +145,8 @@ class CreatorAdminContentSeriesService( | |||||||
|                 throw SodaException("랜덤과 연재요일 동시에 선택할 수 없습니다.") |                 throw SodaException("랜덤과 연재요일 동시에 선택할 수 없습니다.") | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             series.publishedDaysOfWeek.toMutableSet().clear() |             series.publishedDaysOfWeek.clear() | ||||||
|             series.publishedDaysOfWeek.toMutableSet().addAll(request.publishedDaysOfWeek) |             series.publishedDaysOfWeek.addAll(request.publishedDaysOfWeek) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (request.genreId != null) { |         if (request.genreId != null) { | ||||||
| @@ -151,6 +158,10 @@ class CreatorAdminContentSeriesService( | |||||||
|             series.isAdult = request.isAdult |             series.isAdult = request.isAdult | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (request.state != null) { | ||||||
|  |             series.state = request.state | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (request.isActive != null) { |         if (request.isActive != null) { | ||||||
|             series.isActive = request.isActive |             series.isActive = request.isActive | ||||||
|         } |         } | ||||||
| @@ -169,9 +180,23 @@ class CreatorAdminContentSeriesService( | |||||||
|         val seriesList = repository.getSeriesList( |         val seriesList = repository.getSeriesList( | ||||||
|             offset = offset, |             offset = offset, | ||||||
|             limit = limit, |             limit = limit, | ||||||
|             creatorId = creatorId, |             creatorId = creatorId | ||||||
|             imageHost = coverImageHost |  | ||||||
|         ) |         ) | ||||||
|  |             .map { | ||||||
|  |                 GetCreatorAdminContentSeriesListItem( | ||||||
|  |                     seriesId = it.id!!, | ||||||
|  |                     title = it.title, | ||||||
|  |                     introduction = it.introduction, | ||||||
|  |                     coverImageUrl = "$coverImageHost/${it.coverImage!!}", | ||||||
|  |                     publishedDaysOfWeek = it.publishedDaysOfWeek.toList(), | ||||||
|  |                     genreId = it.genre!!.id!!, | ||||||
|  |                     isAdult = it.isAdult, | ||||||
|  |                     state = it.state, | ||||||
|  |                     isActive = it.isActive, | ||||||
|  |                     writer = it.writer, | ||||||
|  |                     studio = it.studio | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|         return GetCreatorAdminContentSeriesListResponse(totalCount, seriesList) |         return GetCreatorAdminContentSeriesListResponse(totalCount, seriesList) | ||||||
|     } |     } | ||||||
| @@ -193,6 +218,7 @@ class CreatorAdminContentSeriesService( | |||||||
|         val seriesContentList = repository.getSeriesContentList( |         val seriesContentList = repository.getSeriesContentList( | ||||||
|             offset = offset, |             offset = offset, | ||||||
|             limit = limit, |             limit = limit, | ||||||
|  |             seriesId = seriesId, | ||||||
|             creatorId = creatorId, |             creatorId = creatorId, | ||||||
|             imageHost = coverImageHost |             imageHost = coverImageHost | ||||||
|         ) |         ) | ||||||
| @@ -218,7 +244,7 @@ class CreatorAdminContentSeriesService( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (seriesContentList.size > 0) { |         if (seriesContentList.size > 0) { | ||||||
|             series.contentList.addAll(seriesContentList) |             series.contentList.addAll(seriesContentList.toSet()) | ||||||
|         } else { |         } else { | ||||||
|             throw SodaException("추가된 콘텐츠가 없습니다.") |             throw SodaException("추가된 콘텐츠가 없습니다.") | ||||||
|         } |         } | ||||||
| @@ -231,4 +257,17 @@ class CreatorAdminContentSeriesService( | |||||||
|  |  | ||||||
|         series.contentList.removeIf { it.content!!.id == request.contentId } |         series.contentList.removeIf { it.content!!.id == request.contentId } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun searchContentNotInSeries( | ||||||
|  |         seriesId: Long, | ||||||
|  |         searchWord: String, | ||||||
|  |         memberId: Long | ||||||
|  |     ): List<SearchContentNotInSeriesResponse> { | ||||||
|  |         return repository.searchContentNotInSeries( | ||||||
|  |             seriesId, | ||||||
|  |             searchWord, | ||||||
|  |             memberId, | ||||||
|  |             imageHost = coverImageHost | ||||||
|  |         ) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ data class GetCreatorAdminContentSeriesDetailResponse( | |||||||
|     val seriesId: Long, |     val seriesId: Long, | ||||||
|     val title: String, |     val title: String, | ||||||
|     val introduction: String, |     val introduction: String, | ||||||
|     val coverImage: String, |     val coverImageUrl: String, | ||||||
|     val publishedDaysOfWeek: String, |     val publishedDaysOfWeek: String, | ||||||
|     val genre: String, |     val genre: String, | ||||||
|     val keywords: String, |     val keywords: String, | ||||||
|   | |||||||
| @@ -1,14 +1,20 @@ | |||||||
| package kr.co.vividnext.sodalive.creator.admin.content.series | package kr.co.vividnext.sodalive.creator.admin.content.series | ||||||
|  |  | ||||||
| import com.querydsl.core.annotations.QueryProjection |  | ||||||
|  |  | ||||||
| data class GetCreatorAdminContentSeriesListResponse( | data class GetCreatorAdminContentSeriesListResponse( | ||||||
|     val totalCount: Int, |     val totalCount: Int, | ||||||
|     val items: List<GetCreatorAdminContentSeriesListItem> |     val items: List<GetCreatorAdminContentSeriesListItem> | ||||||
| ) | ) | ||||||
|  |  | ||||||
| data class GetCreatorAdminContentSeriesListItem @QueryProjection constructor( | data class GetCreatorAdminContentSeriesListItem( | ||||||
|     val seriesId: Long, |     val seriesId: Long, | ||||||
|     val title: String, |     val title: String, | ||||||
|     val coverImageUrl: String |     val introduction: String, | ||||||
|  |     val coverImageUrl: String, | ||||||
|  |     val publishedDaysOfWeek: List<SeriesPublishedDaysOfWeek>, | ||||||
|  |     val genreId: Long, | ||||||
|  |     val isAdult: Boolean, | ||||||
|  |     val state: SeriesState, | ||||||
|  |     val isActive: Boolean, | ||||||
|  |     val writer: String?, | ||||||
|  |     val studio: String? | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ data class ModifySeriesRequest( | |||||||
|     val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>?, |     val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>?, | ||||||
|     val genreId: Long?, |     val genreId: Long?, | ||||||
|     val isAdult: Boolean?, |     val isAdult: Boolean?, | ||||||
|  |     val state: SeriesState?, | ||||||
|     val isActive: Boolean?, |     val isActive: Boolean?, | ||||||
|     val writer: String?, |     val writer: String?, | ||||||
|     val studio: String? |     val studio: String? | ||||||
|   | |||||||
| @@ -25,6 +25,10 @@ enum class SeriesState { | |||||||
|     PROCEEDING, SUSPEND, COMPLETE |     PROCEEDING, SUSPEND, COMPLETE | ||||||
| } | } | ||||||
|  |  | ||||||
|  | enum class SeriesSortType { | ||||||
|  |     NEWEST, POPULAR | ||||||
|  | } | ||||||
|  |  | ||||||
| @Entity | @Entity | ||||||
| data class Series( | data class Series( | ||||||
|     var title: String, |     var title: String, | ||||||
| @@ -37,7 +41,7 @@ data class Series( | |||||||
|     @ElementCollection(targetClass = SeriesPublishedDaysOfWeek::class, fetch = FetchType.EAGER) |     @ElementCollection(targetClass = SeriesPublishedDaysOfWeek::class, fetch = FetchType.EAGER) | ||||||
|     @Enumerated(value = EnumType.STRING) |     @Enumerated(value = EnumType.STRING) | ||||||
|     @CollectionTable(name = "series_published_days_of_week", joinColumns = [JoinColumn(name = "series_id")]) |     @CollectionTable(name = "series_published_days_of_week", joinColumns = [JoinColumn(name = "series_id")]) | ||||||
|     val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek> = mutableSetOf(), |     val publishedDaysOfWeek: MutableSet<SeriesPublishedDaysOfWeek> = mutableSetOf(), | ||||||
|     var isAdult: Boolean = false, |     var isAdult: Boolean = false, | ||||||
|     var isActive: Boolean = true |     var isActive: Boolean = true | ||||||
| ) : BaseEntity() { | ) : BaseEntity() { | ||||||
| @@ -51,10 +55,10 @@ data class Series( | |||||||
|  |  | ||||||
|     var coverImage: String? = null |     var coverImage: String? = null | ||||||
|  |  | ||||||
|     @OneToMany(mappedBy = "series", cascade = [CascadeType.ALL]) |     @OneToMany(mappedBy = "series", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||||
|     var contentList: MutableList<SeriesContent> = mutableListOf() |     var contentList: MutableList<SeriesContent> = mutableListOf() | ||||||
|  |  | ||||||
|     @OneToMany(mappedBy = "series", cascade = [CascadeType.ALL]) |     @OneToMany(mappedBy = "series", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||||
|     var keywordList: MutableList<SeriesKeyword> = mutableListOf() |     var keywordList: MutableList<SeriesKeyword> = mutableListOf() | ||||||
|  |  | ||||||
|     fun toDetailResponse(imageHost: String): GetCreatorAdminContentSeriesDetailResponse { |     fun toDetailResponse(imageHost: String): GetCreatorAdminContentSeriesDetailResponse { | ||||||
| @@ -62,7 +66,7 @@ data class Series( | |||||||
|             seriesId = id!!, |             seriesId = id!!, | ||||||
|             title = title, |             title = title, | ||||||
|             introduction = introduction, |             introduction = introduction, | ||||||
|             coverImage = "$imageHost/$coverImage!!", |             coverImageUrl = "$imageHost/${coverImage!!}", | ||||||
|             publishedDaysOfWeek = publishedDaysOfWeekText(), |             publishedDaysOfWeek = publishedDaysOfWeekText(), | ||||||
|             genre = genre!!.genre, |             genre = genre!!.genre, | ||||||
|             keywords = keywordList.map { it.keyword!!.tag }.joinToString(" ") { it }, |             keywords = keywordList.map { it.keyword!!.tag }.joinToString(" ") { it }, | ||||||
|   | |||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package kr.co.vividnext.sodalive.creator.admin.content.series.content | ||||||
|  |  | ||||||
|  | import com.querydsl.core.annotations.QueryProjection | ||||||
|  |  | ||||||
|  | data class SearchContentNotInSeriesResponse @QueryProjection constructor( | ||||||
|  |     val contentId: Long, | ||||||
|  |     val title: String, | ||||||
|  |     val coverImage: String | ||||||
|  | ) | ||||||
| @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.explorer | |||||||
| import kr.co.vividnext.sodalive.common.SodaException | import kr.co.vividnext.sodalive.common.SodaException | ||||||
| import kr.co.vividnext.sodalive.content.AudioContentService | import kr.co.vividnext.sodalive.content.AudioContentService | ||||||
| import kr.co.vividnext.sodalive.content.SortType | import kr.co.vividnext.sodalive.content.SortType | ||||||
|  | import kr.co.vividnext.sodalive.content.series.ContentSeriesService | ||||||
| import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponse | import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponse | ||||||
| import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponseItem | import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponseItem | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.ChannelNotice | import kr.co.vividnext.sodalive.explorer.profile.ChannelNotice | ||||||
| @@ -38,6 +39,7 @@ class ExplorerService( | |||||||
|     private val cheersRepository: CreatorCheersRepository, |     private val cheersRepository: CreatorCheersRepository, | ||||||
|     private val noticeRepository: ChannelNoticeRepository, |     private val noticeRepository: ChannelNoticeRepository, | ||||||
|     private val communityService: CreatorCommunityService, |     private val communityService: CreatorCommunityService, | ||||||
|  |     private val seriesService: ContentSeriesService, | ||||||
|  |  | ||||||
|     private val applicationEventPublisher: ApplicationEventPublisher, |     private val applicationEventPublisher: ApplicationEventPublisher, | ||||||
|  |  | ||||||
| @@ -252,6 +254,10 @@ class ExplorerService( | |||||||
|         val liveContributorCount = queryRepository.getLiveContributorCount(creatorId) ?: 0 |         val liveContributorCount = queryRepository.getLiveContributorCount(creatorId) ?: 0 | ||||||
|         val contentCount = queryRepository.getContentCount(creatorId) ?: 0 |         val contentCount = queryRepository.getContentCount(creatorId) ?: 0 | ||||||
|  |  | ||||||
|  |         val seriesList = seriesService | ||||||
|  |             .getSeriesList(creatorId = creatorId, member = member) | ||||||
|  |             .items | ||||||
|  |  | ||||||
|         return GetCreatorProfileResponse( |         return GetCreatorProfileResponse( | ||||||
|             creator = CreatorResponse( |             creator = CreatorResponse( | ||||||
|                 creatorId = creatorAccount.id!!, |                 creatorId = creatorAccount.id!!, | ||||||
| @@ -283,6 +289,7 @@ class ExplorerService( | |||||||
|                 liveContributorCount = liveContributorCount, |                 liveContributorCount = liveContributorCount, | ||||||
|                 contentCount = contentCount |                 contentCount = contentCount | ||||||
|             ), |             ), | ||||||
|  |             seriesList = seriesList, | ||||||
|             isBlock = isBlock |             isBlock = isBlock | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package kr.co.vividnext.sodalive.explorer | package kr.co.vividnext.sodalive.explorer | ||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.content.GetAudioContentListItem | import kr.co.vividnext.sodalive.content.GetAudioContentListItem | ||||||
|  | import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse | ||||||
| import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.GetCommunityPostListResponse | import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.GetCommunityPostListResponse | ||||||
|  |  | ||||||
| data class GetCreatorProfileResponse( | data class GetCreatorProfileResponse( | ||||||
| @@ -13,6 +14,7 @@ data class GetCreatorProfileResponse( | |||||||
|     val communityPostList: List<GetCommunityPostListResponse>, |     val communityPostList: List<GetCommunityPostListResponse>, | ||||||
|     val cheers: GetCheersResponse, |     val cheers: GetCheersResponse, | ||||||
|     val activitySummary: GetCreatorActivitySummary, |     val activitySummary: GetCreatorActivitySummary, | ||||||
|  |     val seriesList: List<GetSeriesListResponse.SeriesListItem>, | ||||||
|     val isBlock: Boolean |     val isBlock: Boolean | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user