Merge pull request 'test' (#258) from test into main

Reviewed-on: #258
This commit is contained in:
klaus 2025-02-14 18:09:11 +00:00
commit d2dc045255
34 changed files with 1005 additions and 99 deletions

View File

@ -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.banner.QAudioContentBanner.audioContentBanner
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration 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.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.content.main.tab.QAudioContentMainTab.audioContentMainTab
import kr.co.vividnext.sodalive.content.order.QOrder.order import kr.co.vividnext.sodalive.content.order.QOrder.order
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
@ -72,6 +73,14 @@ interface AudioContentQueryRepository {
isFree: Boolean = false isFree: Boolean = false
): List<GetAudioContentMainItem> ): List<GetAudioContentMainItem>
fun findAlarmContentByTheme(
memberId: Long,
theme: List<String>,
isAdult: Boolean = false,
offset: Long = 0,
limit: Long = 20
): List<GetAudioContentMainItem>
fun totalCountByTheme( fun totalCountByTheme(
memberId: Long, memberId: Long,
theme: String = "", theme: String = "",
@ -80,6 +89,7 @@ interface AudioContentQueryRepository {
): Int ): Int
fun findByThemeFor2Weeks( fun findByThemeFor2Weeks(
isFree: Boolean = false,
cloudfrontHost: String, cloudfrontHost: String,
memberId: Long, memberId: Long,
theme: String = "", theme: String = "",
@ -90,6 +100,7 @@ interface AudioContentQueryRepository {
): List<GetAudioContentMainItem> ): List<GetAudioContentMainItem>
fun totalCountNewContentFor2Weeks( fun totalCountNewContentFor2Weeks(
isFree: Boolean = false,
theme: String, theme: String,
memberId: Long, memberId: Long,
isAdult: Boolean, isAdult: Boolean,
@ -109,6 +120,14 @@ interface AudioContentQueryRepository {
contentType: ContentType contentType: ContentType
): List<GetAudioContentMainItem> ): List<GetAudioContentMainItem>
fun findAudioContentByCurationIdV2(
curationId: Long,
memberId: Long,
isAdult: Boolean,
offset: Long = 0,
limit: Long = 20
): List<GetAudioContentMainItem>
fun getAudioContentRanking( fun getAudioContentRanking(
cloudfrontHost: String, cloudfrontHost: String,
isAdult: Boolean, isAdult: Boolean,
@ -413,6 +432,60 @@ class AudioContentQueryRepositoryImpl(
.fetch() .fetch()
} }
override fun findAlarmContentByTheme(
memberId: Long,
theme: List<String>,
isAdult: Boolean,
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
val orderBy = listOf(audioContent.releaseDate.desc(), audioContent.id.desc())
var where = audioContent.isActive.isTrue
.and(audioContent.duration.isNotNull)
.and(
audioContent.releaseDate.isNull
.or(audioContent.releaseDate.loe(LocalDateTime.now()))
.or(audioContent.member.id.eq(memberId))
)
.and(blockMember.id.isNull)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
}
if (theme.isNotEmpty()) {
where = where.and(audioContentTheme.theme.`in`(theme))
}
return queryFactory
.select(
QGetAudioContentMainItem(
audioContent.id,
audioContent.coverImage.prepend("/").prepend(imageHost),
audioContent.title,
member.id,
member.profileImage.prepend("/").prepend(imageHost),
member.nickname,
audioContent.price,
audioContent.duration
)
)
.from(audioContent)
.innerJoin(audioContent.member, member)
.innerJoin(audioContent.theme, audioContentTheme)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.offset(offset)
.limit(limit)
.orderBy(*orderBy.toTypedArray())
.fetch()
}
override fun totalCountByTheme(memberId: Long, theme: String, isAdult: Boolean, contentType: ContentType): Int { override fun totalCountByTheme(memberId: Long, theme: String, isAdult: Boolean, contentType: ContentType): Int {
var where = audioContent.isActive.isTrue var where = audioContent.isActive.isTrue
.and(audioContent.duration.isNotNull) .and(audioContent.duration.isNotNull)
@ -447,6 +520,7 @@ class AudioContentQueryRepositoryImpl(
} }
override fun totalCountNewContentFor2Weeks( override fun totalCountNewContentFor2Weeks(
isFree: Boolean,
theme: String, theme: String,
memberId: Long, memberId: Long,
isAdult: Boolean, isAdult: Boolean,
@ -475,6 +549,10 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContentTheme.theme.eq(theme)) where = where.and(audioContentTheme.theme.eq(theme))
} }
if (isFree) {
where = where.and(audioContent.price.loe(0))
}
return queryFactory return queryFactory
.select(audioContent.id) .select(audioContent.id)
.from(audioContent) .from(audioContent)
@ -486,6 +564,7 @@ class AudioContentQueryRepositoryImpl(
} }
override fun findByThemeFor2Weeks( override fun findByThemeFor2Weeks(
isFree: Boolean,
cloudfrontHost: String, cloudfrontHost: String,
memberId: Long, memberId: Long,
theme: String, theme: String,
@ -517,6 +596,10 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContentTheme.theme.eq(theme)) where = where.and(audioContentTheme.theme.eq(theme))
} }
if (isFree) {
where = where.and(audioContent.price.loe(0))
}
return queryFactory return queryFactory
.select( .select(
QGetAudioContentMainItem( QGetAudioContentMainItem(
@ -648,6 +731,54 @@ class AudioContentQueryRepositoryImpl(
.fetch() .fetch()
} }
override fun findAudioContentByCurationIdV2(
curationId: Long,
memberId: Long,
isAdult: Boolean,
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
var where = audioContentCuration.isActive.isTrue
.and(audioContentCurationItem.isActive.isTrue)
.and(audioContent.isActive.isTrue)
.and(audioContent.member.isNotNull)
.and(audioContent.duration.isNotNull)
.and(audioContent.member.isActive.isTrue)
.and(audioContentCuration.id.eq(curationId))
.and(blockMember.id.isNull)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
}
return queryFactory
.select(
QGetAudioContentMainItem(
audioContent.id,
audioContent.coverImage.prepend("/").prepend(imageHost),
audioContent.title,
member.id,
member.profileImage.prepend("/").prepend(imageHost),
member.nickname,
audioContent.price,
audioContent.duration
)
)
.from(audioContentCurationItem)
.innerJoin(audioContentCurationItem.content, audioContent)
.innerJoin(audioContentCurationItem.curation, audioContentCuration)
.innerJoin(audioContent.member, member)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.offset(offset)
.limit(limit)
.fetch()
}
override fun getAudioContentRanking( override fun getAudioContentRanking(
cloudfrontHost: String, cloudfrontHost: String,
isAdult: Boolean, isAdult: Boolean,
@ -679,7 +810,8 @@ class AudioContentQueryRepositoryImpl(
audioContent.price, audioContent.price,
audioContent.duration, audioContent.duration,
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )

View File

@ -92,6 +92,7 @@ class AudioContentMainController(
@GetMapping("/new/all") @GetMapping("/new/all")
fun getNewContentAllByTheme( fun getNewContentAllByTheme(
@RequestParam("isFree", required = false) isFree: Boolean? = null,
@RequestParam("theme") theme: String, @RequestParam("theme") theme: String,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null, @RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null, @RequestParam("contentType", required = false) contentType: ContentType? = null,
@ -102,6 +103,7 @@ class AudioContentMainController(
ApiResponse.ok( ApiResponse.ok(
service.getNewContentFor2WeeksByTheme( service.getNewContentFor2WeeksByTheme(
isFree = isFree ?: false,
theme = theme, theme = theme,
isAdultContentVisible = isAdultContentVisible ?: true, isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL, contentType = contentType ?: ContentType.ALL,

View File

@ -50,6 +50,7 @@ class AudioContentMainService(
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun getNewContentFor2WeeksByTheme( fun getNewContentFor2WeeksByTheme(
isFree: Boolean,
theme: String, theme: String,
isAdultContentVisible: Boolean, isAdultContentVisible: Boolean,
contentType: ContentType, contentType: ContentType,
@ -57,12 +58,14 @@ class AudioContentMainService(
pageable: Pageable pageable: Pageable
): GetNewContentAllResponse { ): GetNewContentAllResponse {
val totalCount = repository.totalCountNewContentFor2Weeks( val totalCount = repository.totalCountNewContentFor2Weeks(
isFree,
theme, theme,
memberId = member.id!!, memberId = member.id!!,
isAdult = member.auth != null && isAdultContentVisible, isAdult = member.auth != null && isAdultContentVisible,
contentType = contentType contentType = contentType
) )
val items = repository.findByThemeFor2Weeks( val items = repository.findByThemeFor2Weeks(
isFree,
cloudfrontHost = imageHost, cloudfrontHost = imageHost,
memberId = member.id!!, memberId = member.id!!,
theme = theme, theme = theme,

View File

@ -17,5 +17,6 @@ data class GetAudioContentRankingItem @QueryProjection constructor(
@JsonProperty("price") val price: Int, @JsonProperty("price") val price: Int,
@JsonProperty("duration") val duration: String, @JsonProperty("duration") val duration: String,
@JsonProperty("creatorId") val creatorId: Long, @JsonProperty("creatorId") val creatorId: Long,
@JsonProperty("creatorNickname") val creatorNickname: String @JsonProperty("creatorNickname") val creatorNickname: String,
@JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String
) )

View File

@ -109,9 +109,7 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact
fun findByContentMainTabIdAndTitle( fun findByContentMainTabIdAndTitle(
tabId: Long, tabId: Long,
title: String, title: String,
isAdult: Boolean, isAdult: Boolean
offset: Long = 0,
limit: Long = 12
): List<AudioContentCuration> { ): List<AudioContentCuration> {
var where = audioContentCuration.isActive.isTrue var where = audioContentCuration.isActive.isTrue
.and(audioContentMainTab.id.eq(tabId)) .and(audioContentMainTab.id.eq(tabId))

View File

@ -0,0 +1,62 @@
package kr.co.vividnext.sodalive.content.main.tab
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.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Repository
@Repository
class AudioContentMainTabRepository(
private val queryFactory: JPAQueryFactory,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
) {
fun findCreatorByThemeContent(
memberId: Long,
theme: String,
minCount: Int
): List<ContentCreatorResponse> {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
val where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue)
.and(audioContent.duration.isNotNull)
.and(audioContent.limited.isNull)
.and(audioContentTheme.isActive.isTrue)
.and(audioContentTheme.theme.eq(theme))
.and(blockMember.id.isNull)
return queryFactory
.select(
QContentCreatorResponse(
member.id,
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(member)
.innerJoin(audioContent).on(member.id.eq(audioContent.member.id))
.innerJoin(audioContent.theme, audioContentTheme)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(member.id)
.having(audioContent.id.count().goe(minCount))
.orderBy(
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
)
.offset(0)
.limit(20)
.fetch()
}
}

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.content.main.tab
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
data class GetPopularContentByCreatorResponse(
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>
)

View File

@ -7,5 +7,6 @@ data class GetRecommendSeriesListResponse @QueryProjection constructor(
val title: String, val title: String,
val imageUrl: String, val imageUrl: String,
val creatorId: Long, val creatorId: Long,
val creatorNickname: String val creatorNickname: String,
val creatorProfileImageUrl: String
) )

View File

@ -30,7 +30,8 @@ class RecommendSeriesRepository(
series.title, series.title,
recommendSeries.imagePath.prepend("/").prepend(imageHost), recommendSeries.imagePath.prepend("/").prepend(imageHost),
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )
.from(recommendSeries) .from(recommendSeries)
@ -57,7 +58,8 @@ class RecommendSeriesRepository(
series.title, series.title,
recommendSeries.imagePath.prepend("/").prepend(imageHost), recommendSeries.imagePath.prepend("/").prepend(imageHost),
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )
.from(recommendSeries) .from(recommendSeries)

View File

@ -3,9 +3,11 @@ package kr.co.vividnext.sodalive.content.main.tab.alarm
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +21,22 @@ class AudioContentMainTabAlarmController(private val service: AudioContentMainTa
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/all")
fun fetchAlarmContentByTheme(
@RequestParam("theme") theme: String,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.fetchAlarmContentByTheme(
theme,
member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
} }

View File

@ -1,7 +1,7 @@
package kr.co.vividnext.sodalive.content.main.tab.alarm package kr.co.vividnext.sodalive.content.main.tab.alarm
import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
@ -32,11 +32,10 @@ class AudioContentMainTabAlarmService(
) )
val alarmThemeList = listOf("모닝콜", "슬립콜", "알람") val alarmThemeList = listOf("모닝콜", "슬립콜", "알람")
val newAlarmContentList = contentRepository.findByTheme( val newAlarmContentList = contentRepository.findAlarmContentByTheme(
memberId = memberId, memberId = memberId,
theme = alarmThemeList[0], theme = alarmThemeList,
isAdult = isAdult, isAdult = isAdult,
contentType = ContentType.ALL,
limit = 10 limit = 10
) )
@ -64,10 +63,10 @@ class AudioContentMainTabAlarmService(
.map { .map {
GetContentCurationResponse( GetContentCurationResponse(
title = it.title, title = it.title,
items = contentRepository.findAudioContentByCurationId( items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!, curationId = it.id!!,
isAdult = isAdult, memberId = memberId,
contentType = ContentType.ALL isAdult = isAdult
) )
) )
} }
@ -81,4 +80,28 @@ class AudioContentMainTabAlarmService(
curationList = curationList curationList = curationList
) )
} }
fun fetchAlarmContentByTheme(
theme: String,
member: Member,
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
val alarmThemeList = if (theme.isNotBlank()) {
listOf(theme)
} else {
listOf("모닝콜", "슬립콜", "알람")
}
val memberId = member.id!!
val isAdult = member.auth != null
return contentRepository.findAlarmContentByTheme(
memberId = memberId,
theme = alarmThemeList,
isAdult = isAdult,
offset = offset,
limit = limit
)
}
} }

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +20,19 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service @Service
class AudioContentMainTabAsmrService( class AudioContentMainTabAsmrService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService, private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository, private val contentRepository: AudioContentRepository,
private val rankingService: RankingService, private val rankingService: RankingService,
@ -59,16 +62,42 @@ class AudioContentMainTabAsmrService(
theme = theme theme = theme
) )
val creatorList = repository.findCreatorByThemeContent(
memberId = memberId,
theme = theme,
minCount = 4
)
val salesRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val salesCountRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val eventBannerList = eventService.getEventList(isAdult = isAdult) val eventBannerList = eventService.getEventList(isAdult = isAdult)
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.map { .map {
GetContentCurationResponse( GetContentCurationResponse(
title = it.title, title = it.title,
items = contentRepository.findAudioContentByCurationId( items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!, curationId = it.id!!,
isAdult = isAdult, memberId = memberId,
contentType = ContentType.ALL isAdult = isAdult
) )
) )
} }
@ -77,8 +106,31 @@ class AudioContentMainTabAsmrService(
contentBannerList = contentBannerList, contentBannerList = contentBannerList,
newAsmrContentList = newAsmrContentList, newAsmrContentList = newAsmrContentList,
rankAsmrContentList = rankAsmrContentList, rankAsmrContentList = rankAsmrContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
curationList = curationList curationList = curationList
) )
} }
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val theme = "ASMR"
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.content.main.tab.asmr package kr.co.vividnext.sodalive.content.main.tab.asmr
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabAsmrResponse(
val contentBannerList: List<GetAudioContentBannerResponse>, val contentBannerList: List<GetAudioContentBannerResponse>,
val newAsmrContentList: List<GetAudioContentMainItem>, val newAsmrContentList: List<GetAudioContentMainItem>,
val rankAsmrContentList: List<GetAudioContentRankingItem>, val rankAsmrContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse, val eventBannerList: GetEventResponse,
val curationList: List<GetContentCurationResponse> val curationList: List<GetContentCurationResponse>
) )

View File

@ -2,10 +2,13 @@ package kr.co.vividnext.sodalive.content.main.tab.content
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -13,10 +16,70 @@ import org.springframework.web.bind.annotation.RestController
class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) { class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) {
@GetMapping @GetMapping
fun fetchContentMainTabContent( fun fetchContentMainTabContent(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(
service.fetchData(
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member
)
)
}
@GetMapping("/ranking")
fun getAudioContentRanking(
@RequestParam("sort-type", required = false) sortType: String? = "매출",
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getAudioContentRanking(
memberId = member.id!!,
isAdult = member.auth != null,
sortType = sortType ?: "매출"
)
)
}
@GetMapping("/new-content-by-theme")
fun getNewContentByTheme(
@RequestParam("theme") theme: String,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getNewContentByTheme(
theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member
)
)
}
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
} }
} }

View File

@ -2,7 +2,10 @@ package kr.co.vividnext.sodalive.content.main.tab.content
import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
@ -18,7 +21,11 @@ class AudioContentMainTabContentService(
private val rankingService: RankingService, private val rankingService: RankingService,
private val eventService: EventService private val eventService: EventService
) { ) {
fun fetchData(member: Member): GetContentMainTabContentResponse { fun fetchData(
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member
): GetContentMainTabContentResponse {
val memberId = member.id!! val memberId = member.id!!
val isAdult = member.auth != null val isAdult = member.auth != null
@ -33,18 +40,14 @@ class AudioContentMainTabContentService(
val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult)
// 새로운 단편 // 새로운 단편
val newContentList = if (themeOfContentList.isNotEmpty()) { val newContentList = audioContentRepository.findByTheme(
audioContentRepository.findByTheme( memberId = member.id!!,
memberId = member.id!!, theme = "",
theme = themeOfContentList[0], isAdult = member.auth != null,
isAdult = member.auth != null, contentType = ContentType.ALL,
contentType = ContentType.ALL, offset = 0,
offset = 0, limit = 10
limit = 10 )
)
} else {
emptyList()
}
// 일간 랭킹 // 일간 랭킹
val currentDateTime = LocalDateTime.now() val currentDateTime = LocalDateTime.now()
@ -102,4 +105,59 @@ class AudioContentMainTabContentService(
eventBannerList = eventBannerList eventBannerList = eventBannerList
) )
} }
fun getAudioContentRanking(
memberId: Long,
isAdult: Boolean,
sortType: String = "매출"
): List<GetAudioContentRankingItem> {
val currentDateTime = LocalDateTime.now()
val dailyRankingStartDate = currentDateTime
.withHour(15)
.withMinute(0)
.withSecond(0)
.minusDays(2)
val dailyRankingEndDate = dailyRankingStartDate
.plusDays(1)
return rankingService.getContentRanking(
memberId = memberId,
isAdult = isAdult,
startDate = dailyRankingStartDate,
endDate = dailyRankingEndDate
)
}
fun getNewContentByTheme(
theme: String,
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member
): List<GetAudioContentMainItem> {
return audioContentRepository.findByTheme(
memberId = member.id!!,
theme = theme,
isAdult = member.auth != null && isAdultContentVisible,
contentType = contentType,
offset = 0,
limit = 10
)
}
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -2,10 +2,13 @@ package kr.co.vividnext.sodalive.content.main.tab.free
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +22,42 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/introduce-creator")
fun getIntroduceCreator(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getIntroduceCreator(
member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/new-content-by-theme")
fun getNewContentByTheme(
@RequestParam("theme") theme: String,
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getNewContentByTheme(
theme,
isAdultContentVisible = isAdultContentVisible ?: true,
contentType = contentType ?: ContentType.ALL,
member,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
} }

View File

@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.main.tab.free
import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
@ -38,10 +39,10 @@ class AudioContentMainTabFreeService(
.map { .map {
GetContentCurationResponse( GetContentCurationResponse(
title = it.title, title = it.title,
items = contentRepository.findAudioContentByCurationId( items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!, curationId = it.id!!,
isAdult = isAdult, memberId = memberId,
contentType = ContentType.ALL isAdult = isAdult
) )
) )
} }
@ -64,13 +65,14 @@ class AudioContentMainTabFreeService(
} }
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.filter { it.title != "크리에이터 소개" }
.map { .map {
GetContentCurationResponse( GetContentCurationResponse(
title = it.title, title = it.title,
items = contentRepository.findAudioContentByCurationId( items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!, curationId = it.id!!,
isAdult = isAdult, memberId = memberId,
contentType = ContentType.ALL isAdult = isAdult
) )
) )
} }
@ -88,4 +90,46 @@ class AudioContentMainTabFreeService(
curationList = curationList curationList = curationList
) )
} }
fun getIntroduceCreator(member: Member, offset: Long, limit: Long): List<GetAudioContentMainItem> {
val isAdult = member.auth != null
val memberId = member.id!!
val introduceCreatorCuration = curationRepository.findByContentMainTabIdAndTitle(
tabId = 7L,
title = "크리에이터 소개",
isAdult = isAdult
)
return if (introduceCreatorCuration.isNotEmpty()) {
contentRepository.findAudioContentByCurationIdV2(
curationId = introduceCreatorCuration[0].id!!,
memberId = memberId,
isAdult = isAdult,
offset = offset,
limit = limit
)
} else {
emptyList()
}
}
fun getNewContentByTheme(
theme: String,
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member,
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
return audioContentRepository.findByTheme(
memberId = member.id!!,
theme = theme,
isAdult = member.auth != null && isAdultContentVisible,
contentType = contentType,
offset = offset,
limit = limit,
isFree = true
)
}
} }

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +20,19 @@ class AudioContentMainTabHomeController(private val service: AudioContentMainTab
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.content.main.tab.home package kr.co.vividnext.sodalive.content.main.tab.home
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.notice.ServiceNoticeService import kr.co.vividnext.sodalive.notice.ServiceNoticeService
@ -114,4 +115,21 @@ class AudioContentMainTabHomeService(
salesCountRankContentList = salesCountRankContentList salesCountRankContentList = salesCountRankContentList
) )
} }
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -6,11 +6,11 @@ import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerRespons
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.event.GetEventResponse import kr.co.vividnext.sodalive.event.GetEventResponse
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
import kr.co.vividnext.sodalive.notice.NoticeTitleItem import kr.co.vividnext.sodalive.notice.NoticeItem
data class GetContentMainTabHomeResponse( data class GetContentMainTabHomeResponse(
val tabId: Long = 1, val tabId: Long = 1,
val latestNotice: NoticeTitleItem?, val latestNotice: NoticeItem?,
val bannerList: List<GetAudioContentBannerResponse>, val bannerList: List<GetAudioContentBannerResponse>,
val rankCreatorList: GetExplorerSectionResponse, val rankCreatorList: GetExplorerSectionResponse,
val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>, val rankSeriesList: List<GetSeriesListResponse.SeriesListItem>,

View File

@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +20,19 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/popular-content-by-creator")
fun getPopularContentByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getPopularContentByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -4,7 +4,9 @@ import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.ContentType
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.AudioContentMainTabRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
import kr.co.vividnext.sodalive.content.main.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service @Service
class AudioContentMainTabLiveReplayService( class AudioContentMainTabLiveReplayService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService, private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository, private val contentRepository: AudioContentRepository,
private val rankingService: RankingService, private val rankingService: RankingService,
@ -59,16 +62,42 @@ class AudioContentMainTabLiveReplayService(
theme = theme theme = theme
) )
val creatorList = repository.findCreatorByThemeContent(
memberId = memberId,
theme = theme,
minCount = 4
)
val salesRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val salesCountRankContentList = if (creatorList.isNotEmpty()) {
rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorList[0].creatorId,
isAdult = isAdult,
theme = theme
)
} else {
emptyList()
}
val eventBannerList = eventService.getEventList(isAdult = isAdult) val eventBannerList = eventService.getEventList(isAdult = isAdult)
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.map { .map {
GetContentCurationResponse( GetContentCurationResponse(
title = it.title, title = it.title,
items = contentRepository.findAudioContentByCurationId( items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!, curationId = it.id!!,
isAdult = isAdult, memberId = memberId,
contentType = ContentType.ALL isAdult = isAdult
) )
) )
} }
@ -77,8 +106,31 @@ class AudioContentMainTabLiveReplayService(
contentBannerList = contentBannerList, contentBannerList = contentBannerList,
newLiveReplayContentList = newLiveReplayContentList, newLiveReplayContentList = newLiveReplayContentList,
rankLiveReplayContentList = rankLiveReplayContentList, rankLiveReplayContentList = rankLiveReplayContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
curationList = curationList curationList = curationList
) )
} }
fun getPopularContentByCreator(creatorId: Long, isAdult: Boolean): GetPopularContentByCreatorResponse {
val theme = "다시듣기"
val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2(
creatorId = creatorId,
isAdult = isAdult,
theme = theme
)
return GetPopularContentByCreatorResponse(
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList
)
}
} }

