시리즈 리스트 API
This commit is contained in:
		| @@ -0,0 +1,36 @@ | ||||
| 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.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() | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.Series | ||||
| import kr.co.vividnext.sodalive.member.QMember.member | ||||
| import org.springframework.data.jpa.repository.JpaRepository | ||||
|  | ||||
| interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository | ||||
|  | ||||
| interface ContentSeriesQueryRepository { | ||||
|     fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int | ||||
|     fun getSeriesRawItemList( | ||||
|         imageHost: String, | ||||
|         creatorId: Long, | ||||
|         isAuth: Boolean, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetSeriesListRawItem> | ||||
| } | ||||
|  | ||||
| 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 getSeriesRawItemList( | ||||
|         imageHost: String, | ||||
|         creatorId: Long, | ||||
|         isAuth: Boolean, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetSeriesListRawItem> { | ||||
|         var where = series.member.id.eq(creatorId) | ||||
|             .and(series.isActive.isTrue) | ||||
|  | ||||
|         if (!isAuth) { | ||||
|             where = where.and(series.isAdult.isFalse) | ||||
|         } | ||||
|  | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetSeriesListRawItem( | ||||
|                     series.id, | ||||
|                     series.title, | ||||
|                     series.coverImage.coalesce("profile/default-profile.png") | ||||
|                         .prepend("/") | ||||
|                         .prepend(imageHost), | ||||
|                     series.publishedDaysOfWeek, | ||||
|                     series.state, | ||||
|                     series.genre.genre, | ||||
|                     series.isAdult, | ||||
|                     series.member.id, | ||||
|                     series.member.nickname, | ||||
|                     series.member.profileImage.coalesce("profile/default-profile.png") | ||||
|                         .prepend("/") | ||||
|                         .prepend(imageHost) | ||||
|                 ) | ||||
|             ) | ||||
|             .from(series) | ||||
|             .innerJoin(series.member, member) | ||||
|             .innerJoin(series.genre, seriesGenre) | ||||
|             .where(where) | ||||
|             .fetch() | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesSortType | ||||
| import kr.co.vividnext.sodalive.member.Member | ||||
| import org.springframework.beans.factory.annotation.Value | ||||
| import org.springframework.stereotype.Service | ||||
| import java.time.LocalDateTime | ||||
|  | ||||
| @Service | ||||
| class ContentSeriesService( | ||||
|     private val repository: ContentSeriesRepository, | ||||
|     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.getSeriesRawItemList( | ||||
|             imageHost = coverImageHost, | ||||
|             creatorId = creatorId, | ||||
|             isAuth = member.auth != null, | ||||
|             offset = offset, | ||||
|             limit = limit | ||||
|         ) | ||||
|  | ||||
|         val items = rawItems | ||||
|             .map { it.toSeriesListItem() } | ||||
|             .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) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,57 @@ | ||||
| package kr.co.vividnext.sodalive.content.series | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek | ||||
| import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState | ||||
|  | ||||
| data class GetSeriesListRawItem @QueryProjection constructor( | ||||
|     val seriesId: Long, | ||||
|     val title: String, | ||||
|     val coverImage: String, | ||||
|     val publishedDaysOfWeek: Set<SeriesPublishedDaysOfWeek>, | ||||
|     val state: SeriesState, | ||||
|     val genre: String, | ||||
|     val isAdult: Boolean, | ||||
|     val creatorId: Long, | ||||
|     val creatorNickname: String, | ||||
|     val creatorProfileImage: String | ||||
|  | ||||
| ) { | ||||
|     fun toSeriesListItem(): GetSeriesListResponse.SeriesListItem { | ||||
|         return GetSeriesListResponse.SeriesListItem( | ||||
|             seriesId = seriesId, | ||||
|             title = title, | ||||
|             coverImage = coverImage, | ||||
|             publishedDaysOfWeek = publishedDaysOfWeekText(), | ||||
|             isComplete = state == SeriesState.COMPLETE, | ||||
|             creator = GetSeriesListResponse.SeriesListItemCreator( | ||||
|                 creatorId = creatorId, | ||||
|                 nickname = creatorNickname, | ||||
|                 profileImage = creatorProfileImage | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     private fun publishedDaysOfWeekText(): 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,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,65 @@ | ||||
| package kr.co.vividnext.sodalive.content.series.content | ||||
|  | ||||
| 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 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 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 | ||||
|     } | ||||
| } | ||||
| @@ -25,6 +25,10 @@ enum class SeriesState { | ||||
|     PROCEEDING, SUSPEND, COMPLETE | ||||
| } | ||||
|  | ||||
| enum class SeriesSortType { | ||||
|     NEWEST, POPULAR | ||||
| } | ||||
|  | ||||
| @Entity | ||||
| data class Series( | ||||
|     var title: String, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user