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.curation.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.order.QOrder.order
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
@ -72,6 +73,14 @@ interface AudioContentQueryRepository {
isFree: Boolean = false
): List<GetAudioContentMainItem>
fun findAlarmContentByTheme(
memberId: Long,
theme: List<String>,
isAdult: Boolean = false,
offset: Long = 0,
limit: Long = 20
): List<GetAudioContentMainItem>
fun totalCountByTheme(
memberId: Long,
theme: String = "",
@ -80,6 +89,7 @@ interface AudioContentQueryRepository {
): Int
fun findByThemeFor2Weeks(
isFree: Boolean = false,
cloudfrontHost: String,
memberId: Long,
theme: String = "",
@ -90,6 +100,7 @@ interface AudioContentQueryRepository {
): List<GetAudioContentMainItem>
fun totalCountNewContentFor2Weeks(
isFree: Boolean = false,
theme: String,
memberId: Long,
isAdult: Boolean,
@ -109,6 +120,14 @@ interface AudioContentQueryRepository {
contentType: ContentType
): List<GetAudioContentMainItem>
fun findAudioContentByCurationIdV2(
curationId: Long,
memberId: Long,
isAdult: Boolean,
offset: Long = 0,
limit: Long = 20
): List<GetAudioContentMainItem>
fun getAudioContentRanking(
cloudfrontHost: String,
isAdult: Boolean,
@ -413,6 +432,60 @@ class AudioContentQueryRepositoryImpl(
.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 {
var where = audioContent.isActive.isTrue
.and(audioContent.duration.isNotNull)
@ -447,6 +520,7 @@ class AudioContentQueryRepositoryImpl(
}
override fun totalCountNewContentFor2Weeks(
isFree: Boolean,
theme: String,
memberId: Long,
isAdult: Boolean,
@ -475,6 +549,10 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContentTheme.theme.eq(theme))
}
if (isFree) {
where = where.and(audioContent.price.loe(0))
}
return queryFactory
.select(audioContent.id)
.from(audioContent)
@ -486,6 +564,7 @@ class AudioContentQueryRepositoryImpl(
}
override fun findByThemeFor2Weeks(
isFree: Boolean,
cloudfrontHost: String,
memberId: Long,
theme: String,
@ -517,6 +596,10 @@ class AudioContentQueryRepositoryImpl(
where = where.and(audioContentTheme.theme.eq(theme))
}
if (isFree) {
where = where.and(audioContent.price.loe(0))
}
return queryFactory
.select(
QGetAudioContentMainItem(
@ -648,6 +731,54 @@ class AudioContentQueryRepositoryImpl(
.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(
cloudfrontHost: String,
isAdult: Boolean,
@ -679,7 +810,8 @@ class AudioContentQueryRepositoryImpl(
audioContent.price,
audioContent.duration,
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)

View File

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

View File

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

View File

@ -17,5 +17,6 @@ data class GetAudioContentRankingItem @QueryProjection constructor(
@JsonProperty("price") val price: Int,
@JsonProperty("duration") val duration: String,
@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(
tabId: Long,
title: String,
isAdult: Boolean,
offset: Long = 0,
limit: Long = 12
isAdult: Boolean
): List<AudioContentCuration> {
var where = audioContentCuration.isActive.isTrue
.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 imageUrl: String,
val creatorId: Long,
val creatorNickname: String
val creatorNickname: String,
val creatorProfileImageUrl: String
)

View File

@ -30,7 +30,8 @@ class RecommendSeriesRepository(
series.title,
recommendSeries.imagePath.prepend("/").prepend(imageHost),
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(recommendSeries)
@ -57,7 +58,8 @@ class RecommendSeriesRepository(
series.title,
recommendSeries.imagePath.prepend("/").prepend(imageHost),
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.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.SodaException
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
@ -19,4 +21,22 @@ class AudioContentMainTabAlarmController(private val service: AudioContentMainTa
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
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.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
@ -32,11 +32,10 @@ class AudioContentMainTabAlarmService(
)
val alarmThemeList = listOf("모닝콜", "슬립콜", "알람")
val newAlarmContentList = contentRepository.findByTheme(
val newAlarmContentList = contentRepository.findAlarmContentByTheme(
memberId = memberId,
theme = alarmThemeList[0],
theme = alarmThemeList,
isAdult = isAdult,
contentType = ContentType.ALL,
limit = 10
)
@ -64,10 +63,10 @@ class AudioContentMainTabAlarmService(
.map {
GetContentCurationResponse(
title = it.title,
items = contentRepository.findAudioContentByCurationId(
items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!,
isAdult = isAdult,
contentType = ContentType.ALL
memberId = memberId,
isAdult = isAdult
)
)
}
@ -81,4 +80,28 @@ class AudioContentMainTabAlarmService(
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.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
@ -19,4 +20,19 @@ class AudioContentMainTabAsmrController(private val service: AudioContentMainTab
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.main.banner.AudioContentBannerService
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.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service
class AudioContentMainTabAsmrService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository,
private val rankingService: RankingService,
@ -59,16 +62,42 @@ class AudioContentMainTabAsmrService(
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 curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.map {
GetContentCurationResponse(
title = it.title,
items = contentRepository.findAudioContentByCurationId(
items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!,
isAdult = isAdult,
contentType = ContentType.ALL
memberId = memberId,
isAdult = isAdult
)
)
}
@ -77,8 +106,31 @@ class AudioContentMainTabAsmrService(
contentBannerList = contentBannerList,
newAsmrContentList = newAsmrContentList,
rankAsmrContentList = rankAsmrContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList,
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
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabAsmrResponse(
val contentBannerList: List<GetAudioContentBannerResponse>,
val newAsmrContentList: List<GetAudioContentMainItem>,
val rankAsmrContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse,
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.SodaException
import kr.co.vividnext.sodalive.content.ContentType
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
@ -13,10 +16,70 @@ import org.springframework.web.bind.annotation.RestController
class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) {
@GetMapping
fun fetchContentMainTabContent(
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
@RequestParam("contentType", required = false) contentType: ContentType? = null,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
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.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.tab.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
@ -18,7 +21,11 @@ class AudioContentMainTabContentService(
private val rankingService: RankingService,
private val eventService: EventService
) {
fun fetchData(member: Member): GetContentMainTabContentResponse {
fun fetchData(
isAdultContentVisible: Boolean,
contentType: ContentType,
member: Member
): GetContentMainTabContentResponse {
val memberId = member.id!!
val isAdult = member.auth != null
@ -33,18 +40,14 @@ class AudioContentMainTabContentService(
val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult)
// 새로운 단편
val newContentList = if (themeOfContentList.isNotEmpty()) {
audioContentRepository.findByTheme(
val newContentList = audioContentRepository.findByTheme(
memberId = member.id!!,
theme = themeOfContentList[0],
theme = "",
isAdult = member.auth != null,
contentType = ContentType.ALL,
offset = 0,
limit = 10
)
} else {
emptyList()
}
// 일간 랭킹
val currentDateTime = LocalDateTime.now()
@ -102,4 +105,59 @@ class AudioContentMainTabContentService(
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.SodaException
import kr.co.vividnext.sodalive.content.ContentType
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
@ -19,4 +22,42 @@ class AudioContentMainTabFreeController(private val service: AudioContentMainTab
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.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.curation.AudioContentCurationQueryRepository
import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse
@ -38,10 +39,10 @@ class AudioContentMainTabFreeService(
.map {
GetContentCurationResponse(
title = it.title,
items = contentRepository.findAudioContentByCurationId(
items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!,
isAdult = isAdult,
contentType = ContentType.ALL
memberId = memberId,
isAdult = isAdult
)
)
}
@ -64,13 +65,14 @@ class AudioContentMainTabFreeService(
}
val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.filter { it.title != "크리에이터 소개" }
.map {
GetContentCurationResponse(
title = it.title,
items = contentRepository.findAudioContentByCurationId(
items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!,
isAdult = isAdult,
contentType = ContentType.ALL
memberId = memberId,
isAdult = isAdult
)
)
}
@ -88,4 +90,46 @@ class AudioContentMainTabFreeService(
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.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
@ -19,4 +20,19 @@ class AudioContentMainTabHomeController(private val service: AudioContentMainTab
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
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.member.Member
import kr.co.vividnext.sodalive.notice.ServiceNoticeService
@ -114,4 +115,21 @@ class AudioContentMainTabHomeService(
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.event.GetEventResponse
import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse
import kr.co.vividnext.sodalive.notice.NoticeTitleItem
import kr.co.vividnext.sodalive.notice.NoticeItem
data class GetContentMainTabHomeResponse(
val tabId: Long = 1,
val latestNotice: NoticeTitleItem?,
val latestNotice: NoticeItem?,
val bannerList: List<GetAudioContentBannerResponse>,
val rankCreatorList: GetExplorerSectionResponse,
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.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
@ -19,4 +20,19 @@ class AudioContentMainTabLiveReplayController(private val service: AudioContentM
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.main.banner.AudioContentBannerService
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.GetPopularContentByCreatorResponse
import kr.co.vividnext.sodalive.event.EventService
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.rank.RankingService
@ -15,6 +17,7 @@ import java.time.temporal.TemporalAdjusters
@Service
class AudioContentMainTabLiveReplayService(
private val repository: AudioContentMainTabRepository,
private val bannerService: AudioContentBannerService,
private val contentRepository: AudioContentRepository,
private val rankingService: RankingService,
@ -59,16 +62,42 @@ class AudioContentMainTabLiveReplayService(
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 curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult)
.map {
GetContentCurationResponse(
title = it.title,
items = contentRepository.findAudioContentByCurationId(
items = contentRepository.findAudioContentByCurationIdV2(
curationId = it.id!!,
isAdult = isAdult,
contentType = ContentType.ALL
memberId = memberId,
isAdult = isAdult
)
)
}
@ -77,8 +106,31 @@ class AudioContentMainTabLiveReplayService(
contentBannerList = contentBannerList,
newLiveReplayContentList = newLiveReplayContentList,
rankLiveReplayContentList = rankLiveReplayContentList,
creatorList = creatorList,
salesRankContentList = salesRankContentList,
salesCountRankContentList = salesCountRankContentList,
eventBannerList = eventBannerList,
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
import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem
import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse
@ -11,6 +12,9 @@ data class GetContentMainTabLiveReplayResponse(
val contentBannerList: List<GetAudioContentBannerResponse>,
val newLiveReplayContentList: List<GetAudioContentMainItem>,
val rankLiveReplayContentList: List<GetAudioContentRankingItem>,
val creatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetAudioContentRankingItem>,
val salesCountRankContentList: List<GetAudioContentRankingItem>,
val eventBannerList: GetEventResponse,
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.SodaException
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
@ -19,4 +21,69 @@ class AudioContentMainTabSeriesController(private val service: AudioContentMainT
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.tab.RecommendSeriesRepository
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.member.Member
import kr.co.vividnext.sodalive.rank.RankingService
@ -23,15 +24,17 @@ class AudioContentMainTabSeriesService(
) {
fun fetchData(member: Member): GetContentMainTabSeriesResponse {
val isAdult = member.auth != null
val memberId = member.id!!
// 메인 배너 (시리즈)
val contentBannerList = bannerService.getBannerList(
tabId = 2,
memberId = member.id!!,
memberId = memberId,
isAdult = isAdult
)
val originalAudioDrama = seriesService.getOriginalAudioDramaList(
memberId = memberId,
isAdult = isAdult,
offset = 0,
limit = 20
@ -48,7 +51,7 @@ class AudioContentMainTabSeriesService(
.plusDays(1)
val rankSeriesList = rankingService.getSeriesRanking(
memberId = member.id!!,
memberId = memberId,
isAdult = isAdult,
startDate = dailyRankingStartDate,
endDate = dailyRankingEndDate
@ -60,7 +63,7 @@ class AudioContentMainTabSeriesService(
// 장르별 추천 시리즈
val recommendSeriesList = if (genreList.isNotEmpty()) {
rankingService.getSeriesAllRankingByGenre(
memberId = member.id!!,
memberId = memberId,
isAdult = isAdult,
genreId = genreList[0].id
)
@ -82,7 +85,7 @@ class AudioContentMainTabSeriesService(
.plusMonths(1)
val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking(
memberId = member.id!!,
memberId = memberId,
isAdult = isAdult,
startDate = monthlyRankingStartDate,
endDate = monthlyRankingEndDate
@ -98,7 +101,7 @@ class AudioContentMainTabSeriesService(
.plusDays(6)
val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20(
memberId = member.id!!,
memberId = memberId,
startDate = startDate.minusDays(1),
endDate = endDate
)
@ -122,7 +125,7 @@ class AudioContentMainTabSeriesService(
title = it.title,
items = seriesService.fetchSeriesByCurationId(
curationId = it.id!!,
memberId = member.id!!,
memberId = memberId,
isAdult = isAdult
)
)
@ -141,9 +144,79 @@ class AudioContentMainTabSeriesService(
newSeriesList = newSeriesList,
rankCompleteSeriesList = rankCompleteSeriesList,
seriesRankCreatorList = seriesRankCreatorList,
salesRankContentList = salesRankContentList,
recommendSeriesByChannel = salesRankContentList,
eventBannerList = eventBannerList,
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 rankCompleteSeriesList: List<GetSeriesListResponse.SeriesListItem>,
val seriesRankCreatorList: List<ContentCreatorResponse>,
val salesRankContentList: List<GetSeriesListResponse.SeriesListItem>,
val recommendSeriesByChannel: List<GetSeriesListResponse.SeriesListItem>,
val eventBannerList: GetEventResponse,
val curationList: List<GetSeriesCurationResponse>
)

View File

@ -35,7 +35,8 @@ interface ContentSeriesQueryRepository {
fun getKeywordList(seriesId: Long): List<String>
fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse
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 findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List<Series>
}
@ -143,9 +144,14 @@ class ContentSeriesQueryRepositoryImpl(
.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
.and(series.isActive.isTrue)
.and(blockMember.id.isNull)
if (!isAdult) {
where = where.and(series.isAdult.isFalse)
@ -153,13 +159,38 @@ class ContentSeriesQueryRepositoryImpl(
return queryFactory
.selectFrom(series)
.innerJoin(series.member, member)
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
.orderBy(series.id.desc())
.offset(offset)
.limit(limit)
.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> {
var where = seriesGenre.isActive.isTrue

View File

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

View File

@ -7,14 +7,9 @@ data class GetNoticeResponse(
val noticeList: List<NoticeItem>
)
data class NoticeItem(
data class NoticeItem @QueryProjection constructor(
val id: Long,
val title: String,
val content: 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.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Service
@Transactional(readOnly = true)
@ -46,25 +44,11 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor
fun getNoticeList(pageable: Pageable, timezone: String): GetNoticeResponse {
val totalCount = repository.getNoticeTotalCount()
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)
}
fun getLatestNotice(): NoticeTitleItem? {
fun getLatestNotice(): NoticeItem? {
return repository.getLatestNotice()
}
}

View File

@ -1,18 +1,22 @@
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 kr.co.vividnext.sodalive.notice.QServiceNotice.serviceNotice
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
@Repository
interface ServiceServiceNoticeRepository : JpaRepository<ServiceNotice, Long>, ServiceNoticeQueryRepository
interface ServiceNoticeQueryRepository {
fun getNoticeTotalCount(): Int
fun getNoticeList(pageable: Pageable): List<ServiceNotice>
fun getLatestNotice(): NoticeTitleItem?
fun getNoticeList(pageable: Pageable): List<NoticeItem>
fun getLatestNotice(): NoticeItem?
}
@Repository
@ -26,9 +30,17 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.size
}
override fun getNoticeList(pageable: Pageable): List<ServiceNotice> {
override fun getNoticeList(pageable: Pageable): List<NoticeItem> {
return queryFactory
.selectFrom(serviceNotice)
.select(
QNoticeItem(
serviceNotice.id,
serviceNotice.title,
serviceNotice.content,
getFormattedDate(serviceNotice.createdAt)
)
)
.from(serviceNotice)
.where(serviceNotice.isActive.isTrue)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
@ -36,12 +48,14 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.fetch()
}
override fun getLatestNotice(): NoticeTitleItem? {
override fun getLatestNotice(): NoticeItem? {
return queryFactory
.select(
QNoticeTitleItem(
QNoticeItem(
serviceNotice.id,
serviceNotice.title
serviceNotice.title,
serviceNotice.content,
getFormattedDate(serviceNotice.createdAt)
)
)
.from(serviceNotice)
@ -49,4 +63,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory
.orderBy(serviceNotice.id.desc())
.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.duration,
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
@ -208,6 +209,48 @@ class RankingRepository(
.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(
memberId: Long,
isAdult: Boolean,
@ -247,10 +290,7 @@ class RankingRepository(
.leftJoin(blockMember).on(blockMemberCondition)
.where(where)
.groupBy(series.id)
.orderBy(
order.can.sum().desc(),
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
)
.orderBy(order.can.sum().desc())
.offset(offset)
.limit(limit)
.fetch()
@ -288,7 +328,7 @@ class RankingRepository(
.where(where)
.groupBy(series.id)
.orderBy(
order.can.sum().desc(),
order.id.countDistinct().desc(),
Expressions.numberTemplate(Double::class.java, "function('rand')").asc()
)
.offset(0)
@ -341,12 +381,17 @@ class RankingRepository(
.fetch()
}
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
fun fetchCreatorContentBySalesTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var 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(order.isActive.isTrue)
.and(member.id.eq(creatorId))
@ -354,6 +399,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse)
}
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory
.select(
QGetAudioContentRankingItem(
@ -364,11 +413,13 @@ class RankingRepository(
audioContent.price,
audioContent.duration,
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.theme, audioContentTheme)
.innerJoin(audioContent.member, member)
.where(where)
.groupBy(audioContent.id)
@ -378,12 +429,17 @@ class RankingRepository(
.fetch()
}
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
fun fetchCreatorContentBySalesCountTop2(
creatorId: Long,
isAdult: Boolean,
theme: String
): List<GetAudioContentRankingItem> {
var 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(order.isActive.isTrue)
.and(member.id.eq(creatorId))
@ -391,6 +447,10 @@ class RankingRepository(
where = where.and(series.isAdult.isFalse)
}
if (theme.isNotBlank()) {
where = where.and(audioContentTheme.theme.eq(theme))
}
return queryFactory
.select(
QGetAudioContentRankingItem(
@ -401,11 +461,13 @@ class RankingRepository(
audioContent.price,
audioContent.duration,
member.id,
member.nickname
member.nickname,
member.profileImage.prepend("/").prepend(imageHost)
)
)
.from(order)
.innerJoin(order.audioContent, audioContent)
.innerJoin(audioContent.theme, audioContentTheme)
.innerJoin(audioContent.member, member)
.where(where)
.groupBy(audioContent.id)

View File

@ -82,6 +82,20 @@ class RankingService(
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(
memberId: Long,
isAdult: Boolean,
@ -175,12 +189,20 @@ class RankingService(
return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate)
}
fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult)
fun fetchCreatorContentBySalesTop2(
creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult, theme)
}
fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult)
fun fetchCreatorContentBySalesCountTop2(
creatorId: Long,
isAdult: Boolean,
theme: String = ""
): List<GetAudioContentRankingItem> {
return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult, theme)
}
fun fetchCreatorBySeriesRevenueRankTop20(