View File

@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.content.main.tab.replay package kr.co.vividnext.sodalive.content.main.tab.replay
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabLiveReplayResponse(
val contentBannerList: List<GetAudioContentBannerResponse>, val contentBannerList: List<GetAudioContentBannerResponse>,
val newLiveReplayContentList: List<GetAudioContentMainItem>, val newLiveReplayContentList: List<GetAudioContentMainItem>,
val rankLiveReplayContentList: List<GetAudioContentRankingItem>, val rankLiveReplayContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse, val eventBannerList: GetEventResponse,
val curationList: List<GetContentCurationResponse> val curationList: List<GetContentCurationResponse>
) )

View File

@ -3,9 +3,11 @@ package kr.co.vividnext.sodalive.content.main.tab.series
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import org.springframework.data.domain.Pageable
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
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.RestController import org.springframework.web.bind.annotation.RestController
@RestController @RestController
@ -19,4 +21,69 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
ApiResponse.ok(service.fetchData(member)) ApiResponse.ok(service.fetchData(member))
} }
@GetMapping("/original")
fun getOriginalAudioDramaList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getOriginalAudioDramaList(
memberId = member.id!!,
isAdult = member.auth != null,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/completed-monthly-rank")
fun getRankMonthlyCompletedSeriesList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getRankMonthlyCompletedSeriesList(
memberId = member.id!!,
isAdult = member.auth != null,
offset = pageable.offset,
limit = pageable.pageSize.toLong()
)
)
}
@GetMapping("/recommend-by-genre")
fun getRecommendSeriesListByGenre(
@RequestParam genreId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getRecommendSeriesListByGenre(
genreId,
memberId = member.id!!,
isAdult = member.auth != null
)
)
}
@GetMapping("/recommend-series-by-creator")
fun getRecommendSeriesByCreator(
@RequestParam creatorId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(
service.getRecommendSeriesByCreator(
creatorId = creatorId,
isAdult = member.auth != null
)
)
}
} }

