diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt new file mode 100644 index 0000000..f7a187b --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesController.kt @@ -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() + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt new file mode 100644 index 0000000..6aaa22f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -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, ContentSeriesQueryRepository + +interface ContentSeriesQueryRepository { + fun getSeriesTotalCount(creatorId: Long, isAuth: Boolean): Int + fun getSeriesRawItemList( + imageHost: String, + creatorId: Long, + isAuth: Boolean, + offset: Long, + limit: Long + ): List +} + +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 { + 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() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt new file mode 100644 index 0000000..f7ac607 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -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) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListRawItem.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListRawItem.kt new file mode 100644 index 0000000..8b65f7d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListRawItem.kt @@ -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, + 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}요일" + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListResponse.kt new file mode 100644 index 0000000..f83b40c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesListResponse.kt @@ -0,0 +1,24 @@ +package kr.co.vividnext.sodalive.content.series + +data class GetSeriesListResponse( + val totalCount: Int, + val items: List +) { + 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 + ) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/ContentSeriesContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/ContentSeriesContentRepository.kt new file mode 100644 index 0000000..e8f955a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/content/ContentSeriesContentRepository.kt @@ -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, 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 + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/series/Series.kt b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/series/Series.kt index 00c9866..82a63f4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/series/Series.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/content/series/Series.kt @@ -25,6 +25,10 @@ enum class SeriesState { PROCEEDING, SUSPEND, COMPLETE } +enum class SeriesSortType { + NEWEST, POPULAR +} + @Entity data class Series( var title: String,