View File

@ -4,6 +4,7 @@ import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository
import kr.co.vividnext.sodalive.content.series.ContentSeriesService import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse
import kr.co.vividnext.sodalive.event.EventService import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService import kr.co.vividnext.sodalive.rank.RankingService
@ -23,15 +24,17 @@ class AudioContentMainTabSeriesService(
) { ) {
fun fetchData(member: Member): GetContentMainTabSeriesResponse { fun fetchData(member: Member): GetContentMainTabSeriesResponse {
val isAdult = member.auth != null val isAdult = member.auth != null
val memberId = member.id!!
// 메인 배너 (시리즈) // 메인 배너 (시리즈)
val contentBannerList = bannerService.getBannerList( val contentBannerList = bannerService.getBannerList(
tabId = 2, tabId = 2,
memberId = member.id!!, memberId = memberId,
isAdult = isAdult isAdult = isAdult
) )
val originalAudioDrama = seriesService.getOriginalAudioDramaList( val originalAudioDrama = seriesService.getOriginalAudioDramaList(
memberId = memberId,
isAdult = isAdult, isAdult = isAdult,
offset = 0, offset = 0,
limit = 20 limit = 20
@ -48,7 +51,7 @@ class AudioContentMainTabSeriesService(
.plusDays(1) .plusDays(1)
val rankSeriesList = rankingService.getSeriesRanking( val rankSeriesList = rankingService.getSeriesRanking(
memberId = member.id!!, memberId = memberId,
isAdult = isAdult, isAdult = isAdult,
startDate = dailyRankingStartDate, startDate = dailyRankingStartDate,
endDate = dailyRankingEndDate endDate = dailyRankingEndDate
@ -60,7 +63,7 @@ class AudioContentMainTabSeriesService(
// 장르별 추천 시리즈 // 장르별 추천 시리즈
val recommendSeriesList = if (genreList.isNotEmpty()) { val recommendSeriesList = if (genreList.isNotEmpty()) {
rankingService.getSeriesAllRankingByGenre( rankingService.getSeriesAllRankingByGenre(
memberId = member.id!!, memberId = memberId,
isAdult = isAdult, isAdult = isAdult,
genreId = genreList[0].id genreId = genreList[0].id
) )
@ -82,7 +85,7 @@ class AudioContentMainTabSeriesService(
.plusMonths(1) .plusMonths(1)
val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking(
memberId = member.id!!, memberId = memberId,
isAdult = isAdult, isAdult = isAdult,
startDate = monthlyRankingStartDate, startDate = monthlyRankingStartDate,
endDate = monthlyRankingEndDate endDate = monthlyRankingEndDate
@ -98,7 +101,7 @@ class AudioContentMainTabSeriesService(
.plusDays(6) .plusDays(6)
val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20(
memberId = member.id!!, memberId = memberId,
startDate = startDate.minusDays(1), startDate = startDate.minusDays(1),
endDate = endDate endDate = endDate
) )
@ -122,7 +125,7 @@ class AudioContentMainTabSeriesService(
title = it.title, title = it.title,
items = seriesService.fetchSeriesByCurationId( items = seriesService.fetchSeriesByCurationId(
curationId = it.id!!, curationId = it.id!!,
memberId = member.id!!, memberId = memberId,
isAdult = isAdult isAdult = isAdult
) )
) )
@ -141,9 +144,79 @@ class AudioContentMainTabSeriesService(
newSeriesList = newSeriesList, newSeriesList = newSeriesList,
rankCompleteSeriesList = rankCompleteSeriesList, rankCompleteSeriesList = rankCompleteSeriesList,
seriesRankCreatorList = seriesRankCreatorList, seriesRankCreatorList = seriesRankCreatorList,
salesRankContentList = salesRankContentList, recommendSeriesByChannel = salesRankContentList,
eventBannerList = eventBannerList, eventBannerList = eventBannerList,
curationList = curationList curationList = curationList
) )
} }
fun getOriginalAudioDramaList(
memberId: Long,
isAdult: Boolean,
offset: Long,
limit: Long
): GetSeriesListResponse {
val totalCount = seriesService.getOriginalAudioDramaTotalCount(memberId, isAdult)
val items = seriesService.getOriginalAudioDramaList(
memberId = memberId,
isAdult = isAdult,
offset = offset,
limit = limit
)
return GetSeriesListResponse(totalCount, items)
}
fun getRankMonthlyCompletedSeriesList(
memberId: Long,
isAdult: Boolean,
offset: Long,
limit: Long
): GetSeriesListResponse {
val monthlyRankingStartDate = LocalDateTime.now()
.withDayOfMonth(1)
.withHour(15)
.withMinute(0)
.withSecond(0)
.minusDays(1)
val monthlyRankingEndDate = monthlyRankingStartDate
.plusMonths(1)
val totalCount = rankingService.getCompleteSeriesRankingTotalCount(
memberId = memberId,
isAdult = isAdult,
startDate = monthlyRankingStartDate,
endDate = monthlyRankingEndDate
)
val items = rankingService.getCompleteSeriesRanking(
memberId = memberId,
isAdult = isAdult,
startDate = monthlyRankingStartDate,
endDate = monthlyRankingEndDate,
offset = offset,
limit = limit
)
return GetSeriesListResponse(totalCount, items)
}
fun getRecommendSeriesListByGenre(
genreId: Long,
memberId: Long,
isAdult: Boolean
): List<GetSeriesListResponse.SeriesListItem> {
return rankingService.getSeriesAllRankingByGenre(
memberId = memberId,
isAdult = isAdult,
genreId = genreId
)
}
fun getRecommendSeriesByCreator(creatorId: Long, isAdult: Boolean): List<GetSeriesListResponse.SeriesListItem> {
return rankingService.fetchCreatorSeriesBySales(
creatorId = creatorId,
isAdult = isAdult
)
}
} }

View File

@ -17,7 +17,7 @@ data class GetContentMainTabSeriesResponse(
val newSeriesList: List<GetRecommendSeriesListResponse>, val newSeriesList: List<GetRecommendSeriesListResponse>,
val rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>, val rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>,
val seriesRankCreatorList: List<ContentCreatorResponse>, val seriesRankCreatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetSeriesListResponse.SeriesListItem>, val recommendSeriesByChannel: List<GetSeriesListResponse.SeriesListItem>,
val eventBannerList: GetEventResponse, val eventBannerList: GetEventResponse,
val curationList: List<GetSeriesCurationResponse> val curationList: List<GetSeriesCurationResponse>
) )

View File

@ -35,7 +35,8 @@ interface ContentSeriesQueryRepository {
fun getKeywordList(seriesId: Long): List<String> fun getKeywordList(seriesId: Long): List<String>
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series> fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List<Series>
fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long = 0, limit: Long = 20): List<Series> fun getOriginalAudioDramaList(memberId: Long, isAdult: Boolean, offset: Long = 0, limit: Long = 20): List<Series>
fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int
fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse>
fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series> fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series>
} }
@ -143,9 +144,14 @@ class ContentSeriesQueryRepositoryImpl(
.fetch() .fetch()
} }
override fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long, limit: Long): List<Series> { override fun getOriginalAudioDramaList(memberId: Long, isAdult: Boolean, offset: Long, limit: Long): List<Series> {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
var where = series.isOriginal.isTrue var where = series.isOriginal.isTrue
.and(series.isActive.isTrue) .and(series.isActive.isTrue)
.and(blockMember.id.isNull)
if (!isAdult) { if (!isAdult) {
where = where.and(series.isAdult.isFalse) where = where.and(series.isAdult.isFalse)
@ -153,13 +159,38 @@ class ContentSeriesQueryRepositoryImpl(
return queryFactory return queryFactory
.selectFrom(series) .selectFrom(series)
.innerJoin(series.member, member)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where) .where(where)
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc()) .orderBy(series.id.desc())
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.fetch() .fetch()
} }
override fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
var where = series.isOriginal.isTrue
.and(series.isActive.isTrue)
.and(blockMember.id.isNull)
if (!isAdult) {
where = where.and(series.isAdult.isFalse)
}
return queryFactory
.select(series.id)
.from(series)
.innerJoin(series.member, member)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.fetch()
.size
}
override fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> { override fun getGenreList(isAdult: Boolean): List<GetSeriesGenreListResponse> {
var where = seriesGenre.isActive.isTrue var where = seriesGenre.isActive.isTrue

View File

@ -30,12 +30,17 @@ class ContentSeriesService(
@Value("\${cloud.aws.cloud-front.host}") @Value("\${cloud.aws.cloud-front.host}")
private val coverImageHost: String private val coverImageHost: String
) { ) {
fun getOriginalAudioDramaTotalCount(memberId: Long, isAdult: Boolean): Int {
return repository.getOriginalAudioDramaTotalCount(memberId, isAdult)
}
fun getOriginalAudioDramaList( fun getOriginalAudioDramaList(
memberId: Long,
isAdult: Boolean, isAdult: Boolean,
offset: Long = 0, offset: Long = 0,
limit: Long = 20 limit: Long = 20
): List<GetSeriesListResponse.SeriesListItem> { ): List<GetSeriesListResponse.SeriesListItem> {
val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, offset, limit) val originalAudioDramaList = repository.getOriginalAudioDramaList(memberId, isAdult, offset, limit)
return seriesToSeriesListItem(originalAudioDramaList, isAdult) return seriesToSeriesListItem(originalAudioDramaList, isAdult)
} }
@ -219,6 +224,9 @@ class ContentSeriesService(
it it
} }
.filter {
it.numberOfContent > 0
}
.map { .map {
val nowDateTime = LocalDateTime.now() val nowDateTime = LocalDateTime.now()

View File

@ -7,14 +7,9 @@ data class GetNoticeResponse(
val noticeList: List<NoticeItem> val noticeList: List<NoticeItem>
) )
data class NoticeItem( data class NoticeItem @QueryProjection constructor(
val id: Long, val id: Long,
val title: String, val title: String,
val content: String, val content: String,
val date: String val date: String
) )
data class NoticeTitleItem @QueryProjection constructor(
val id: Long,
val title: String
)

View File

@ -5,8 +5,6 @@ import org.springframework.data.domain.Pageable
import org.springframework.data.repository.findByIdOrNull import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
@ -46,25 +44,11 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor
fun getNoticeList(pageable: Pageable, timezone: String): GetNoticeResponse { fun getNoticeList(pageable: Pageable, timezone: String): GetNoticeResponse {
val totalCount = repository.getNoticeTotalCount() val totalCount = repository.getNoticeTotalCount()
val noticeList = repository.getNoticeList(pageable) val noticeList = repository.getNoticeList(pageable)
.asSequence()
.map {
val createdAt = it.createdAt!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of(timezone))
NoticeItem(
it.id!!,
it.title,
it.content,
createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
)
}
.toList()
return GetNoticeResponse(totalCount, noticeList) return GetNoticeResponse(totalCount, noticeList)
} }
fun getLatestNotice(): NoticeTitleItem? { fun getLatestNotice(): NoticeItem? {
return repository.getLatestNotice() return repository.getLatestNotice()
} }
} }

View File

@ -1,18 +1,22 @@
package kr.co.vividnext.sodalive.notice package kr.co.vividnext.sodalive.notice
import com.querydsl.core.types.dsl.DateTimePath
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.core.types.dsl.StringTemplate
import com.querydsl.jpa.impl.JPAQueryFactory import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.notice.QServiceNotice.serviceNotice import kr.co.vividnext.sodalive.notice.QServiceNotice.serviceNotice
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@Repository @Repository
interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, ServiceNoticeQueryRepository interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, ServiceNoticeQueryRepository
interface ServiceNoticeQueryRepository { interface ServiceNoticeQueryRepository {
fun getNoticeTotalCount(): Int fun getNoticeTotalCount(): Int
fun getNoticeList(pageable: Pageable): List<ServiceNotice> fun getNoticeList(pageable: Pageable): List<NoticeItem>
fun getLatestNotice(): NoticeTitleItem? fun getLatestNotice(): NoticeItem?
} }
@Repository @Repository
@ -26,9 +30,17 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.size .size
} }
override fun getNoticeList(pageable: Pageable): List<ServiceNotice> { override fun getNoticeList(pageable: Pageable): List<NoticeItem> {
return queryFactory return queryFactory
.selectFrom(serviceNotice) .select(
QNoticeItem(
serviceNotice.id,
serviceNotice.title,
serviceNotice.content,
getFormattedDate(serviceNotice.createdAt)
)
)
.from(serviceNotice)
.where(serviceNotice.isActive.isTrue) .where(serviceNotice.isActive.isTrue)
.offset(pageable.offset) .offset(pageable.offset)
.limit(pageable.pageSize.toLong()) .limit(pageable.pageSize.toLong())
@ -36,12 +48,14 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.fetch() .fetch()
} }
override fun getLatestNotice(): NoticeTitleItem? { override fun getLatestNotice(): NoticeItem? {
return queryFactory return queryFactory
.select( .select(
QNoticeTitleItem( QNoticeItem(
serviceNotice.id, serviceNotice.id,
serviceNotice.title serviceNotice.title,
serviceNotice.content,
getFormattedDate(serviceNotice.createdAt)
) )
) )
.from(serviceNotice) .from(serviceNotice)
@ -49,4 +63,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.orderBy(serviceNotice.id.desc()) .orderBy(serviceNotice.id.desc())
.fetchFirst() .fetchFirst()
} }
private fun getFormattedDate(dateTimePath: DateTimePath<LocalDateTime>): StringTemplate {
return Expressions.stringTemplate(
"DATE_FORMAT({0}, {1})",
Expressions.dateTimeTemplate(
LocalDateTime::class.java,
"CONVERT_TZ({0},{1},{2})",
dateTimePath,
"UTC",
"Asia/Seoul"
),
"%Y-%m-%d"
)
}
} }

View File

@ -82,7 +82,8 @@ class RankingRepository(
audioContent.price, audioContent.price,
audioContent.duration, audioContent.duration,
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )
@ -208,6 +209,48 @@ class RankingRepository(
.fetch() .fetch()
} }
fun getCompleteSeriesRankingTotalCount(
memberId: Long,
isAdult: Boolean,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int {
val blockMemberCondition = blockMember.member.id.eq(member.id)
.and(blockMember.isActive.isTrue)
.and(blockMember.blockedMember.id.eq(memberId))
var where = series.isActive.isTrue
.and(series.state.eq(SeriesState.COMPLETE))
.and(audioContent.isActive.isTrue)
.and(member.isActive.isTrue)
.and(member.isNotNull)
.and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.duration.isNotNull)
.and(audioContent.limited.isNull)
.and(blockMember.id.isNull)
.and(order.isActive.isTrue)
.and(order.createdAt.goe(startDate))
.and(order.createdAt.lt(endDate))
if (!isAdult) {
where = where.and(series.isAdult.isFalse)
}
return queryFactory
.select(series.id)
.from(seriesContent)
.innerJoin(seriesContent.series, series)
.innerJoin(seriesContent.content, audioContent)
.innerJoin(series.member, member)
.leftJoin(order).on(audioContent.id.eq(order.audioContent.id))
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(series.id)
.orderBy(order.can.sum().desc())
.fetch()
.size
}
fun getCompleteSeriesRanking( fun getCompleteSeriesRanking(
memberId: Long, memberId: Long,
isAdult: Boolean, isAdult: Boolean,
@ -247,10 +290,7 @@ class RankingRepository(
.leftJoin(blockMember).on(blockMemberCondition) .leftJoin(blockMember).on(blockMemberCondition)
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
.orderBy( .orderBy(order.can.sum().desc())
order.can.sum().desc(),
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
)
.offset(offset) .offset(offset)
.limit(limit) .limit(limit)
.fetch() .fetch()
@ -288,7 +328,7 @@ class RankingRepository(
.where(where) .where(where)
.groupBy(series.id) .groupBy(series.id)
.orderBy( .orderBy(
order.can.sum().desc(), order.id.countDistinct().desc(),
Expressions.numberTemplate(Double::class.java, "function('rand')").asc() Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
) )
.offset(0) .offset(0)
@ -341,12 +381,17 @@ class RankingRepository(
.fetch() .fetch()
} }
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var where = member.isActive.isTrue var where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR)) .and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue) .and(audioContent.isActive.isTrue)
.and(audioContent.duration.isNotNull) .and(audioContent.duration.isNotNull)
.and(audioContent.limited.isNull) .and(audioContent.limited.isNull)
.and(audioContentTheme.isActive.isTrue)
.and(order.isActive.isTrue) .and(order.isActive.isTrue)
.and(member.id.eq(creatorId)) .and(member.id.eq(creatorId))
@ -354,6 +399,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse) where = where.and(series.isAdult.isFalse)
} }
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory return queryFactory
.select( .select(
QGetAudioContentRankingItem( QGetAudioContentRankingItem(
@ -364,11 +413,13 @@ class RankingRepository(
audioContent.price, audioContent.price,
audioContent.duration, audioContent.duration,
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )
.from(order) .from(order)
.innerJoin(order.audioContent, audioContent) .innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.theme, audioContentTheme)
.innerJoin(audioContent.member, member) .innerJoin(audioContent.member, member)
.where(where) .where(where)
.groupBy(audioContent.id) .groupBy(audioContent.id)
@ -378,12 +429,17 @@ class RankingRepository(
.fetch() .fetch()
} }
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesCountTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var where = member.isActive.isTrue var where = member.isActive.isTrue
.and(member.role.eq(MemberRole.CREATOR)) .and(member.role.eq(MemberRole.CREATOR))
.and(audioContent.isActive.isTrue) .and(audioContent.isActive.isTrue)
.and(audioContent.duration.isNotNull) .and(audioContent.duration.isNotNull)
.and(audioContent.limited.isNull) .and(audioContent.limited.isNull)
.and(audioContentTheme.isActive.isTrue)
.and(order.isActive.isTrue) .and(order.isActive.isTrue)
.and(member.id.eq(creatorId)) .and(member.id.eq(creatorId))
@ -391,6 +447,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse) where = where.and(series.isAdult.isFalse)
} }
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory return queryFactory
.select( .select(
QGetAudioContentRankingItem( QGetAudioContentRankingItem(
@ -401,11 +461,13 @@ class RankingRepository(
audioContent.price, audioContent.price,
audioContent.duration, audioContent.duration,
member.id, member.id,
member.nickname member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
) )
) )
.from(order) .from(order)
.innerJoin(order.audioContent, audioContent) .innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.theme, audioContentTheme)
.innerJoin(audioContent.member, member) .innerJoin(audioContent.member, member)
.where(where) .where(where)
.groupBy(audioContent.id) .groupBy(audioContent.id)

View File

@ -82,6 +82,20 @@ class RankingService(
return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult)
} }
fun getCompleteSeriesRankingTotalCount(
memberId: Long,
isAdult: Boolean,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int {
return repository.getCompleteSeriesRankingTotalCount(
memberId = memberId,
isAdult = isAdult,
startDate = startDate,
endDate = endDate
)
}
fun getCompleteSeriesRanking( fun getCompleteSeriesRanking(
memberId: Long, memberId: Long,
isAdult: Boolean, isAdult: Boolean,
@ -175,12 +189,20 @@ class RankingService(
return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate)
} }
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesTop2(
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult) creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme)
} }
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> { fun fetchCreatorContentBySalesCountTop2(
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult, theme)
} }
fun fetchCreatorBySeriesRevenueRankTop20( fun fetchCreatorBySeriesRevenueRankTop20(