Test #457
+5
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.all.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.content.all.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.all.application.MainContentAllFacade
|
import kr.co.vividnext.sodalive.v2.api.content.all.application.MainContentAllFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -12,7 +13,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/audio/contents")
|
@RequestMapping("/api/v2/audio/contents")
|
||||||
class MainContentAllController(
|
class MainContentAllController(
|
||||||
private val facade: MainContentAllFacade
|
private val facade: MainContentAllFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getContents(
|
fun getContents(
|
||||||
@@ -30,7 +32,8 @@ class MainContentAllController(
|
|||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
member = member
|
member = member,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.all.application
|
package kr.co.vividnext.sodalive.v2.api.content.all.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponse
|
import kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryService
|
import kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryService
|
||||||
@@ -15,17 +16,18 @@ class MainContentAllFacade(
|
|||||||
dayOfWeek: String?,
|
dayOfWeek: String?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
member: Member?
|
member: Member?,
|
||||||
|
lang: Lang
|
||||||
): MainContentAllTabResponse {
|
): MainContentAllTabResponse {
|
||||||
return MainContentAllTabResponse.from(
|
val contents = queryService.getContents(
|
||||||
queryService.getContents(
|
|
||||||
type = type,
|
type = type,
|
||||||
sort = sort,
|
sort = sort,
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
member = member
|
member = member,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return MainContentAllTabResponse.from(contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.content.overview.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewFacade
|
import kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/contents")
|
@RequestMapping("/api/v2/contents")
|
||||||
class ContentOverviewController(
|
class ContentOverviewController(
|
||||||
private val facade: ContentOverviewFacade
|
private val facade: ContentOverviewFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getContents(
|
fun getContents(
|
||||||
@@ -22,7 +24,7 @@ class ContentOverviewController(
|
|||||||
@RequestParam(required = false) size: Int?,
|
@RequestParam(required = false) size: Int?,
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(facade.getContents(type, page, size, requireMember(member)))
|
ApiResponse.ok(facade.getContents(type, page, size, requireMember(member), langContext.lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireMember(member: Member?): Member {
|
private fun requireMember(member: Member?): Member {
|
||||||
|
|||||||
+14
-9
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.overview.application
|
package kr.co.vividnext.sodalive.v2.api.content.overview.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewItemResponse
|
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewItemResponse
|
||||||
@@ -21,42 +22,46 @@ class ContentOverviewFacade(
|
|||||||
private val cloudFrontHost: String,
|
private val cloudFrontHost: String,
|
||||||
private val queryPolicy: ContentOverviewQueryPolicy = ContentOverviewQueryPolicy()
|
private val queryPolicy: ContentOverviewQueryPolicy = ContentOverviewQueryPolicy()
|
||||||
) {
|
) {
|
||||||
fun getContents(type: String?, page: Int?, size: Int?, member: Member): ContentOverviewPageResponse {
|
fun getContents(type: String?, page: Int?, size: Int?, member: Member, lang: Lang): ContentOverviewPageResponse {
|
||||||
val resolvedType = queryPolicy.resolveType(type)
|
val resolvedType = queryPolicy.resolveType(type)
|
||||||
val resolvedPage = queryPolicy.createPage(page, size)
|
val resolvedPage = queryPolicy.createPage(page, size)
|
||||||
|
|
||||||
return when (resolvedType) {
|
return when (resolvedType) {
|
||||||
ContentOverviewType.NEW_AND_HOT_AUDIO -> getNewAndHotContents(member, resolvedPage)
|
ContentOverviewType.NEW_AND_HOT_AUDIO -> getNewAndHotContents(member, resolvedPage, lang)
|
||||||
ContentOverviewType.FIRST_AUDIO_CONTENT -> getFirstAudioContents(member, resolvedPage)
|
ContentOverviewType.FIRST_AUDIO_CONTENT -> getFirstAudioContents(member, resolvedPage, lang)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNewAndHotContents(member: Member, page: ContentOverviewPage): ContentOverviewPageResponse {
|
private fun getNewAndHotContents(member: Member, page: ContentOverviewPage, lang: Lang): ContentOverviewPageResponse {
|
||||||
val fetched = audioRecommendationQueryService.findNewAndHotAudios(
|
val fetched = audioRecommendationQueryService.findNewAndHotAudios(
|
||||||
member = member,
|
member = member,
|
||||||
offset = page.offset,
|
offset = page.offset,
|
||||||
limit = page.size + 1
|
limit = page.size + 1,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
val items = queryPolicy.pageItems(fetched, page)
|
||||||
return ContentOverviewPageResponse(
|
return ContentOverviewPageResponse(
|
||||||
type = ContentOverviewType.NEW_AND_HOT_AUDIO,
|
type = ContentOverviewType.NEW_AND_HOT_AUDIO,
|
||||||
items = queryPolicy.pageItems(fetched, page).map { ContentOverviewItemResponse.fromNewAndHot(it) },
|
items = items.map(ContentOverviewItemResponse::fromNewAndHot),
|
||||||
page = page.page,
|
page = page.page,
|
||||||
size = page.size,
|
size = page.size,
|
||||||
hasNext = queryPolicy.hasNext(fetched, page)
|
hasNext = queryPolicy.hasNext(fetched, page)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFirstAudioContents(member: Member, page: ContentOverviewPage): ContentOverviewPageResponse {
|
private fun getFirstAudioContents(member: Member, page: ContentOverviewPage, lang: Lang): ContentOverviewPageResponse {
|
||||||
val fetched = homeRecommendationQueryService.findFirstAudioContents(
|
val fetched = homeRecommendationQueryService.findFirstAudioContents(
|
||||||
now = LocalDateTime.now(),
|
now = LocalDateTime.now(),
|
||||||
offset = page.offset,
|
offset = page.offset,
|
||||||
limit = page.size + 1,
|
limit = page.size + 1,
|
||||||
memberId = member.id,
|
memberId = member.id,
|
||||||
includeAdultContents = memberContentPreferenceService.canViewAdultContent(member)
|
includeAdultContents = memberContentPreferenceService.canViewAdultContent(member),
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
val items = queryPolicy.pageItems(fetched, page)
|
||||||
return ContentOverviewPageResponse(
|
return ContentOverviewPageResponse(
|
||||||
type = ContentOverviewType.FIRST_AUDIO_CONTENT,
|
type = ContentOverviewType.FIRST_AUDIO_CONTENT,
|
||||||
items = queryPolicy.pageItems(fetched, page).map {
|
items = items.map {
|
||||||
ContentOverviewItemResponse.fromFirstAudioContent(
|
ContentOverviewItemResponse.fromFirstAudioContent(
|
||||||
audio = it,
|
audio = it,
|
||||||
coverImage = it.coverImage.toCdnUrl(cloudFrontHost),
|
coverImage = it.coverImage.toCdnUrl(cloudFrontHost),
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.ranking.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.content.ranking.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.ranking.application.AudioRankingFacade
|
import kr.co.vividnext.sodalive.v2.api.content.ranking.application.AudioRankingFacade
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
@@ -13,13 +14,14 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/audio/rankings")
|
@RequestMapping("/api/v2/audio/rankings")
|
||||||
class AudioRankingController(
|
class AudioRankingController(
|
||||||
private val facade: AudioRankingFacade
|
private val facade: AudioRankingFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getRankings(
|
fun getRankings(
|
||||||
@RequestParam(defaultValue = "WEEKLY_POPULAR") type: AudioRankingType,
|
@RequestParam(defaultValue = "WEEKLY_POPULAR") type: AudioRankingType,
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(facade.getRankings(type, member))
|
ApiResponse.ok(facade.getRankings(type, member, langContext.lang))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.ranking.application
|
package kr.co.vividnext.sodalive.v2.api.content.ranking.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.ranking.dto.AudioRankingResponse
|
import kr.co.vividnext.sodalive.v2.api.content.ranking.dto.AudioRankingResponse
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryService
|
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryService
|
||||||
@@ -10,7 +11,8 @@ import org.springframework.stereotype.Component
|
|||||||
class AudioRankingFacade(
|
class AudioRankingFacade(
|
||||||
private val queryService: AudioRankingQueryService
|
private val queryService: AudioRankingQueryService
|
||||||
) {
|
) {
|
||||||
fun getRankings(type: AudioRankingType, member: Member?): AudioRankingResponse {
|
fun getRankings(type: AudioRankingType, member: Member?, lang: Lang): AudioRankingResponse {
|
||||||
return AudioRankingResponse.from(queryService.getRankings(type, member))
|
val result = queryService.getRankings(type, member, lang)
|
||||||
|
return AudioRankingResponse.from(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacade
|
import kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/audio/recommendations")
|
@RequestMapping("/api/v2/audio/recommendations")
|
||||||
class AudioRecommendationController(
|
class AudioRecommendationController(
|
||||||
private val audioRecommendationFacade: AudioRecommendationFacade
|
private val audioRecommendationFacade: AudioRecommendationFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getRecommendations(
|
fun getRecommendations(
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(audioRecommendationFacade.getRecommendations(member))
|
ApiResponse.ok(audioRecommendationFacade.getRecommendations(member, langContext.lang))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.recommendation.application
|
package kr.co.vividnext.sodalive.v2.api.content.recommendation.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.recommendation.dto.AudioRecommendationsResponse
|
import kr.co.vividnext.sodalive.v2.api.content.recommendation.dto.AudioRecommendationsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
||||||
@@ -9,7 +10,8 @@ import org.springframework.stereotype.Component
|
|||||||
class AudioRecommendationFacade(
|
class AudioRecommendationFacade(
|
||||||
private val queryService: AudioRecommendationQueryService
|
private val queryService: AudioRecommendationQueryService
|
||||||
) {
|
) {
|
||||||
fun getRecommendations(member: Member?): AudioRecommendationsResponse {
|
fun getRecommendations(member: Member?, lang: Lang): AudioRecommendationsResponse {
|
||||||
return AudioRecommendationsResponse.from(queryService.getRecommendations(member))
|
val result = queryService.getRecommendations(member, lang)
|
||||||
|
return AudioRecommendationsResponse.from(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application.CreatorChannelAudioFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application.CreatorChannelAudioFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelAudioController(
|
class CreatorChannelAudioController(
|
||||||
private val creatorChannelAudioFacade: CreatorChannelAudioFacade
|
private val creatorChannelAudioFacade: CreatorChannelAudioFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/audio")
|
@GetMapping("/{creatorId}/audio")
|
||||||
fun getAudioTab(
|
fun getAudioTab(
|
||||||
@@ -32,7 +34,8 @@ class CreatorChannelAudioController(
|
|||||||
sort = sort,
|
sort = sort,
|
||||||
themeId = themeId,
|
themeId = themeId,
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelAudioTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelAudioTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.application.CreatorChannelAudioQueryService
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.application.CreatorChannelAudioQueryService
|
||||||
@@ -19,7 +20,8 @@ class CreatorChannelAudioFacade(
|
|||||||
themeId: Long?,
|
themeId: Long?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelAudioTabResponse {
|
): CreatorChannelAudioTabResponse {
|
||||||
return CreatorChannelAudioTabResponse.from(
|
return CreatorChannelAudioTabResponse.from(
|
||||||
creatorChannelAudioQueryService.getAudioTab(
|
creatorChannelAudioQueryService.getAudioTab(
|
||||||
@@ -29,7 +31,8 @@ class CreatorChannelAudioFacade(
|
|||||||
themeId = themeId,
|
themeId = themeId,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.`in`.w
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.application.CreatorChannelCommunityFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.application.CreatorChannelCommunityFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelCommunityController(
|
class CreatorChannelCommunityController(
|
||||||
private val creatorChannelCommunityFacade: CreatorChannelCommunityFacade
|
private val creatorChannelCommunityFacade: CreatorChannelCommunityFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/community")
|
@GetMapping("/{creatorId}/community")
|
||||||
fun getCommunityTab(
|
fun getCommunityTab(
|
||||||
@@ -28,7 +30,8 @@ class CreatorChannelCommunityController(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -41,7 +44,8 @@ class CreatorChannelCommunityController(
|
|||||||
ApiResponse.ok(
|
ApiResponse.ok(
|
||||||
creatorChannelCommunityFacade.getCommunityPostDetail(
|
creatorChannelCommunityFacade.getCommunityPostDetail(
|
||||||
postId = postId,
|
postId = postId,
|
||||||
viewer = requireMember(member)
|
viewer = requireMember(member),
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -58,7 +62,8 @@ class CreatorChannelCommunityController(
|
|||||||
postId = postId,
|
postId = postId,
|
||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -75,7 +80,8 @@ class CreatorChannelCommunityController(
|
|||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-20
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityCommentsResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityCommentsResponse
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityPostDetailResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityPostDetailResponse
|
||||||
@@ -20,31 +21,33 @@ class CreatorChannelCommunityFacade(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityTabResponse {
|
): CreatorChannelCommunityTabResponse {
|
||||||
return CreatorChannelCommunityTabResponse.from(
|
val tab = creatorChannelCommunityQueryService.getCommunityTab(
|
||||||
creatorChannelCommunityQueryService.getCommunityTab(
|
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return CreatorChannelCommunityTabResponse.from(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCommunityPostDetail(
|
fun getCommunityPostDetail(
|
||||||
postId: Long,
|
postId: Long,
|
||||||
viewer: Member,
|
viewer: Member,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityPostDetailResponse {
|
): CreatorChannelCommunityPostDetailResponse {
|
||||||
return CreatorChannelCommunityPostDetailResponse.from(
|
val detail = creatorChannelCommunityQueryService.getCommunityPostDetail(
|
||||||
creatorChannelCommunityQueryService.getCommunityPostDetail(
|
|
||||||
postId = postId,
|
postId = postId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
now = now
|
now = now,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return CreatorChannelCommunityPostDetailResponse.from(detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCommunityComments(
|
fun getCommunityComments(
|
||||||
@@ -52,17 +55,18 @@ class CreatorChannelCommunityFacade(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityCommentsResponse {
|
): CreatorChannelCommunityCommentsResponse {
|
||||||
return CreatorChannelCommunityCommentsResponse.from(
|
val comments = creatorChannelCommunityQueryService.getCommunityComments(
|
||||||
creatorChannelCommunityQueryService.getCommunityComments(
|
|
||||||
postId = postId,
|
postId = postId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return CreatorChannelCommunityCommentsResponse.from(comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCommunityReplies(
|
fun getCommunityReplies(
|
||||||
@@ -70,16 +74,17 @@ class CreatorChannelCommunityFacade(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityRepliesResponse {
|
): CreatorChannelCommunityRepliesResponse {
|
||||||
return CreatorChannelCommunityRepliesResponse.from(
|
val replies = creatorChannelCommunityQueryService.getCommunityReplies(
|
||||||
creatorChannelCommunityQueryService.getCommunityReplies(
|
|
||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return CreatorChannelCommunityRepliesResponse.from(replies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.adapter.`in`.we
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application.CreatorChannelDonationFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application.CreatorChannelDonationFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelDonationController(
|
class CreatorChannelDonationController(
|
||||||
private val creatorChannelDonationFacade: CreatorChannelDonationFacade
|
private val creatorChannelDonationFacade: CreatorChannelDonationFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/donations")
|
@GetMapping("/{creatorId}/donations")
|
||||||
fun getDonationTab(
|
fun getDonationTab(
|
||||||
@@ -28,7 +30,8 @@ class CreatorChannelDonationController(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-5
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.dto.CreatorChannelDonationTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.dto.CreatorChannelDonationTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryService
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryService
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
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.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -10,22 +12,29 @@ import java.time.LocalDateTime
|
|||||||
@Service
|
@Service
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
class CreatorChannelDonationFacade(
|
class CreatorChannelDonationFacade(
|
||||||
private val creatorChannelDonationQueryService: CreatorChannelDonationQueryService
|
private val creatorChannelDonationQueryService: CreatorChannelDonationQueryService,
|
||||||
|
private val creatorLocalizedNameService: CreatorLocalizedNameService
|
||||||
) {
|
) {
|
||||||
fun getDonationTab(
|
fun getDonationTab(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelDonationTabResponse {
|
): CreatorChannelDonationTabResponse {
|
||||||
return CreatorChannelDonationTabResponse.from(
|
val tab = creatorChannelDonationQueryService.getDonationTab(
|
||||||
creatorChannelDonationQueryService.getDonationTab(
|
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = lang
|
||||||
|
)
|
||||||
|
val names = creatorLocalizedNameService.findCurrentNames(tab.rankings.map { it.userId }.toSet(), lang)
|
||||||
|
return CreatorChannelDonationTabResponse.from(
|
||||||
|
tab.copy(
|
||||||
|
rankings = tab.rankings.map { it.copy(nickname = names[it.userId] ?: it.nickname) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.application.CreatorChannelFanTalkFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.application.CreatorChannelFanTalkFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelFanTalkController(
|
class CreatorChannelFanTalkController(
|
||||||
private val creatorChannelFanTalkFacade: CreatorChannelFanTalkFacade
|
private val creatorChannelFanTalkFacade: CreatorChannelFanTalkFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/fan-talks")
|
@GetMapping("/{creatorId}/fan-talks")
|
||||||
fun getFanTalkTab(
|
fun getFanTalkTab(
|
||||||
@@ -28,7 +30,8 @@ class CreatorChannelFanTalkController(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-5
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.dto.CreatorChannelFanTalkTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.dto.CreatorChannelFanTalkTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.application.CreatorChannelFanTalkQueryService
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.application.CreatorChannelFanTalkQueryService
|
||||||
@@ -17,16 +18,17 @@ class CreatorChannelFanTalkFacade(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelFanTalkTabResponse {
|
): CreatorChannelFanTalkTabResponse {
|
||||||
return CreatorChannelFanTalkTabResponse.from(
|
val tab = creatorChannelFanTalkQueryService.getFanTalkTab(
|
||||||
creatorChannelFanTalkQueryService.getFanTalkTab(
|
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
)
|
lang = lang
|
||||||
)
|
)
|
||||||
|
return CreatorChannelFanTalkTabResponse.from(tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelHomeController(
|
class CreatorChannelHomeController(
|
||||||
private val creatorChannelHomeFacade: CreatorChannelHomeFacade
|
private val creatorChannelHomeFacade: CreatorChannelHomeFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/home")
|
@GetMapping("/{creatorId}/home")
|
||||||
fun getHome(
|
fun getHome(
|
||||||
@@ -23,7 +25,8 @@ class CreatorChannelHomeController(
|
|||||||
ApiResponse.ok(
|
ApiResponse.ok(
|
||||||
creatorChannelHomeFacade.getHome(
|
creatorChannelHomeFacade.getHome(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewer = requireMember(member)
|
viewer = requireMember(member),
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-8
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.home.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.home.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto.CreatorChannelHomeResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto.CreatorChannelHomeResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChannelHomeQueryService
|
import kr.co.vividnext.sodalive.v2.creator.channel.home.application.CreatorChannelHomeQueryService
|
||||||
@@ -16,14 +17,10 @@ class CreatorChannelHomeFacade(
|
|||||||
fun getHome(
|
fun getHome(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewer: Member,
|
viewer: Member,
|
||||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelHomeResponse {
|
): CreatorChannelHomeResponse {
|
||||||
return CreatorChannelHomeResponse.from(
|
val home = creatorChannelHomeQueryService.getHome(creatorId = creatorId, viewer = viewer, now = now, lang = lang)
|
||||||
creatorChannelHomeQueryService.getHome(
|
return CreatorChannelHomeResponse.from(home)
|
||||||
creatorId = creatorId,
|
|
||||||
viewer = viewer,
|
|
||||||
now = now
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.live.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.live.application.CreatorChannelLiveFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.live.application.CreatorChannelLiveFacade
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
@@ -15,7 +16,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelLiveController(
|
class CreatorChannelLiveController(
|
||||||
private val creatorChannelLiveFacade: CreatorChannelLiveFacade
|
private val creatorChannelLiveFacade: CreatorChannelLiveFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/live")
|
@GetMapping("/{creatorId}/live")
|
||||||
fun getLiveTab(
|
fun getLiveTab(
|
||||||
@@ -31,7 +33,8 @@ class CreatorChannelLiveController(
|
|||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
sort = sort,
|
sort = sort,
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.live.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.live.dto.CreatorChannelLiveTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.live.dto.CreatorChannelLiveTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
@@ -19,7 +20,8 @@ class CreatorChannelLiveFacade(
|
|||||||
sort: ContentSort,
|
sort: ContentSort,
|
||||||
page: Int,
|
page: Int,
|
||||||
size: Int,
|
size: Int,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelLiveTabResponse {
|
): CreatorChannelLiveTabResponse {
|
||||||
return CreatorChannelLiveTabResponse.from(
|
return CreatorChannelLiveTabResponse.from(
|
||||||
creatorChannelLiveQueryService.getLiveTab(
|
creatorChannelLiveQueryService.getLiveTab(
|
||||||
@@ -28,7 +30,8 @@ class CreatorChannelLiveFacade(
|
|||||||
sort = sort,
|
sort = sort,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.series.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.series.application.CreatorChannelSeriesFacade
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.series.application.CreatorChannelSeriesFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/creator-channels")
|
@RequestMapping("/api/v2/creator-channels")
|
||||||
class CreatorChannelSeriesController(
|
class CreatorChannelSeriesController(
|
||||||
private val creatorChannelSeriesFacade: CreatorChannelSeriesFacade
|
private val creatorChannelSeriesFacade: CreatorChannelSeriesFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{creatorId}/series")
|
@GetMapping("/{creatorId}/series")
|
||||||
fun getSeriesTab(
|
fun getSeriesTab(
|
||||||
@@ -30,7 +32,8 @@ class CreatorChannelSeriesController(
|
|||||||
viewer = requireMember(member),
|
viewer = requireMember(member),
|
||||||
sort = sort,
|
sort = sort,
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.series.application
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.series.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.series.dto.CreatorChannelSeriesTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.series.dto.CreatorChannelSeriesTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.application.CreatorChannelSeriesQueryService
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.application.CreatorChannelSeriesQueryService
|
||||||
@@ -18,7 +19,8 @@ class CreatorChannelSeriesFacade(
|
|||||||
sort: String?,
|
sort: String?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelSeriesTabResponse {
|
): CreatorChannelSeriesTabResponse {
|
||||||
return CreatorChannelSeriesTabResponse.from(
|
return CreatorChannelSeriesTabResponse.from(
|
||||||
creatorChannelSeriesQueryService.getSeriesTab(
|
creatorChannelSeriesQueryService.getSeriesTab(
|
||||||
@@ -27,7 +29,8 @@ class CreatorChannelSeriesFacade(
|
|||||||
sort = sort,
|
sort = sort,
|
||||||
page = page,
|
page = page,
|
||||||
size = size,
|
size = size,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.gift.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationRequest
|
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationRequest
|
||||||
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationResponse
|
import kr.co.vividnext.sodalive.v2.api.gift.dto.GiftApplicationResponse
|
||||||
@@ -32,7 +33,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/api/v2/gifts")
|
@RequestMapping("/api/v2/gifts")
|
||||||
class GiftController(
|
class GiftController(
|
||||||
private val queryService: GiftQueryService,
|
private val queryService: GiftQueryService,
|
||||||
private val commandService: GiftCommandService
|
private val commandService: GiftCommandService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/form-options")
|
@GetMapping("/form-options")
|
||||||
fun getFormOptions() = ApiResponse.ok(GiftFormOptionsResponse.from(queryService.getFormOptions()))
|
fun getFormOptions() = ApiResponse.ok(GiftFormOptionsResponse.from(queryService.getFormOptions()))
|
||||||
@@ -49,7 +51,8 @@ class GiftController(
|
|||||||
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||||
type = type,
|
type = type,
|
||||||
page = page,
|
page = page,
|
||||||
size = size
|
size = size,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -62,7 +65,8 @@ class GiftController(
|
|||||||
GiftDetailResponse.from(
|
GiftDetailResponse.from(
|
||||||
queryService.getGiftDetail(
|
queryService.getGiftDetail(
|
||||||
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||||
applicationNo = applicationNo
|
applicationNo = applicationNo,
|
||||||
|
lang = langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.home.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.application.HomeCreatorRankingFacade
|
import kr.co.vividnext.sodalive.v2.api.home.application.HomeCreatorRankingFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/home/rankings")
|
@RequestMapping("/api/v2/home/rankings")
|
||||||
class CreatorRankingController(
|
class CreatorRankingController(
|
||||||
private val homeCreatorRankingFacade: HomeCreatorRankingFacade
|
private val homeCreatorRankingFacade: HomeCreatorRankingFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping("/creators")
|
@GetMapping("/creators")
|
||||||
fun getCreatorRankings(
|
fun getCreatorRankings(
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(homeCreatorRankingFacade.getCreatorRankings(member))
|
ApiResponse.ok(homeCreatorRankingFacade.getCreatorRankings(member, langContext.lang))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.home.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacade
|
import kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacade
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.FollowRecommendedCreatorsRequest
|
import kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.FollowRecommendedCreatorsRequest
|
||||||
@@ -18,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RequestMapping("/api/v2/home/recommendations")
|
@RequestMapping("/api/v2/home/recommendations")
|
||||||
class HomeRecommendationController(
|
class HomeRecommendationController(
|
||||||
private val homeRecommendationFacade: HomeRecommendationFacade,
|
private val homeRecommendationFacade: HomeRecommendationFacade,
|
||||||
private val recommendedCreatorFollowService: RecommendedCreatorFollowService
|
private val recommendedCreatorFollowService: RecommendedCreatorFollowService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getHomeRecommendations(
|
fun getHomeRecommendations(
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(homeRecommendationFacade.getHomeRecommendations(member))
|
ApiResponse.ok(homeRecommendationFacade.getHomeRecommendations(member, langContext.lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/lives")
|
@GetMapping("/lives")
|
||||||
@@ -37,7 +39,8 @@ class HomeRecommendationController(
|
|||||||
homeRecommendationFacade.getLives(
|
homeRecommendationFacade.getLives(
|
||||||
requireMember(member),
|
requireMember(member),
|
||||||
normalizePage(page),
|
normalizePage(page),
|
||||||
normalizeSize(size)
|
normalizeSize(size),
|
||||||
|
langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,8 @@ class HomeRecommendationController(
|
|||||||
homeRecommendationFacade.getRecentDebutCreators(
|
homeRecommendationFacade.getRecentDebutCreators(
|
||||||
requireMember(member),
|
requireMember(member),
|
||||||
normalizePage(page),
|
normalizePage(page),
|
||||||
normalizeSize(size)
|
normalizeSize(size),
|
||||||
|
langContext.lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.application
|
package kr.co.vividnext.sodalive.v2.api.home.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.dto.ranking.CreatorRankingResponse
|
import kr.co.vividnext.sodalive.v2.api.home.dto.ranking.CreatorRankingResponse
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryService
|
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryService
|
||||||
@@ -9,9 +10,8 @@ import org.springframework.stereotype.Component
|
|||||||
class HomeCreatorRankingFacade(
|
class HomeCreatorRankingFacade(
|
||||||
private val creatorRankingQueryService: CreatorRankingQueryService
|
private val creatorRankingQueryService: CreatorRankingQueryService
|
||||||
) {
|
) {
|
||||||
fun getCreatorRankings(member: Member?): CreatorRankingResponse {
|
fun getCreatorRankings(member: Member?, lang: Lang): CreatorRankingResponse {
|
||||||
return CreatorRankingResponse.from(
|
val result = creatorRankingQueryService.getCreatorRankings(viewerMemberId = member?.id, lang = lang)
|
||||||
creatorRankingQueryService.getCreatorRankings(viewerMemberId = member?.id)
|
return CreatorRankingResponse.from(result)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-15
@@ -44,21 +44,23 @@ class HomeRecommendationFacade(
|
|||||||
) {
|
) {
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
fun getHomeRecommendations(member: Member?): HomeRecommendationResponse {
|
fun getHomeRecommendations(member: Member?, lang: Lang): HomeRecommendationResponse {
|
||||||
val startedAt = System.currentTimeMillis()
|
val startedAt = System.currentTimeMillis()
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val includeAdult = resolveAdultVisibility(member)
|
val includeAdult = resolveAdultVisibility(member)
|
||||||
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
|
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
|
||||||
|
|
||||||
HomeRecommendationResponse(
|
val lives = queryService.findLiveRecommendations(
|
||||||
lives = queryService.findLiveRecommendations(
|
|
||||||
limit = HOME_LIVE_LIMIT,
|
limit = HOME_LIVE_LIMIT,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
includeAdultLives = includeAdult,
|
includeAdultLives = includeAdult,
|
||||||
isViewerCreator = member?.role == MemberRole.CREATOR,
|
isViewerCreator = member?.role == MemberRole.CREATOR,
|
||||||
effectiveViewerGender = member?.effectiveGender()
|
effectiveViewerGender = member?.effectiveGender(),
|
||||||
).map { it.toItem() },
|
lang = lang
|
||||||
|
)
|
||||||
|
val response = HomeRecommendationResponse(
|
||||||
|
lives = lives.map { it.toItem() },
|
||||||
banners = queryService.findHomeBanners(
|
banners = queryService.findHomeBanners(
|
||||||
limit = HOME_BANNER_LIMIT,
|
limit = HOME_BANNER_LIMIT,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
@@ -69,21 +71,24 @@ class HomeRecommendationFacade(
|
|||||||
recentlyActiveCreators = queryService.findRecentlyActiveCreators(
|
recentlyActiveCreators = queryService.findRecentlyActiveCreators(
|
||||||
HOME_ACTIVE_CREATOR_LIMIT,
|
HOME_ACTIVE_CREATOR_LIMIT,
|
||||||
member?.id,
|
member?.id,
|
||||||
includeAdult
|
includeAdult,
|
||||||
|
lang
|
||||||
)
|
)
|
||||||
.map { it.toItem() },
|
.map { it.toItem() },
|
||||||
recentDebutCreators = queryService.findRecentDebutCreators(
|
recentDebutCreators = queryService.findRecentDebutCreators(
|
||||||
now,
|
now,
|
||||||
limit = HOME_RECENT_DEBUT_CREATOR_LIMIT,
|
limit = HOME_RECENT_DEBUT_CREATOR_LIMIT,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
includeAdultContents = includeAdult
|
includeAdultContents = includeAdult,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
.map { it.toItem() },
|
.map { it.toItem() },
|
||||||
firstAudioContents = queryService.findFirstAudioContents(
|
firstAudioContents = queryService.findFirstAudioContents(
|
||||||
now,
|
now,
|
||||||
limit = HOME_FIRST_AUDIO_CONTENT_LIMIT,
|
limit = HOME_FIRST_AUDIO_CONTENT_LIMIT,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
includeAdultContents = includeAdult
|
includeAdultContents = includeAdult,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
.map { it.toItem() },
|
.map { it.toItem() },
|
||||||
aiCharacters = queryService.findAiCharacterRecommendations(
|
aiCharacters = queryService.findAiCharacterRecommendations(
|
||||||
@@ -94,16 +99,19 @@ class HomeRecommendationFacade(
|
|||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
includeAdultGenres = includeAdult,
|
includeAdultGenres = includeAdult,
|
||||||
genreLimit = HOME_GENRE_CREATOR_GENRE_LIMIT,
|
genreLimit = HOME_GENRE_CREATOR_GENRE_LIMIT,
|
||||||
creatorLimit = HOME_GENRE_CREATOR_CREATOR_LIMIT
|
creatorLimit = HOME_GENRE_CREATOR_CREATOR_LIMIT,
|
||||||
|
lang = lang
|
||||||
).map { it.toItem() },
|
).map { it.toItem() },
|
||||||
cheerCreators = queryService.findCheerCreatorRecommendations(HOME_CHEER_CREATOR_LIMIT, member?.id)
|
cheerCreators = queryService.findCheerCreatorRecommendations(HOME_CHEER_CREATOR_LIMIT, member?.id, lang)
|
||||||
.map { it.toCreatorItem() },
|
.map { it.toCreatorItem() },
|
||||||
popularCommunityPosts = queryService.findPopularCommunityRecommendations(
|
popularCommunityPosts = queryService.findPopularCommunityRecommendations(
|
||||||
limit = HOME_POPULAR_COMMUNITY_LIMIT,
|
limit = HOME_POPULAR_COMMUNITY_LIMIT,
|
||||||
memberId = member?.id,
|
memberId = member?.id,
|
||||||
includeAdultCommunities = includeAdult
|
includeAdultCommunities = includeAdult,
|
||||||
|
lang = lang
|
||||||
).map { it.toItem() }
|
).map { it.toItem() }
|
||||||
)
|
)
|
||||||
|
response
|
||||||
}.onSuccess { response ->
|
}.onSuccess { response ->
|
||||||
log.info(
|
log.info(
|
||||||
"event=home_recommendations_query_success memberId={} elapsedMs={} emptySections={}",
|
"event=home_recommendations_query_success memberId={} elapsedMs={} emptySections={}",
|
||||||
@@ -122,7 +130,7 @@ class HomeRecommendationFacade(
|
|||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLives(member: Member, page: Int, size: Int): HomeRecommendationPageResponse<HomeLiveItem> {
|
fun getLives(member: Member, page: Int, size: Int, lang: Lang): HomeRecommendationPageResponse<HomeLiveItem> {
|
||||||
val startedAt = System.currentTimeMillis()
|
val startedAt = System.currentTimeMillis()
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val fetched = queryService.findLiveRecommendations(
|
val fetched = queryService.findLiveRecommendations(
|
||||||
@@ -131,7 +139,8 @@ class HomeRecommendationFacade(
|
|||||||
memberId = member.id,
|
memberId = member.id,
|
||||||
includeAdultLives = resolveAdultVisibility(member),
|
includeAdultLives = resolveAdultVisibility(member),
|
||||||
isViewerCreator = member.role == MemberRole.CREATOR,
|
isViewerCreator = member.role == MemberRole.CREATOR,
|
||||||
effectiveViewerGender = member.effectiveGender()
|
effectiveViewerGender = member.effectiveGender(),
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
fetched.toPage(page, size) { it.toItem() }
|
fetched.toPage(page, size) { it.toItem() }
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
@@ -141,7 +150,12 @@ class HomeRecommendationFacade(
|
|||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRecentDebutCreators(member: Member, page: Int, size: Int): HomeRecommendationPageResponse<HomeCreatorItem> {
|
fun getRecentDebutCreators(
|
||||||
|
member: Member,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
lang: Lang
|
||||||
|
): HomeRecommendationPageResponse<HomeCreatorItem> {
|
||||||
val startedAt = System.currentTimeMillis()
|
val startedAt = System.currentTimeMillis()
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val fetched = queryService.findRecentDebutCreators(
|
val fetched = queryService.findRecentDebutCreators(
|
||||||
@@ -149,7 +163,8 @@ class HomeRecommendationFacade(
|
|||||||
offset = page.toOffset(size),
|
offset = page.toOffset(size),
|
||||||
limit = size + 1,
|
limit = size + 1,
|
||||||
memberId = member.id,
|
memberId = member.id,
|
||||||
includeAdultContents = resolveAdultVisibility(member)
|
includeAdultContents = resolveAdultVisibility(member),
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
fetched.toPage(page, size) { it.toItem() }
|
fetched.toPage(page, size) { it.toItem() }
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.following.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.home.following.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.following.application.HomeFollowingFacade
|
import kr.co.vividnext.sodalive.v2.api.home.following.application.HomeFollowingFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/home/following")
|
@RequestMapping("/api/v2/home/following")
|
||||||
class HomeFollowingController(
|
class HomeFollowingController(
|
||||||
private val facade: HomeFollowingFacade
|
private val facade: HomeFollowingFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getFollowingTab(
|
fun getFollowingTab(
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(facade.getFollowingTab(member))
|
ApiResponse.ok(facade.getFollowingTab(member, langContext.lang))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.following.application
|
package kr.co.vividnext.sodalive.v2.api.home.following.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.following.dto.HomeFollowingTabResponse
|
import kr.co.vividnext.sodalive.v2.api.home.following.dto.HomeFollowingTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
||||||
@@ -11,14 +12,15 @@ class HomeFollowingFacade(
|
|||||||
private val homeFollowingQueryService: HomeFollowingQueryService,
|
private val homeFollowingQueryService: HomeFollowingQueryService,
|
||||||
private val chatRoomListService: ChatRoomListService
|
private val chatRoomListService: ChatRoomListService
|
||||||
) {
|
) {
|
||||||
fun getFollowingTab(member: Member?): HomeFollowingTabResponse {
|
fun getFollowingTab(member: Member?, lang: Lang): HomeFollowingTabResponse {
|
||||||
if (member == null) {
|
if (member == null) {
|
||||||
return HomeFollowingTabResponse.loginRequired()
|
return HomeFollowingTabResponse.loginRequired()
|
||||||
}
|
}
|
||||||
|
|
||||||
val home = homeFollowingQueryService.findHomeFollowing(member)
|
val home = homeFollowingQueryService.findHomeFollowing(member, lang)
|
||||||
val recentChats = chatRoomListService.getRooms(
|
val recentChats = chatRoomListService.getRooms(
|
||||||
member,
|
member,
|
||||||
|
lang = lang,
|
||||||
filter = "ALL",
|
filter = "ALL",
|
||||||
cursor = null,
|
cursor = null,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
|
|||||||
+4
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.home.live.adapter.`in`.web
|
|||||||
|
|
||||||
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.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacade
|
import kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacade
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
@@ -13,14 +14,15 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/home/on-air-lives")
|
@RequestMapping("/api/v2/home/on-air-lives")
|
||||||
class HomeOnAirLiveController(
|
class HomeOnAirLiveController(
|
||||||
private val homeOnAirLiveFacade: HomeOnAirLiveFacade
|
private val homeOnAirLiveFacade: HomeOnAirLiveFacade,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getOnAirLives(
|
fun getOnAirLives(
|
||||||
@RequestParam(defaultValue = "0") page: Int,
|
@RequestParam(defaultValue = "0") page: Int,
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
) = run {
|
) = run {
|
||||||
ApiResponse.ok(homeOnAirLiveFacade.getOnAirLives(requireMember(member), page))
|
ApiResponse.ok(homeOnAirLiveFacade.getOnAirLives(requireMember(member), page, langContext.lang))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireMember(member: Member?): Member {
|
private fun requireMember(member: Member?): Member {
|
||||||
|
|||||||
+6
-3
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.home.live.application
|
package kr.co.vividnext.sodalive.v2.api.home.live.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
@@ -20,7 +21,7 @@ class HomeOnAirLiveFacade(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
fun getOnAirLives(member: Member, page: Int): HomeOnAirLivePageResponse {
|
fun getOnAirLives(member: Member, page: Int, lang: Lang): HomeOnAirLivePageResponse {
|
||||||
val normalizedPage = page.coerceIn(0, MAX_PAGE)
|
val normalizedPage = page.coerceIn(0, MAX_PAGE)
|
||||||
val fetched = queryService.findLiveRecommendations(
|
val fetched = queryService.findLiveRecommendations(
|
||||||
offset = normalizedPage.toLong() * PAGE_SIZE,
|
offset = normalizedPage.toLong() * PAGE_SIZE,
|
||||||
@@ -28,9 +29,11 @@ class HomeOnAirLiveFacade(
|
|||||||
memberId = member.id,
|
memberId = member.id,
|
||||||
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member),
|
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member),
|
||||||
isViewerCreator = member.role == MemberRole.CREATOR,
|
isViewerCreator = member.role == MemberRole.CREATOR,
|
||||||
effectiveViewerGender = member.effectiveGender()
|
effectiveViewerGender = member.effectiveGender(),
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val items = fetched.take(PAGE_SIZE).map { it.toResponse() }
|
val pageItems = fetched.take(PAGE_SIZE)
|
||||||
|
val items = pageItems.map { it.toResponse() }
|
||||||
|
|
||||||
return HomeOnAirLivePageResponse(
|
return HomeOnAirLivePageResponse(
|
||||||
items = items,
|
items = items,
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.chat.controller
|
package kr.co.vividnext.sodalive.v2.chat.controller
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListPageResponse
|
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListPageResponse
|
||||||
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
||||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v2/chat/rooms")
|
@RequestMapping("/api/v2/chat/rooms")
|
||||||
class ChatRoomListController(
|
class ChatRoomListController(
|
||||||
private val service: ChatRoomListService
|
private val service: ChatRoomListService,
|
||||||
|
private val langContext: LangContext
|
||||||
) {
|
) {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getRooms(
|
fun getRooms(
|
||||||
@@ -23,6 +25,6 @@ class ChatRoomListController(
|
|||||||
@RequestParam(defaultValue = "30") limit: Int
|
@RequestParam(defaultValue = "30") limit: Int
|
||||||
) = run {
|
) = run {
|
||||||
if (member == null) return@run ApiResponse.ok(ChatRoomListPageResponse(emptyList(), false, null))
|
if (member == null) return@run ApiResponse.ok(ChatRoomListPageResponse(emptyList(), false, null))
|
||||||
ApiResponse.ok(service.getRooms(member, filter, cursor, limit))
|
ApiResponse.ok(service.getRooms(member, langContext.lang, filter, cursor, limit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.chat.service
|
|||||||
import kr.co.vividnext.sodalive.chat.room.ChatMessageType
|
import kr.co.vividnext.sodalive.chat.room.ChatMessageType
|
||||||
import kr.co.vividnext.sodalive.chat.room.repository.ChatRoomRepository
|
import kr.co.vividnext.sodalive.chat.room.repository.ChatRoomRepository
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.toUtcIso
|
import kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.toUtcIso
|
||||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
||||||
@@ -26,6 +27,7 @@ class ChatRoomListService(
|
|||||||
) {
|
) {
|
||||||
fun getRooms(
|
fun getRooms(
|
||||||
member: Member,
|
member: Member,
|
||||||
|
lang: Lang,
|
||||||
filter: String = ChatRoomListFilter.ALL.name,
|
filter: String = ChatRoomListFilter.ALL.name,
|
||||||
cursor: String? = null,
|
cursor: String? = null,
|
||||||
limit: Int = DEFAULT_LIMIT,
|
limit: Int = DEFAULT_LIMIT,
|
||||||
@@ -50,6 +52,7 @@ class ChatRoomListService(
|
|||||||
parsedCursor?.chatType,
|
parsedCursor?.chatType,
|
||||||
parsedCursor?.roomId,
|
parsedCursor?.roomId,
|
||||||
followedCreatorsOnly,
|
followedCreatorsOnly,
|
||||||
|
lang,
|
||||||
pageable
|
pageable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -70,6 +73,7 @@ class ChatRoomListService(
|
|||||||
parsedCursor?.chatType,
|
parsedCursor?.chatType,
|
||||||
parsedCursor?.roomId,
|
parsedCursor?.roomId,
|
||||||
followedCreatorsOnly,
|
followedCreatorsOnly,
|
||||||
|
lang,
|
||||||
pageable
|
pageable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-9
@@ -16,6 +16,7 @@ import kr.co.vividnext.sodalive.content.translation.QContentTranslation
|
|||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
@@ -23,6 +24,7 @@ import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
|||||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllAudio
|
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllAudio
|
||||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllSeries
|
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllSeries
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -64,7 +66,10 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
onlyFree: Boolean,
|
onlyFree: Boolean,
|
||||||
onlyPointAvailable: Boolean
|
onlyPointAvailable: Boolean
|
||||||
): List<MainContentAllAudio> {
|
): List<MainContentAllAudio> {
|
||||||
val rows = findAudioRows(memberId, canViewAdultContent, now, sort, offset, limit, onlyPaid, onlyFree, onlyPointAvailable)
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "mainContentAllAudioCreatorName")
|
||||||
|
val rows = findAudioRows(
|
||||||
|
memberId, canViewAdultContent, now, sort, offset, limit, onlyPaid, onlyFree, onlyPointAvailable, creatorName
|
||||||
|
)
|
||||||
if (rows.isEmpty()) return emptyList()
|
if (rows.isEmpty()) return emptyList()
|
||||||
|
|
||||||
val contentIds = rows.map { it.get(audioContent.id)!! }
|
val contentIds = rows.map { it.get(audioContent.id)!! }
|
||||||
@@ -85,7 +90,8 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
||||||
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
||||||
isOriginalSeries = originalSeriesByContentId[contentId] ?: false,
|
isOriginalSeries = originalSeriesByContentId[contentId] ?: false,
|
||||||
creatorNickname = row.get(member.nickname)!!
|
creatorNickname = row.get(creatorName.displayName)!!,
|
||||||
|
creatorMemberId = creatorId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,18 +128,21 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
if (seriesIds.isEmpty()) return emptyList()
|
if (seriesIds.isEmpty()) return emptyList()
|
||||||
|
|
||||||
val seriesTranslation = QSeriesTranslation("mainContentAllSeriesTranslation")
|
val seriesTranslation = QSeriesTranslation("mainContentAllSeriesTranslation")
|
||||||
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "mainContentAllSeriesCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
series.id,
|
series.id,
|
||||||
series.title,
|
series.title,
|
||||||
seriesTranslation.renderedPayload,
|
seriesTranslation.renderedPayload,
|
||||||
series.coverImage,
|
series.coverImage,
|
||||||
member.nickname,
|
member.id,
|
||||||
|
creatorName.displayName,
|
||||||
series.isOriginal,
|
series.isOriginal,
|
||||||
series.isAdult
|
series.isAdult
|
||||||
)
|
)
|
||||||
.from(series)
|
.from(series)
|
||||||
.join(series.member, member)
|
.join(series.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(seriesTranslation)
|
.leftJoin(seriesTranslation)
|
||||||
.on(
|
.on(
|
||||||
seriesTranslation.seriesId.eq(series.id),
|
seriesTranslation.seriesId.eq(series.id),
|
||||||
@@ -148,9 +157,10 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
seriesId = row.get(series.id)!!,
|
seriesId = row.get(series.id)!!,
|
||||||
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: row.get(series.title)!!,
|
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: row.get(series.title)!!,
|
||||||
coverImageUrl = row.get(series.coverImage).toCdnUrl(cloudFrontHost),
|
coverImageUrl = row.get(series.coverImage).toCdnUrl(cloudFrontHost),
|
||||||
creatorNickname = row.get(member.nickname)!!,
|
creatorNickname = row.get(creatorName.displayName)!!,
|
||||||
isOriginal = row.get(series.isOriginal)!!,
|
isOriginal = row.get(series.isOriginal)!!,
|
||||||
isAdult = row.get(series.isAdult)!!
|
isAdult = row.get(series.isAdult)!!,
|
||||||
|
creatorMemberId = row.get(member.id)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +174,8 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
limit: Int,
|
limit: Int,
|
||||||
onlyPaid: Boolean,
|
onlyPaid: Boolean,
|
||||||
onlyFree: Boolean,
|
onlyFree: Boolean,
|
||||||
onlyPointAvailable: Boolean
|
onlyPointAvailable: Boolean,
|
||||||
|
creatorName: CreatorNameJoin
|
||||||
): List<Tuple> {
|
): List<Tuple> {
|
||||||
val query = queryFactory
|
val query = queryFactory
|
||||||
.select(
|
.select(
|
||||||
@@ -175,11 +186,12 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
audioContent.isAdult,
|
audioContent.isAdult,
|
||||||
audioContent.isPointAvailable,
|
audioContent.isPointAvailable,
|
||||||
audioContent.member.id,
|
audioContent.member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
audioContent.releaseDate
|
audioContent.releaseDate
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.join(audioContent.member, member)
|
.join(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.join(audioContent.theme, audioContentTheme)
|
.join(audioContent.theme, audioContentTheme)
|
||||||
.where(audioCondition(memberId, canViewAdultContent, now, onlyPaid, onlyFree, onlyPointAvailable))
|
.where(audioCondition(memberId, canViewAdultContent, now, onlyPaid, onlyFree, onlyPointAvailable))
|
||||||
|
|
||||||
@@ -192,7 +204,7 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
revenueOrder.audioContent.id.eq(audioContent.id),
|
revenueOrder.audioContent.id.eq(audioContent.id),
|
||||||
revenueOrder.isActive.isTrue
|
revenueOrder.isActive.isTrue
|
||||||
)
|
)
|
||||||
.groupByAudioRow()
|
.groupByAudioRow(creatorName)
|
||||||
.orderBy(
|
.orderBy(
|
||||||
revenueOrder.can.sum().coalesce(0).desc(),
|
revenueOrder.can.sum().coalesce(0).desc(),
|
||||||
audioContent.releaseDate.desc(),
|
audioContent.releaseDate.desc(),
|
||||||
@@ -301,7 +313,7 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
return query.offset(offset).limit(limit.toLong()).fetch()
|
return query.offset(offset).limit(limit.toLong()).fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JPAQuery<Tuple>.groupByAudioRow(): JPAQuery<Tuple> {
|
private fun JPAQuery<Tuple>.groupByAudioRow(creatorName: CreatorNameJoin): JPAQuery<Tuple> {
|
||||||
return groupBy(
|
return groupBy(
|
||||||
audioContent.id,
|
audioContent.id,
|
||||||
audioContent.title,
|
audioContent.title,
|
||||||
@@ -311,6 +323,7 @@ class DefaultMainContentAllQueryRepository(
|
|||||||
audioContent.isPointAvailable,
|
audioContent.isPointAvailable,
|
||||||
audioContent.member.id,
|
audioContent.member.id,
|
||||||
member.nickname,
|
member.nickname,
|
||||||
|
creatorName.name.displayName,
|
||||||
audioContent.releaseDate
|
audioContent.releaseDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-7
@@ -1,7 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.content.all.application
|
package kr.co.vividnext.sodalive.v2.content.all.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
@@ -19,8 +19,7 @@ import java.time.LocalDateTime
|
|||||||
class MainContentAllQueryService(
|
class MainContentAllQueryService(
|
||||||
private val queryPort: MainContentAllQueryPort,
|
private val queryPort: MainContentAllQueryPort,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val queryPolicy: MainContentAllQueryPolicy = MainContentAllQueryPolicy(),
|
private val queryPolicy: MainContentAllQueryPolicy = MainContentAllQueryPolicy()
|
||||||
private val langContext: LangContext
|
|
||||||
) {
|
) {
|
||||||
fun getContents(
|
fun getContents(
|
||||||
type: String?,
|
type: String?,
|
||||||
@@ -28,7 +27,8 @@ class MainContentAllQueryService(
|
|||||||
dayOfWeek: String?,
|
dayOfWeek: String?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
member: Member?
|
member: Member?,
|
||||||
|
lang: Lang
|
||||||
): MainContentAll {
|
): MainContentAll {
|
||||||
val resolvedType = queryPolicy.resolveType(type)
|
val resolvedType = queryPolicy.resolveType(type)
|
||||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||||
@@ -47,6 +47,7 @@ class MainContentAllQueryService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
now = now,
|
now = now,
|
||||||
|
lang = lang,
|
||||||
onlyPaid = true
|
onlyPaid = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ class MainContentAllQueryService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
now = now,
|
now = now,
|
||||||
|
lang = lang,
|
||||||
onlyFree = true
|
onlyFree = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ class MainContentAllQueryService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
now = now,
|
now = now,
|
||||||
|
lang = lang,
|
||||||
onlyPointAvailable = true
|
onlyPointAvailable = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,7 +82,8 @@ class MainContentAllQueryService(
|
|||||||
page = resolvedPage,
|
page = resolvedPage,
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
|
||||||
MainContentAllType.ORIGINAL -> getSeriesContents(
|
MainContentAllType.ORIGINAL -> getSeriesContents(
|
||||||
@@ -90,6 +94,7 @@ class MainContentAllQueryService(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
now = now,
|
now = now,
|
||||||
|
lang = lang,
|
||||||
onlyOriginal = true
|
onlyOriginal = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -103,6 +108,7 @@ class MainContentAllQueryService(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
|
lang: Lang,
|
||||||
onlyPaid: Boolean = false,
|
onlyPaid: Boolean = false,
|
||||||
onlyFree: Boolean = false,
|
onlyFree: Boolean = false,
|
||||||
onlyPointAvailable: Boolean = false
|
onlyPointAvailable: Boolean = false
|
||||||
@@ -122,7 +128,7 @@ class MainContentAllQueryService(
|
|||||||
sort = sort,
|
sort = sort,
|
||||||
offset = page.offset,
|
offset = page.offset,
|
||||||
limit = page.size + 1,
|
limit = page.size + 1,
|
||||||
locale = langContext.lang.code,
|
locale = lang.code,
|
||||||
onlyPaid = onlyPaid,
|
onlyPaid = onlyPaid,
|
||||||
onlyFree = onlyFree,
|
onlyFree = onlyFree,
|
||||||
onlyPointAvailable = onlyPointAvailable
|
onlyPointAvailable = onlyPointAvailable
|
||||||
@@ -148,6 +154,7 @@ class MainContentAllQueryService(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
|
lang: Lang,
|
||||||
onlyOriginal: Boolean = false
|
onlyOriginal: Boolean = false
|
||||||
): MainContentAll {
|
): MainContentAll {
|
||||||
val totalCount = queryPort.countSeries(memberId, canViewAdultContent, now, onlyOriginal, dayOfWeek)
|
val totalCount = queryPort.countSeries(memberId, canViewAdultContent, now, onlyOriginal, dayOfWeek)
|
||||||
@@ -160,7 +167,7 @@ class MainContentAllQueryService(
|
|||||||
limit = page.size + 1,
|
limit = page.size + 1,
|
||||||
onlyOriginal = onlyOriginal,
|
onlyOriginal = onlyOriginal,
|
||||||
dayOfWeek = dayOfWeek,
|
dayOfWeek = dayOfWeek,
|
||||||
locale = langContext.lang.code
|
locale = lang.code
|
||||||
)
|
)
|
||||||
|
|
||||||
return MainContentAll(
|
return MainContentAll(
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ data class MainContentAllAudio(
|
|||||||
val isPointAvailable: Boolean,
|
val isPointAvailable: Boolean,
|
||||||
val isFirstContent: Boolean,
|
val isFirstContent: Boolean,
|
||||||
val isOriginalSeries: Boolean,
|
val isOriginalSeries: Boolean,
|
||||||
val creatorNickname: String
|
val creatorNickname: String,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class MainContentAllSeries(
|
data class MainContentAllSeries(
|
||||||
@@ -32,5 +33,6 @@ data class MainContentAllSeries(
|
|||||||
val coverImageUrl: String?,
|
val coverImageUrl: String?,
|
||||||
val creatorNickname: String,
|
val creatorNickname: String,
|
||||||
val isOriginal: Boolean,
|
val isOriginal: Boolean,
|
||||||
val isAdult: Boolean
|
val isAdult: Boolean,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|||||||
+34
-8
@@ -1,9 +1,15 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
package kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import com.querydsl.jpa.JPAExpressions
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
import kr.co.vividnext.sodalive.content.translation.ContentTranslationRepository
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.adapter.out.persistence.QAudioRankingSnapshot.audioRankingSnapshot
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -11,14 +17,34 @@ import java.time.LocalDateTime
|
|||||||
@Repository
|
@Repository
|
||||||
class DefaultAudioRankingSnapshotPersistenceAdapter(
|
class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||||
private val repository: AudioRankingSnapshotRepository,
|
private val repository: AudioRankingSnapshotRepository,
|
||||||
private val contentTranslationRepository: ContentTranslationRepository
|
private val contentTranslationRepository: ContentTranslationRepository,
|
||||||
|
private val queryFactory: JPAQueryFactory
|
||||||
) : AudioRankingSnapshotPort {
|
) : AudioRankingSnapshotPort {
|
||||||
override fun findLatestVisibleSnapshots(
|
override fun findLatestVisibleSnapshots(
|
||||||
rankingType: AudioRankingType,
|
rankingType: AudioRankingType,
|
||||||
nowUtc: LocalDateTime,
|
nowUtc: LocalDateTime,
|
||||||
locale: String
|
locale: String
|
||||||
): List<AudioRankingSnapshotRecord> {
|
): List<AudioRankingSnapshotRecord> {
|
||||||
return repository.findLatestVisibleSnapshots(rankingType.name, nowUtc).toRecords(locale)
|
val snapshot = audioRankingSnapshot
|
||||||
|
val latest = QAudioRankingSnapshot("latestVisibleAudioRanking")
|
||||||
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "audioRankingCreatorName")
|
||||||
|
val displayName = creatorName.displayName.coalesce(snapshot.creatorNickname)
|
||||||
|
return queryFactory
|
||||||
|
.select(snapshot, displayName)
|
||||||
|
.from(snapshot)
|
||||||
|
.leftJoin(member).on(member.id.eq(snapshot.creatorMemberId))
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
.where(
|
||||||
|
snapshot.rankingType.eq(rankingType),
|
||||||
|
snapshot.visibleFromAtUtc.eq(
|
||||||
|
JPAExpressions.select(latest.visibleFromAtUtc.max()).from(latest)
|
||||||
|
.where(latest.rankingType.eq(rankingType), latest.visibleFromAtUtc.loe(nowUtc))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(snapshot.rank.asc())
|
||||||
|
.fetch()
|
||||||
|
.map { it.get(snapshot)!!.toRecord(displayCreatorNickname = it.get(displayName)!!) }
|
||||||
|
.translateTitles(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findPreviousVisibleSnapshots(
|
override fun findPreviousVisibleSnapshots(
|
||||||
@@ -31,7 +57,7 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
|||||||
rankingType = rankingType.name,
|
rankingType = rankingType.name,
|
||||||
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
|
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
|
||||||
nowUtc = nowUtc
|
nowUtc = nowUtc
|
||||||
).toRecords(locale)
|
).map { it.toRecord() }.translateTitles(locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -50,7 +76,7 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
|||||||
repository.saveAll(newSnapshots.map { it.toEntity(visibleFromAtUtc) })
|
repository.saveAll(newSnapshots.map { it.toEntity(visibleFromAtUtc) })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<AudioRankingSnapshot>.toRecords(locale: String): List<AudioRankingSnapshotRecord> {
|
private fun List<AudioRankingSnapshotRecord>.translateTitles(locale: String): List<AudioRankingSnapshotRecord> {
|
||||||
if (isEmpty()) return emptyList()
|
if (isEmpty()) return emptyList()
|
||||||
val translatedTitleByContentId = contentTranslationRepository.findByContentIdInAndLocale(
|
val translatedTitleByContentId = contentTranslationRepository.findByContentIdInAndLocale(
|
||||||
map { it.contentId },
|
map { it.contentId },
|
||||||
@@ -58,19 +84,19 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
|||||||
).mapNotNull { translation ->
|
).mapNotNull { translation ->
|
||||||
translation.renderedPayload.title.takeIf { it.isNotBlank() }?.let { translation.contentId to it }
|
translation.renderedPayload.title.takeIf { it.isNotBlank() }?.let { translation.contentId to it }
|
||||||
}.toMap()
|
}.toMap()
|
||||||
return map { snapshot -> snapshot.toRecord(translatedTitleByContentId[snapshot.contentId] ?: snapshot.title) }
|
return map { snapshot -> snapshot.copy(title = translatedTitleByContentId[snapshot.contentId] ?: snapshot.title) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AudioRankingSnapshot.toRecord(displayTitle: String = title): AudioRankingSnapshotRecord {
|
private fun AudioRankingSnapshot.toRecord(displayCreatorNickname: String = creatorNickname): AudioRankingSnapshotRecord {
|
||||||
return AudioRankingSnapshotRecord(
|
return AudioRankingSnapshotRecord(
|
||||||
rankingType = rankingType,
|
rankingType = rankingType,
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
contentId = contentId,
|
contentId = contentId,
|
||||||
title = displayTitle,
|
title = title,
|
||||||
creatorMemberId = creatorMemberId,
|
creatorMemberId = creatorMemberId,
|
||||||
creatorNickname = creatorNickname,
|
creatorNickname = displayCreatorNickname,
|
||||||
coverImageUrl = coverImageUrl,
|
coverImageUrl = coverImageUrl,
|
||||||
releaseDate = releaseDate,
|
releaseDate = releaseDate,
|
||||||
isAdult = isAdult,
|
isAdult = isAdult,
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||||
@@ -19,7 +19,6 @@ import java.time.ZonedDateTime
|
|||||||
@Service
|
@Service
|
||||||
class AudioRankingQueryService(
|
class AudioRankingQueryService(
|
||||||
private val snapshotPort: AudioRankingSnapshotPort,
|
private val snapshotPort: AudioRankingSnapshotPort,
|
||||||
private val langContext: LangContext,
|
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val blockPort: AudioRankingBlockPort,
|
private val blockPort: AudioRankingBlockPort,
|
||||||
private val jobService: AudioRankingSnapshotJobService,
|
private val jobService: AudioRankingSnapshotJobService,
|
||||||
@@ -29,9 +28,9 @@ class AudioRankingQueryService(
|
|||||||
) {
|
) {
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
fun getRankings(type: AudioRankingType, member: Member?): AudioRanking {
|
fun getRankings(type: AudioRankingType, member: Member?, lang: Lang): AudioRanking {
|
||||||
val nowUtc = nowProvider().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
val nowUtc = nowProvider().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
||||||
val locale = langContext.lang.code
|
val locale = lang.code
|
||||||
val latestSnapshots = findLatestVisibleSnapshots(type, nowUtc, locale)
|
val latestSnapshots = findLatestVisibleSnapshots(type, nowUtc, locale)
|
||||||
if (latestSnapshots.isEmpty()) {
|
if (latestSnapshots.isEmpty()) {
|
||||||
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
||||||
@@ -114,7 +113,8 @@ class AudioRankingQueryService(
|
|||||||
rank = rank,
|
rank = rank,
|
||||||
rankChange = if (showRankChange && previousRank != null) previousRank - rank else null,
|
rankChange = if (showRankChange && previousRank != null) previousRank - rank else null,
|
||||||
isNew = showRankChange && previousRank == null,
|
isNew = showRankChange && previousRank == null,
|
||||||
coverImageUrl = coverImageUrl.toCdnUrl(cloudFrontHost)
|
coverImageUrl = coverImageUrl.toCdnUrl(cloudFrontHost),
|
||||||
|
creatorMemberId = creatorMemberId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ data class AudioRankingItem(
|
|||||||
val rank: Int,
|
val rank: Int,
|
||||||
val rankChange: Int?,
|
val rankChange: Int?,
|
||||||
val isNew: Boolean,
|
val isNew: Boolean,
|
||||||
val coverImageUrl: String?
|
val coverImageUrl: String?,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-5
@@ -26,6 +26,7 @@ import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
|||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioRecommendationVisibility
|
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioRecommendationVisibility
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.CommentedAudio
|
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.CommentedAudio
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.OriginalSeries
|
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.OriginalSeries
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.port.out.RecommendationSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.recommendation.port.out.RecommendationSnapshotRecord
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
@@ -120,7 +121,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
locale: String
|
locale: String
|
||||||
): List<AudioCard> {
|
): List<AudioCard> {
|
||||||
val rows = audioRows(memberId, canViewAdultContent, now) {
|
val rows = audioRows(memberId, canViewAdultContent, now, locale) {
|
||||||
orderBy(audioContent.releaseDate.desc(), audioContent.id.desc()).limit(limit.toLong())
|
orderBy(audioContent.releaseDate.desc(), audioContent.id.desc()).limit(limit.toLong())
|
||||||
}
|
}
|
||||||
return rows.toAudioCards(now, canViewAdultContent, locale)
|
return rows.toAudioCards(now, canViewAdultContent, locale)
|
||||||
@@ -134,7 +135,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
locale: String
|
locale: String
|
||||||
): List<AudioCard> {
|
): List<AudioCard> {
|
||||||
val randomTieBreaker = Expressions.numberTemplate(Double::class.java, "function('rand')")
|
val randomTieBreaker = Expressions.numberTemplate(Double::class.java, "function('rand')")
|
||||||
val rows = audioRows(memberId, canViewAdultContent, now, audioContent.price.eq(0)) {
|
val rows = audioRows(memberId, canViewAdultContent, now, locale, audioContent.price.eq(0)) {
|
||||||
orderBy(randomTieBreaker.asc()).limit(limit.toLong())
|
orderBy(randomTieBreaker.asc()).limit(limit.toLong())
|
||||||
}
|
}
|
||||||
return rows.toAudioCards(now, canViewAdultContent, locale)
|
return rows.toAudioCards(now, canViewAdultContent, locale)
|
||||||
@@ -152,6 +153,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
memberId,
|
memberId,
|
||||||
canViewAdultContent,
|
canViewAdultContent,
|
||||||
now,
|
now,
|
||||||
|
locale,
|
||||||
audioContent.isPointAvailable.isTrue.and(audioContent.price.gt(0))
|
audioContent.isPointAvailable.isTrue.and(audioContent.price.gt(0))
|
||||||
) {
|
) {
|
||||||
orderBy(randomTieBreaker.asc()).limit(limit.toLong())
|
orderBy(randomTieBreaker.asc()).limit(limit.toLong())
|
||||||
@@ -168,7 +170,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
): List<AudioCard> {
|
): List<AudioCard> {
|
||||||
if (contentIds.isEmpty()) return emptyList()
|
if (contentIds.isEmpty()) return emptyList()
|
||||||
val orderById = contentIds.withIndex().associate { it.value to it.index }
|
val orderById = contentIds.withIndex().associate { it.value to it.index }
|
||||||
val rows = audioRows(memberId, canViewAdultContent, now, audioContent.id.`in`(contentIds)) { this }
|
val rows = audioRows(memberId, canViewAdultContent, now, locale, audioContent.id.`in`(contentIds)) { this }
|
||||||
return rows.toAudioCards(now, canViewAdultContent, locale).sortedBy { orderById[it.audioContentId] ?: Int.MAX_VALUE }
|
return rows.toAudioCards(now, canViewAdultContent, locale).sortedBy { orderById[it.audioContentId] ?: Int.MAX_VALUE }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,9 +446,11 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
|
locale: String,
|
||||||
extraCondition: BooleanExpression? = null,
|
extraCondition: BooleanExpression? = null,
|
||||||
customize: com.querydsl.jpa.impl.JPAQuery<Tuple>.() -> com.querydsl.jpa.impl.JPAQuery<Tuple>
|
customize: com.querydsl.jpa.impl.JPAQuery<Tuple>.() -> com.querydsl.jpa.impl.JPAQuery<Tuple>
|
||||||
): List<Tuple> {
|
): List<Tuple> {
|
||||||
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "audioRecommendationCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
audioContent.id,
|
audioContent.id,
|
||||||
@@ -457,10 +461,11 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
audioContent.isAdult,
|
audioContent.isAdult,
|
||||||
audioContent.isPointAvailable,
|
audioContent.isPointAvailable,
|
||||||
audioContent.member.id,
|
audioContent.member.id,
|
||||||
member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.join(audioContent.member, member)
|
.join(audioContent.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.join(audioContent.theme, audioContentTheme)
|
.join(audioContent.theme, audioContentTheme)
|
||||||
.where(publicAudioCondition(memberId, canViewAdultContent, now), extraCondition)
|
.where(publicAudioCondition(memberId, canViewAdultContent, now), extraCondition)
|
||||||
.customize()
|
.customize()
|
||||||
@@ -478,6 +483,7 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
val firstContentIdByCreatorId = firstAudioContentIds(creatorIds, now, canViewAdultContent)
|
val firstContentIdByCreatorId = firstAudioContentIds(creatorIds, now, canViewAdultContent)
|
||||||
val isOriginalSeriesByContentId = originalSeriesFlags(contentIds)
|
val isOriginalSeriesByContentId = originalSeriesFlags(contentIds)
|
||||||
val translatedTitleByContentId = translatedTitles(contentIds, locale)
|
val translatedTitleByContentId = translatedTitles(contentIds, locale)
|
||||||
|
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "audioRecommendationCreatorName")
|
||||||
return map { row ->
|
return map { row ->
|
||||||
val contentId = row.get(audioContent.id)!!
|
val contentId = row.get(audioContent.id)!!
|
||||||
val creatorId = row.get(audioContent.member.id)!!
|
val creatorId = row.get(audioContent.member.id)!!
|
||||||
@@ -491,7 +497,8 @@ class DefaultAudioRecommendationQueryRepository(
|
|||||||
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
||||||
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
||||||
isOriginalSeries = isOriginalSeriesByContentId[contentId] ?: false,
|
isOriginalSeries = isOriginalSeriesByContentId[contentId] ?: false,
|
||||||
creatorNickname = row.get(member.nickname)!!
|
creatorNickname = row.get(creatorName.displayName)!!,
|
||||||
|
creatorMemberId = creatorId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-7
@@ -1,7 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.content.recommendation.application
|
package kr.co.vividnext.sodalive.v2.content.recommendation.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
||||||
@@ -18,16 +17,15 @@ import java.time.ZoneId
|
|||||||
class AudioRecommendationQueryService(
|
class AudioRecommendationQueryService(
|
||||||
private val queryPort: AudioRecommendationQueryPort,
|
private val queryPort: AudioRecommendationQueryPort,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val snapshotFallbackService: AudioRecommendationSnapshotFallbackService,
|
private val snapshotFallbackService: AudioRecommendationSnapshotFallbackService
|
||||||
private val langContext: LangContext
|
|
||||||
) {
|
) {
|
||||||
fun getRecommendations(member: Member?): AudioRecommendations {
|
fun getRecommendations(member: Member?, lang: Lang): AudioRecommendations {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val fallbackNow = LocalDateTime.now(KST_ZONE)
|
val fallbackNow = LocalDateTime.now(KST_ZONE)
|
||||||
val canViewAdultContent = canViewAdultContent(member)
|
val canViewAdultContent = canViewAdultContent(member)
|
||||||
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
|
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
|
||||||
val memberId = member?.id
|
val memberId = member?.id
|
||||||
val locale = langContext.lang.code
|
val locale = lang.code
|
||||||
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
|
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
|
||||||
val newAndHotSectionType = newAndHotSectionType(visibility)
|
val newAndHotSectionType = newAndHotSectionType(visibility)
|
||||||
val mostCommentedSectionType = mostCommentedSectionType(visibility)
|
val mostCommentedSectionType = mostCommentedSectionType(visibility)
|
||||||
@@ -77,7 +75,7 @@ class AudioRecommendationQueryService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findNewAndHotAudios(member: Member, offset: Long, limit: Int): List<AudioCard> {
|
fun findNewAndHotAudios(member: Member, offset: Long, limit: Int, lang: Lang): List<AudioCard> {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val fallbackNow = LocalDateTime.now(KST_ZONE)
|
val fallbackNow = LocalDateTime.now(KST_ZONE)
|
||||||
val canViewAdultContent = canViewAdultContent(member)
|
val canViewAdultContent = canViewAdultContent(member)
|
||||||
@@ -90,7 +88,7 @@ class AudioRecommendationQueryService(
|
|||||||
member.id,
|
member.id,
|
||||||
canViewAdultContent,
|
canViewAdultContent,
|
||||||
now,
|
now,
|
||||||
langContext.lang.code
|
lang.code
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,8 @@ data class AudioCard(
|
|||||||
val isPointAvailable: Boolean,
|
val isPointAvailable: Boolean,
|
||||||
val isFirstContent: Boolean,
|
val isFirstContent: Boolean,
|
||||||
val isOriginalSeries: Boolean,
|
val isOriginalSeries: Boolean,
|
||||||
val creatorNickname: String
|
val creatorNickname: String,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CommentedAudio(
|
data class CommentedAudio(
|
||||||
|
|||||||
+6
-2
@@ -15,12 +15,14 @@ import kr.co.vividnext.sodalive.content.theme.translation.QContentThemeTranslati
|
|||||||
import kr.co.vividnext.sodalive.content.translation.QContentTranslation
|
import kr.co.vividnext.sodalive.content.translation.QContentTranslation
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioContentRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioContentRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioThemeRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out.CreatorChannelAudioThemeRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -28,17 +30,19 @@ import java.time.LocalDateTime
|
|||||||
class DefaultCreatorChannelAudioQueryRepository(
|
class DefaultCreatorChannelAudioQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelAudioQueryRepository {
|
) : CreatorChannelAudioQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelAudioCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelAudioCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelAudioCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelAudioCreatorRecord::class.java,
|
CreatorChannelAudioCreatorRecord::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.role,
|
member.role,
|
||||||
member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.audio.application
|
package kr.co.vividnext.sodalive.v2.creator.channel.audio.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -39,18 +40,19 @@ class CreatorChannelAudioQueryService(
|
|||||||
themeId: Long?,
|
themeId: Long?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelAudioTab {
|
): CreatorChannelAudioTab {
|
||||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||||
val audioPage = queryPolicy.createPage(page, size)
|
val audioPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,11 +1,12 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.audio.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelAudioQueryPort {
|
interface CreatorChannelAudioQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelAudioCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelAudioCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
|
|||||||
+58
-23
@@ -4,28 +4,37 @@ import com.querydsl.core.Tuple
|
|||||||
import com.querydsl.core.types.OrderSpecifier
|
import com.querydsl.core.types.OrderSpecifier
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
import com.querydsl.core.types.dsl.CaseBuilder
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommunity.creatorCommunity
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentContextRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentContextRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCommentRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.port.out.CreatorChannelCommunityPostRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class DefaultCreatorChannelCommunityQueryRepository(
|
class DefaultCreatorChannelCommunityQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelCommunityQueryRepository {
|
) : CreatorChannelCommunityQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCommunityCreatorRecord? {
|
private val postCreatorDisplayName = Expressions.stringPath("postCreatorDisplayName")
|
||||||
|
private val commentWriterDisplayName = Expressions.stringPath("commentWriterDisplayName")
|
||||||
|
private val localizedWriterName = Expressions.booleanPath("localizedWriterName")
|
||||||
|
|
||||||
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCommunityCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelCommunityCreatorName")
|
||||||
val creator = queryFactory
|
val creator = queryFactory
|
||||||
.select(member.id, member.role, member.nickname)
|
.select(member.id, member.role, creatorName.displayName)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
@@ -35,7 +44,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
return CreatorChannelCommunityCreatorRecord(
|
return CreatorChannelCommunityCreatorRecord(
|
||||||
creatorId = creator.get(member.id)!!,
|
creatorId = creator.get(member.id)!!,
|
||||||
role = creator.get(member.role)!!,
|
role = creator.get(member.role)!!,
|
||||||
nickname = creator.get(member.nickname)!!
|
nickname = creator.get(creatorName.displayName)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +80,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityPostRecord> {
|
): List<CreatorChannelCommunityPostRecord> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelCommunityPostCreatorName")
|
||||||
val rows = queryFactory
|
val rows = queryFactory
|
||||||
.selectCommunityPostRow()
|
.selectCommunityPostRow(creatorName)
|
||||||
.from(creatorCommunity)
|
.from(creatorCommunity)
|
||||||
|
.join(creatorCommunity.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(communityPostCondition(creatorId, canViewAdultContent))
|
.where(communityPostCondition(creatorId, canViewAdultContent))
|
||||||
.orderBy(
|
.orderBy(
|
||||||
CaseBuilder()
|
CaseBuilder()
|
||||||
@@ -104,11 +117,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
isPinned: Boolean,
|
isPinned: Boolean,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityPostRecord> {
|
): List<CreatorChannelCommunityPostRecord> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelHomePostCreatorName")
|
||||||
val rows = queryFactory
|
val rows = queryFactory
|
||||||
.selectCommunityPostRow()
|
.selectCommunityPostRow(creatorName)
|
||||||
.from(creatorCommunity)
|
.from(creatorCommunity)
|
||||||
|
.join(creatorCommunity.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
homeCommunityPostCondition(creatorId, viewerId, canViewAdultContent),
|
homeCommunityPostCondition(creatorId, viewerId, canViewAdultContent),
|
||||||
creatorCommunity.isFixed.eq(isPinned),
|
creatorCommunity.isFixed.eq(isPinned),
|
||||||
@@ -124,11 +141,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
override fun findCommunityPost(
|
override fun findCommunityPost(
|
||||||
postId: Long,
|
postId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
canViewAdultContent: Boolean
|
canViewAdultContent: Boolean,
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityPostRecord? {
|
): CreatorChannelCommunityPostRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelPostDetailCreatorName")
|
||||||
val rows = queryFactory
|
val rows = queryFactory
|
||||||
.selectCommunityPostRow()
|
.selectCommunityPostRow(creatorName)
|
||||||
.from(creatorCommunity)
|
.from(creatorCommunity)
|
||||||
|
.join(creatorCommunity.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(communityPostByIdCondition(postId, canViewAdultContent))
|
.where(communityPostByIdCondition(postId, canViewAdultContent))
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
||||||
@@ -155,11 +176,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord> {
|
): List<CreatorChannelCommunityCommentRecord> {
|
||||||
|
val writerName = CreatorNameJoin(member, lang, "channelCommentWriterName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectCommunityCommentRow()
|
.selectCommunityCommentRow(writerName)
|
||||||
.from(creatorCommunityComment)
|
.from(creatorCommunityComment)
|
||||||
|
.join(creatorCommunityComment.member, member)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(rootCommentCondition(postId, creatorId, viewerId))
|
.where(rootCommentCondition(postId, creatorId, viewerId))
|
||||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@@ -170,13 +195,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
|
|
||||||
override fun findLatestRepliesByCommentIds(
|
override fun findLatestRepliesByCommentIds(
|
||||||
commentIds: List<Long>,
|
commentIds: List<Long>,
|
||||||
viewerId: Long
|
viewerId: Long,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord> {
|
): List<CreatorChannelCommunityCommentRecord> {
|
||||||
if (commentIds.isEmpty()) return emptyList()
|
if (commentIds.isEmpty()) return emptyList()
|
||||||
|
val writerName = CreatorNameJoin(member, lang, "channelLatestReplyWriterName")
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectCommunityCommentRow()
|
.selectCommunityCommentRow(writerName)
|
||||||
.from(creatorCommunityComment)
|
.from(creatorCommunityComment)
|
||||||
|
.join(creatorCommunityComment.member, member)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(replyCondition(commentIds, viewerId))
|
.where(replyCondition(commentIds, viewerId))
|
||||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
@@ -224,11 +253,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
commentId: Long,
|
commentId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord> {
|
): List<CreatorChannelCommunityCommentRecord> {
|
||||||
|
val writerName = CreatorNameJoin(member, lang, "channelReplyWriterName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectCommunityCommentRow()
|
.selectCommunityCommentRow(writerName)
|
||||||
.from(creatorCommunityComment)
|
.from(creatorCommunityComment)
|
||||||
|
.join(creatorCommunityComment.member, member)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(replyCondition(commentId, viewerId))
|
.where(replyCondition(commentId, viewerId))
|
||||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@@ -237,10 +270,10 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
.map { it.toCommunityCommentRecord() }
|
.map { it.toCommunityCommentRecord() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JPAQueryFactory.selectCommunityPostRow() = select(
|
private fun JPAQueryFactory.selectCommunityPostRow(creatorName: CreatorNameJoin) = select(
|
||||||
creatorCommunity.id,
|
creatorCommunity.id,
|
||||||
creatorCommunity.member.id,
|
creatorCommunity.member.id,
|
||||||
creatorCommunity.member.nickname,
|
creatorName.displayName.`as`(postCreatorDisplayName),
|
||||||
creatorCommunity.member.profileImage,
|
creatorCommunity.member.profileImage,
|
||||||
creatorCommunity.imagePath,
|
creatorCommunity.imagePath,
|
||||||
creatorCommunity.audioPath,
|
creatorCommunity.audioPath,
|
||||||
@@ -252,16 +285,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
creatorCommunity.isCommentAvailable
|
creatorCommunity.isCommentAvailable
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun JPAQueryFactory.selectCommunityCommentRow() = select(
|
private fun JPAQueryFactory.selectCommunityCommentRow(writerName: CreatorNameJoin) = select(
|
||||||
creatorCommunityComment.id,
|
creatorCommunityComment.id,
|
||||||
creatorCommunityComment.parent.id,
|
creatorCommunityComment.parent.id,
|
||||||
creatorCommunityComment.creatorCommunity.id,
|
creatorCommunityComment.creatorCommunity.id,
|
||||||
creatorCommunityComment.member.id,
|
creatorCommunityComment.member.id,
|
||||||
creatorCommunityComment.member.profileImage,
|
creatorCommunityComment.member.profileImage,
|
||||||
creatorCommunityComment.member.nickname,
|
writerName.displayName.`as`(commentWriterDisplayName),
|
||||||
creatorCommunityComment.comment,
|
creatorCommunityComment.comment,
|
||||||
creatorCommunityComment.isSecret,
|
creatorCommunityComment.isSecret,
|
||||||
creatorCommunityComment.createdAt
|
creatorCommunityComment.createdAt,
|
||||||
|
writerName.name.id.isNotNull.`as`(localizedWriterName)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun communityPostCondition(creatorId: Long, canViewAdultContent: Boolean): BooleanExpression {
|
private fun communityPostCondition(creatorId: Long, canViewAdultContent: Boolean): BooleanExpression {
|
||||||
@@ -519,7 +553,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
get() = get(creatorCommunity.member.id)!!
|
get() = get(creatorCommunity.member.id)!!
|
||||||
|
|
||||||
private val Tuple.creatorNickname: String
|
private val Tuple.creatorNickname: String
|
||||||
get() = get(creatorCommunity.member.nickname)!!
|
get() = get(postCreatorDisplayName)!!
|
||||||
|
|
||||||
private val Tuple.creatorProfilePath: String?
|
private val Tuple.creatorProfilePath: String?
|
||||||
get() = get(creatorCommunity.member.profileImage)
|
get() = get(creatorCommunity.member.profileImage)
|
||||||
@@ -555,10 +589,11 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
|||||||
postId = get(creatorCommunityComment.creatorCommunity.id)!!,
|
postId = get(creatorCommunityComment.creatorCommunity.id)!!,
|
||||||
writerId = get(creatorCommunityComment.member.id)!!,
|
writerId = get(creatorCommunityComment.member.id)!!,
|
||||||
writerProfilePath = get(creatorCommunityComment.member.profileImage),
|
writerProfilePath = get(creatorCommunityComment.member.profileImage),
|
||||||
writerNickname = get(creatorCommunityComment.member.nickname)!!,
|
writerNickname = get(commentWriterDisplayName)!!,
|
||||||
content = get(creatorCommunityComment.comment)!!,
|
content = get(creatorCommunityComment.comment)!!,
|
||||||
isSecret = get(creatorCommunityComment.isSecret)!!,
|
isSecret = get(creatorCommunityComment.isSecret)!!,
|
||||||
createdAt = get(creatorCommunityComment.createdAt)!!
|
createdAt = get(creatorCommunityComment.createdAt)!!,
|
||||||
|
isLocalizedName = get(localizedWriterName)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-17
@@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.community.application
|
|||||||
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -46,17 +47,18 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityTab {
|
): CreatorChannelCommunityTab {
|
||||||
val communityPage = queryPolicy.createPage(page, size)
|
val communityPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,8 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
offset = communityPage.offset,
|
offset = communityPage.offset,
|
||||||
limit = communityPage.fetchLimit
|
limit = communityPage.fetchLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val communityPosts = queryPolicy.limitItems(fetchedPosts, communityPage)
|
val communityPosts = queryPolicy.limitItems(fetchedPosts, communityPage)
|
||||||
val displayContents = creatorCommunityTranslationService
|
val displayContents = creatorCommunityTranslationService
|
||||||
@@ -94,7 +97,8 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
isPinned: Boolean,
|
isPinned: Boolean,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityPost> {
|
): List<CreatorChannelCommunityPost> {
|
||||||
val posts = queryPortProvider.getObject()
|
val posts = queryPortProvider.getObject()
|
||||||
.findHomeCommunityPosts(
|
.findHomeCommunityPosts(
|
||||||
@@ -102,7 +106,8 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
isPinned = isPinned,
|
isPinned = isPinned,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
limit = limit
|
limit = limit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val displayContents = creatorCommunityTranslationService
|
val displayContents = creatorCommunityTranslationService
|
||||||
.findDisplayContents(posts.map { it.postId }, langContext.lang.code)
|
.findDisplayContents(posts.map { it.postId }, langContext.lang.code)
|
||||||
@@ -115,12 +120,13 @@ class CreatorChannelCommunityQueryService(
|
|||||||
fun getCommunityPostDetail(
|
fun getCommunityPostDetail(
|
||||||
postId: Long,
|
postId: Long,
|
||||||
viewer: Member,
|
viewer: Member,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityPostDetail {
|
): CreatorChannelCommunityPostDetail {
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||||
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent)
|
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent, lang)
|
||||||
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||||
|
|
||||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||||
@@ -134,7 +140,7 @@ class CreatorChannelCommunityQueryService(
|
|||||||
|
|
||||||
return CreatorChannelCommunityPostDetail(
|
return CreatorChannelCommunityPostDetail(
|
||||||
post = postRecord.copy(content = displayContent?.content ?: postRecord.content).toDomain(viewerId),
|
post = postRecord.copy(content = displayContent?.content ?: postRecord.content).toDomain(viewerId),
|
||||||
comments = getCommunityComments(postId, viewer, page = 0, size = 20, now = now)
|
comments = getCommunityComments(postId, viewer, page = 0, size = 20, now = now, lang = lang)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +149,14 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityComments {
|
): CreatorChannelCommunityComments {
|
||||||
val communityPage = queryPolicy.createPage(page, size)
|
val communityPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(viewer)
|
||||||
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent)
|
val postRecord = queryPort.findCommunityPost(postId, viewerId, canViewAdultContent, lang)
|
||||||
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
?: throw SodaException(messageKey = INVALID_COMMUNITY_REQUEST_MESSAGE_KEY)
|
||||||
|
|
||||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||||
@@ -168,12 +175,14 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
creatorId = postRecord.creatorId,
|
creatorId = postRecord.creatorId,
|
||||||
offset = communityPage.offset,
|
offset = communityPage.offset,
|
||||||
limit = communityPage.fetchLimit
|
limit = communityPage.fetchLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val limitedComments = queryPolicy.limitItems(fetchedComments, communityPage)
|
val limitedComments = queryPolicy.limitItems(fetchedComments, communityPage)
|
||||||
val latestReplies = queryPort.findLatestRepliesByCommentIds(
|
val latestReplies = queryPort.findLatestRepliesByCommentIds(
|
||||||
commentIds = limitedComments.map { it.commentId },
|
commentIds = limitedComments.map { it.commentId },
|
||||||
viewerId = viewerId
|
viewerId = viewerId,
|
||||||
|
lang = lang
|
||||||
).associateBy { it.parentCommentId }
|
).associateBy { it.parentCommentId }
|
||||||
|
|
||||||
return CreatorChannelCommunityComments(
|
return CreatorChannelCommunityComments(
|
||||||
@@ -193,7 +202,8 @@ class CreatorChannelCommunityQueryService(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityReplies {
|
): CreatorChannelCommunityReplies {
|
||||||
val communityPage = queryPolicy.createPage(page, size)
|
val communityPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
@@ -217,7 +227,8 @@ class CreatorChannelCommunityQueryService(
|
|||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
offset = communityPage.offset,
|
offset = communityPage.offset,
|
||||||
limit = communityPage.fetchLimit
|
limit = communityPage.fetchLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return CreatorChannelCommunityReplies(
|
return CreatorChannelCommunityReplies(
|
||||||
@@ -274,7 +285,7 @@ class CreatorChannelCommunityQueryService(
|
|||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
writerId = writerId,
|
writerId = writerId,
|
||||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||||
content = content,
|
content = content,
|
||||||
isSecret = isSecret,
|
isSecret = isSecret,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
@@ -287,7 +298,7 @@ class CreatorChannelCommunityQueryService(
|
|||||||
commentId = commentId,
|
commentId = commentId,
|
||||||
writerId = writerId,
|
writerId = writerId,
|
||||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||||
content = content,
|
content = content,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
|
|||||||
+16
-8
@@ -1,10 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.community.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.community.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelCommunityQueryPort {
|
interface CreatorChannelCommunityQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCommunityCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCommunityCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ interface CreatorChannelCommunityQueryPort {
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityPostRecord>
|
): List<CreatorChannelCommunityPostRecord>
|
||||||
|
|
||||||
fun findHomeCommunityPosts(
|
fun findHomeCommunityPosts(
|
||||||
@@ -27,13 +29,15 @@ interface CreatorChannelCommunityQueryPort {
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
isPinned: Boolean,
|
isPinned: Boolean,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityPostRecord>
|
): List<CreatorChannelCommunityPostRecord>
|
||||||
|
|
||||||
fun findCommunityPost(
|
fun findCommunityPost(
|
||||||
postId: Long,
|
postId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
canViewAdultContent: Boolean
|
canViewAdultContent: Boolean,
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelCommunityPostRecord?
|
): CreatorChannelCommunityPostRecord?
|
||||||
|
|
||||||
fun countCommunityComments(
|
fun countCommunityComments(
|
||||||
@@ -47,12 +51,14 @@ interface CreatorChannelCommunityQueryPort {
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord>
|
): List<CreatorChannelCommunityCommentRecord>
|
||||||
|
|
||||||
fun findLatestRepliesByCommentIds(
|
fun findLatestRepliesByCommentIds(
|
||||||
commentIds: List<Long>,
|
commentIds: List<Long>,
|
||||||
viewerId: Long
|
viewerId: Long,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord>
|
): List<CreatorChannelCommunityCommentRecord>
|
||||||
|
|
||||||
fun findRootCommentContext(
|
fun findRootCommentContext(
|
||||||
@@ -70,7 +76,8 @@ interface CreatorChannelCommunityQueryPort {
|
|||||||
commentId: Long,
|
commentId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelCommunityCommentRecord>
|
): List<CreatorChannelCommunityCommentRecord>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +114,8 @@ data class CreatorChannelCommunityCommentRecord(
|
|||||||
val writerNickname: String,
|
val writerNickname: String,
|
||||||
val content: String,
|
val content: String,
|
||||||
val isSecret: Boolean,
|
val isSecret: Boolean,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelCommunityCommentContextRecord(
|
data class CreatorChannelCommunityCommentContextRecord(
|
||||||
|
|||||||
+16
-6
@@ -4,11 +4,13 @@ import com.querydsl.core.types.Projections
|
|||||||
import com.querydsl.core.types.dsl.BooleanExpression
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationQueryPolicy
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationQueryPolicy
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out.CreatorChannelDonationCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out.CreatorChannelDonationCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out.CreatorChannelDonationRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out.CreatorChannelDonationRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -18,16 +20,18 @@ class DefaultCreatorChannelDonationQueryRepository(
|
|||||||
) : CreatorChannelDonationQueryRepository {
|
) : CreatorChannelDonationQueryRepository {
|
||||||
private val queryPolicy = CreatorChannelDonationQueryPolicy()
|
private val queryPolicy = CreatorChannelDonationQueryPolicy()
|
||||||
|
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelDonationCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelDonationCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelDonationCreatorName")
|
||||||
val creator = queryFactory
|
val creator = queryFactory
|
||||||
.select(
|
.select(
|
||||||
member.id,
|
member.id,
|
||||||
member.role,
|
member.role,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.isVisibleDonationRank,
|
member.isVisibleDonationRank,
|
||||||
member.donationRankingPeriod
|
member.donationRankingPeriod
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
@@ -37,7 +41,7 @@ class DefaultCreatorChannelDonationQueryRepository(
|
|||||||
return CreatorChannelDonationCreatorRecord(
|
return CreatorChannelDonationCreatorRecord(
|
||||||
creatorId = creator.get(member.id)!!,
|
creatorId = creator.get(member.id)!!,
|
||||||
role = creator.get(member.role)!!,
|
role = creator.get(member.role)!!,
|
||||||
nickname = creator.get(member.nickname)!!,
|
nickname = creator.get(creatorName.displayName)!!,
|
||||||
isVisibleDonationRank = creator.get(member.isVisibleDonationRank)!!,
|
isVisibleDonationRank = creator.get(member.isVisibleDonationRank)!!,
|
||||||
donationRankingPeriod = creator.get(member.donationRankingPeriod)
|
donationRankingPeriod = creator.get(member.donationRankingPeriod)
|
||||||
)
|
)
|
||||||
@@ -75,20 +79,26 @@ class DefaultCreatorChannelDonationQueryRepository(
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelDonationRecord> {
|
): List<CreatorChannelDonationRecord> {
|
||||||
|
val donorName = CreatorNameJoin(member, lang, "channelDonationDonorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelDonationRecord::class.java,
|
CreatorChannelDonationRecord::class.java,
|
||||||
channelDonationMessage.member.nickname,
|
channelDonationMessage.member.id,
|
||||||
|
donorName.displayName,
|
||||||
channelDonationMessage.member.profileImage,
|
channelDonationMessage.member.profileImage,
|
||||||
channelDonationMessage.can,
|
channelDonationMessage.can,
|
||||||
channelDonationMessage.additionalMessage,
|
channelDonationMessage.additionalMessage,
|
||||||
channelDonationMessage.createdAt
|
channelDonationMessage.createdAt,
|
||||||
|
donorName.name.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(channelDonationMessage)
|
.from(channelDonationMessage)
|
||||||
|
.join(channelDonationMessage.member, member)
|
||||||
|
.leftJoin(donorName.name).on(donorName.condition)
|
||||||
.where(channelDonationCondition(creatorId, viewerId, now))
|
.where(channelDonationCondition(creatorId, viewerId, now))
|
||||||
.orderBy(channelDonationMessage.createdAt.desc(), channelDonationMessage.id.desc())
|
.orderBy(channelDonationMessage.createdAt.desc(), channelDonationMessage.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
|
|||||||
+9
-7
@@ -2,7 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.donation.application
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -30,7 +30,6 @@ class CreatorChannelDonationQueryService(
|
|||||||
private val rankingPort: CreatorChannelDonationRankingPort,
|
private val rankingPort: CreatorChannelDonationRankingPort,
|
||||||
private val queryPolicy: CreatorChannelDonationQueryPolicy,
|
private val queryPolicy: CreatorChannelDonationQueryPolicy,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
@@ -39,17 +38,18 @@ class CreatorChannelDonationQueryService(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime
|
now: LocalDateTime,
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelDonationTab {
|
): CreatorChannelDonationTab {
|
||||||
val donationPage = queryPolicy.createPage(page, size)
|
val donationPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,8 @@ class CreatorChannelDonationQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
now = now,
|
now = now,
|
||||||
offset = donationPage.offset,
|
offset = donationPage.offset,
|
||||||
limit = donationPage.fetchLimit
|
limit = donationPage.fetchLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return CreatorChannelDonationTab(
|
return CreatorChannelDonationTab(
|
||||||
@@ -95,7 +96,8 @@ class CreatorChannelDonationQueryService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||||
nickname = nickname.removeDeletedNicknamePrefix(),
|
memberId = memberId,
|
||||||
|
nickname = if (isLocalizedName) nickname else nickname.removeDeletedNicknamePrefix(),
|
||||||
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
can = can,
|
can = can,
|
||||||
message = message.orEmpty(),
|
message = message.orEmpty(),
|
||||||
|
|||||||
+1
@@ -19,6 +19,7 @@ data class CreatorChannelDonationRanking(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonation(
|
data class CreatorChannelDonation(
|
||||||
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImageUrl: String,
|
val profileImageUrl: String,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
|
|||||||
+7
-3
@@ -1,11 +1,12 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.donation.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
import kr.co.vividnext.sodalive.member.DonationRankingPeriod
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelDonationQueryPort {
|
interface CreatorChannelDonationQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelDonationCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelDonationCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ interface CreatorChannelDonationQueryPort {
|
|||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelDonationRecord>
|
): List<CreatorChannelDonationRecord>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +35,11 @@ data class CreatorChannelDonationCreatorRecord(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonationRecord(
|
data class CreatorChannelDonationRecord(
|
||||||
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImagePath: String?,
|
val profileImagePath: String?,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
val message: String?,
|
val message: String?,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
+23
-9
@@ -4,21 +4,25 @@ import com.querydsl.core.types.Projections
|
|||||||
import com.querydsl.core.types.dsl.BooleanExpression
|
import com.querydsl.core.types.dsl.BooleanExpression
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkReplyRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out.CreatorChannelFanTalkReplyRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class DefaultCreatorChannelFanTalkQueryRepository(
|
class DefaultCreatorChannelFanTalkQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelFanTalkQueryRepository {
|
) : CreatorChannelFanTalkQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelFanTalkCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelFanTalkCreatorName")
|
||||||
val creator = queryFactory
|
val creator = queryFactory
|
||||||
.select(member.id, member.role, member.nickname)
|
.select(member.id, member.role, creatorName.displayName)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
@@ -28,7 +32,7 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
|||||||
return CreatorChannelFanTalkCreatorRecord(
|
return CreatorChannelFanTalkCreatorRecord(
|
||||||
creatorId = creator.get(member.id)!!,
|
creatorId = creator.get(member.id)!!,
|
||||||
role = creator.get(member.role)!!,
|
role = creator.get(member.role)!!,
|
||||||
nickname = creator.get(member.nickname)!!
|
nickname = creator.get(creatorName.displayName)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,21 +63,26 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelFanTalkRecord> {
|
): List<CreatorChannelFanTalkRecord> {
|
||||||
|
val writerName = CreatorNameJoin(member, lang, "channelFanTalkWriterName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelFanTalkRecord::class.java,
|
CreatorChannelFanTalkRecord::class.java,
|
||||||
creatorCheers.id,
|
creatorCheers.id,
|
||||||
creatorCheers.member.id,
|
creatorCheers.member.id,
|
||||||
creatorCheers.member.nickname,
|
writerName.displayName,
|
||||||
creatorCheers.member.profileImage,
|
creatorCheers.member.profileImage,
|
||||||
creatorCheers.cheers,
|
creatorCheers.cheers,
|
||||||
creatorCheers.createdAt
|
creatorCheers.createdAt,
|
||||||
|
writerName.name.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(creatorCheers)
|
.from(creatorCheers)
|
||||||
|
.join(creatorCheers.member, member)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(fanTalkCondition(creatorId, viewerId))
|
.where(fanTalkCondition(creatorId, viewerId))
|
||||||
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
@@ -83,9 +92,11 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
|||||||
|
|
||||||
override fun findCreatorReplies(
|
override fun findCreatorReplies(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
parentFanTalkIds: List<Long>
|
parentFanTalkIds: List<Long>,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelFanTalkReplyRecord> {
|
): List<CreatorChannelFanTalkReplyRecord> {
|
||||||
if (parentFanTalkIds.isEmpty()) return emptyList()
|
if (parentFanTalkIds.isEmpty()) return emptyList()
|
||||||
|
val writerName = CreatorNameJoin(member, lang, "channelFanTalkReplyWriterName")
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
@@ -94,13 +105,16 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
|||||||
creatorCheers.id,
|
creatorCheers.id,
|
||||||
creatorCheers.parent.id,
|
creatorCheers.parent.id,
|
||||||
creatorCheers.member.id,
|
creatorCheers.member.id,
|
||||||
creatorCheers.member.nickname,
|
writerName.displayName,
|
||||||
creatorCheers.member.profileImage,
|
creatorCheers.member.profileImage,
|
||||||
creatorCheers.cheers,
|
creatorCheers.cheers,
|
||||||
creatorCheers.createdAt
|
creatorCheers.createdAt,
|
||||||
|
writerName.name.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(creatorCheers)
|
.from(creatorCheers)
|
||||||
|
.join(creatorCheers.member, member)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(
|
.where(
|
||||||
creatorCheers.creator.id.eq(creatorId),
|
creatorCheers.creator.id.eq(creatorId),
|
||||||
creatorCheers.member.id.eq(creatorId),
|
creatorCheers.member.id.eq(creatorId),
|
||||||
|
|||||||
+13
-11
@@ -2,7 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.application
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
@@ -27,7 +27,6 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
private val queryPortProvider: ObjectProvider<CreatorChannelFanTalkQueryPort>,
|
private val queryPortProvider: ObjectProvider<CreatorChannelFanTalkQueryPort>,
|
||||||
private val queryPolicy: CreatorChannelFanTalkQueryPolicy,
|
private val queryPolicy: CreatorChannelFanTalkQueryPolicy,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
@@ -36,17 +35,18 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
viewer: Member,
|
viewer: Member,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelFanTalkTab {
|
): CreatorChannelFanTalkTab {
|
||||||
val fanTalkPage = queryPolicy.createPage(page, size)
|
val fanTalkPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
@@ -57,10 +57,11 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
offset = fanTalkPage.offset,
|
offset = fanTalkPage.offset,
|
||||||
limit = fanTalkPage.fetchLimit
|
limit = fanTalkPage.fetchLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
val fanTalkRecords = queryPolicy.limitItems(fetchedFanTalks, fanTalkPage)
|
val fanTalkRecords = queryPolicy.limitItems(fetchedFanTalks, fanTalkPage)
|
||||||
val repliesByParentId = findRepliesByParentId(queryPort, creatorId, fanTalkRecords)
|
val repliesByParentId = findRepliesByParentId(queryPort, creatorId, fanTalkRecords, lang)
|
||||||
|
|
||||||
return CreatorChannelFanTalkTab(
|
return CreatorChannelFanTalkTab(
|
||||||
fanTalkCount = queryPort.countFanTalks(creatorId, viewerId),
|
fanTalkCount = queryPort.countFanTalks(creatorId, viewerId),
|
||||||
@@ -80,11 +81,12 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
private fun findRepliesByParentId(
|
private fun findRepliesByParentId(
|
||||||
queryPort: CreatorChannelFanTalkQueryPort,
|
queryPort: CreatorChannelFanTalkQueryPort,
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
fanTalkRecords: List<CreatorChannelFanTalkRecord>
|
fanTalkRecords: List<CreatorChannelFanTalkRecord>,
|
||||||
|
lang: Lang
|
||||||
): Map<Long, List<CreatorChannelFanTalkReply>> {
|
): Map<Long, List<CreatorChannelFanTalkReply>> {
|
||||||
val parentFanTalkIds = fanTalkRecords.map { it.fanTalkId }
|
val parentFanTalkIds = fanTalkRecords.map { it.fanTalkId }
|
||||||
if (parentFanTalkIds.isEmpty()) return emptyMap()
|
if (parentFanTalkIds.isEmpty()) return emptyMap()
|
||||||
return queryPort.findCreatorReplies(creatorId, parentFanTalkIds)
|
return queryPort.findCreatorReplies(creatorId, parentFanTalkIds, lang)
|
||||||
.groupBy(
|
.groupBy(
|
||||||
keySelector = { it.parentFanTalkId },
|
keySelector = { it.parentFanTalkId },
|
||||||
valueTransform = { it.toDomain() }
|
valueTransform = { it.toDomain() }
|
||||||
@@ -96,7 +98,7 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
) = CreatorChannelFanTalk(
|
) = CreatorChannelFanTalk(
|
||||||
fanTalkId = fanTalkId,
|
fanTalkId = fanTalkId,
|
||||||
writerId = writerId,
|
writerId = writerId,
|
||||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||||
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
content = content,
|
content = content,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
@@ -106,7 +108,7 @@ class CreatorChannelFanTalkQueryService(
|
|||||||
private fun CreatorChannelFanTalkReplyRecord.toDomain() = CreatorChannelFanTalkReply(
|
private fun CreatorChannelFanTalkReplyRecord.toDomain() = CreatorChannelFanTalkReply(
|
||||||
fanTalkId = fanTalkId,
|
fanTalkId = fanTalkId,
|
||||||
writerId = writerId,
|
writerId = writerId,
|
||||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||||
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
content = content,
|
content = content,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
|
|||||||
+10
-5
@@ -1,10 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.fantalk.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelFanTalkQueryPort {
|
interface CreatorChannelFanTalkQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelFanTalkCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
@@ -14,12 +15,14 @@ interface CreatorChannelFanTalkQueryPort {
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewerId: Long,
|
viewerId: Long,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelFanTalkRecord>
|
): List<CreatorChannelFanTalkRecord>
|
||||||
|
|
||||||
fun findCreatorReplies(
|
fun findCreatorReplies(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
parentFanTalkIds: List<Long>
|
parentFanTalkIds: List<Long>,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelFanTalkReplyRecord>
|
): List<CreatorChannelFanTalkReplyRecord>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,8 @@ data class CreatorChannelFanTalkRecord(
|
|||||||
val writerNickname: String,
|
val writerNickname: String,
|
||||||
val writerProfileImagePath: String?,
|
val writerProfileImagePath: String?,
|
||||||
val content: String,
|
val content: String,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelFanTalkReplyRecord(
|
data class CreatorChannelFanTalkReplyRecord(
|
||||||
@@ -45,5 +49,6 @@ data class CreatorChannelFanTalkReplyRecord(
|
|||||||
val writerNickname: String,
|
val writerNickname: String,
|
||||||
val writerProfileImagePath: String?,
|
val writerProfileImagePath: String?,
|
||||||
val content: String,
|
val content: String,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
+27
-10
@@ -17,11 +17,13 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seri
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
import kr.co.vividnext.sodalive.explorer.profile.channelDonation.QChannelDonationMessage.channelDonationMessage
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.MemberKind
|
import kr.co.vividnext.sodalive.member.MemberKind
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||||
@@ -36,6 +38,7 @@ import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelL
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelScheduleRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelScheduleRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelSeriesRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelSeriesRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelSnsRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.home.port.out.CreatorChannelSnsRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
@@ -46,10 +49,12 @@ import java.time.temporal.ChronoUnit
|
|||||||
class DefaultCreatorChannelHomeQueryRepository(
|
class DefaultCreatorChannelHomeQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelHomeQueryRepository {
|
) : CreatorChannelHomeQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelHomeCreatorName")
|
||||||
val creator = queryFactory
|
val creator = queryFactory
|
||||||
.select(member.id, member.role, member.nickname, member.profileImage, member.introduce, member.memberKind)
|
.select(member.id, member.role, creatorName.displayName, member.profileImage, member.introduce, member.memberKind)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
@@ -81,7 +86,7 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
creatorId = creator.get(member.id)!!,
|
creatorId = creator.get(member.id)!!,
|
||||||
role = creator.get(member.role)!!,
|
role = creator.get(member.role)!!,
|
||||||
characterId = characterId,
|
characterId = characterId,
|
||||||
nickname = creator.get(member.nickname)!!,
|
nickname = creator.get(creatorName.displayName)!!,
|
||||||
profileImagePath = creator.get(member.profileImage),
|
profileImagePath = creator.get(member.profileImage),
|
||||||
introduce = creator.get(member.introduce)!!,
|
introduce = creator.get(member.introduce)!!,
|
||||||
followerCount = queryFactory
|
followerCount = queryFactory
|
||||||
@@ -172,8 +177,11 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewerId: Long?,
|
viewerId: Long?,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelDonationRecord> {
|
): List<CreatorChannelDonationRecord> {
|
||||||
|
val donor = QMember("channelHomeDonor")
|
||||||
|
val donorName = CreatorNameJoin(donor, lang, "channelHomeDonorName")
|
||||||
val kstZoneId = ZoneId.of("Asia/Seoul")
|
val kstZoneId = ZoneId.of("Asia/Seoul")
|
||||||
val utcZoneId = ZoneId.of("UTC")
|
val utcZoneId = ZoneId.of("UTC")
|
||||||
val nowKst = now.atZone(utcZoneId).withZoneSameInstant(kstZoneId)
|
val nowKst = now.atZone(utcZoneId).withZoneSameInstant(kstZoneId)
|
||||||
@@ -187,14 +195,18 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelDonationRecord::class.java,
|
CreatorChannelDonationRecord::class.java,
|
||||||
channelDonationMessage.member.nickname,
|
channelDonationMessage.member.id,
|
||||||
|
donorName.displayName,
|
||||||
channelDonationMessage.member.profileImage,
|
channelDonationMessage.member.profileImage,
|
||||||
channelDonationMessage.can,
|
channelDonationMessage.can,
|
||||||
channelDonationMessage.additionalMessage.coalesce(""),
|
channelDonationMessage.additionalMessage.coalesce(""),
|
||||||
channelDonationMessage.createdAt
|
channelDonationMessage.createdAt,
|
||||||
|
donorName.name.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(channelDonationMessage)
|
.from(channelDonationMessage)
|
||||||
|
.join(channelDonationMessage.member, donor)
|
||||||
|
.leftJoin(donorName.name).on(donorName.condition)
|
||||||
.where(
|
.where(
|
||||||
channelDonationMessage.creator.id.eq(creatorId),
|
channelDonationMessage.creator.id.eq(creatorId),
|
||||||
channelDonationMessage.createdAt.goe(start),
|
channelDonationMessage.createdAt.goe(start),
|
||||||
@@ -356,7 +368,9 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findFanTalkSummary(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkSummaryRecord {
|
override fun findFanTalkSummary(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelFanTalkSummaryRecord {
|
||||||
|
val writer = QMember("channelHomeFanTalkWriter")
|
||||||
|
val writerName = CreatorNameJoin(writer, lang, "channelHomeFanTalkWriterName")
|
||||||
val totalCount = queryFactory
|
val totalCount = queryFactory
|
||||||
.select(creatorCheers.id.count())
|
.select(creatorCheers.id.count())
|
||||||
.from(creatorCheers)
|
.from(creatorCheers)
|
||||||
@@ -371,19 +385,22 @@ class DefaultCreatorChannelHomeQueryRepository(
|
|||||||
CreatorChannelFanTalkRecord::class.java,
|
CreatorChannelFanTalkRecord::class.java,
|
||||||
creatorCheers.id,
|
creatorCheers.id,
|
||||||
creatorCheers.member.id,
|
creatorCheers.member.id,
|
||||||
creatorCheers.member.nickname,
|
writerName.displayName,
|
||||||
creatorCheers.member.profileImage,
|
creatorCheers.member.profileImage,
|
||||||
creatorCheers.cheers,
|
creatorCheers.cheers,
|
||||||
creatorCheers.languageCode,
|
creatorCheers.languageCode,
|
||||||
creatorCheers.createdAt
|
creatorCheers.createdAt,
|
||||||
|
writerName.name.id.isNotNull
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(creatorCheers)
|
.from(creatorCheers)
|
||||||
|
.join(creatorCheers.member, writer)
|
||||||
|
.leftJoin(writerName.name).on(writerName.condition)
|
||||||
.where(fanTalkSummaryCondition(creatorId, viewerId))
|
.where(fanTalkSummaryCondition(creatorId, viewerId))
|
||||||
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.fetchFirst()
|
.fetchFirst()
|
||||||
?.let { it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) }
|
?.let { if (it.isLocalizedName) it else it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) }
|
||||||
|
|
||||||
return CreatorChannelFanTalkSummaryRecord(
|
return CreatorChannelFanTalkSummaryRecord(
|
||||||
totalCount = totalCount,
|
totalCount = totalCount,
|
||||||
|
|||||||
+13
-8
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.home.application
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
import kr.co.vividnext.sodalive.extensions.removeDeletedNicknamePrefix
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
@@ -54,15 +55,16 @@ class CreatorChannelHomeQueryService(
|
|||||||
fun getHome(
|
fun getHome(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewer: Member,
|
viewer: Member,
|
||||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelHome {
|
): CreatorChannelHome {
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
@@ -100,13 +102,14 @@ class CreatorChannelHomeQueryService(
|
|||||||
effectiveViewerGender = effectiveViewerGender
|
effectiveViewerGender = effectiveViewerGender
|
||||||
)?.toDomain(),
|
)?.toDomain(),
|
||||||
latestAudioContent = latestAudioContent,
|
latestAudioContent = latestAudioContent,
|
||||||
channelDonations = queryPort.findChannelDonations(creatorId, viewerId, now).map { it.toDomain() },
|
channelDonations = queryPort.findChannelDonations(creatorId, viewerId, now, lang = lang).map { it.toDomain() },
|
||||||
notices = communityQueryService.findHomeCommunityPosts(
|
notices = communityQueryService.findHomeCommunityPosts(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
isPinned = true,
|
isPinned = true,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
limit = 3
|
limit = 3,
|
||||||
|
lang = lang
|
||||||
),
|
),
|
||||||
schedules = queryPolicy.limitSchedules(
|
schedules = queryPolicy.limitSchedules(
|
||||||
queryPort.findSchedules(
|
queryPort.findSchedules(
|
||||||
@@ -135,9 +138,10 @@ class CreatorChannelHomeQueryService(
|
|||||||
viewerId = viewerId,
|
viewerId = viewerId,
|
||||||
isPinned = false,
|
isPinned = false,
|
||||||
canViewAdultContent = canViewAdultContent,
|
canViewAdultContent = canViewAdultContent,
|
||||||
limit = 3
|
limit = 3,
|
||||||
|
lang = lang
|
||||||
),
|
),
|
||||||
fanTalk = queryPort.findFanTalkSummary(creatorId, viewerId).toDomain(),
|
fanTalk = queryPort.findFanTalkSummary(creatorId, viewerId, lang).toDomain(),
|
||||||
introduce = creator.introduce,
|
introduce = creator.introduce,
|
||||||
activity = queryPort.findActivity(creatorId, now).toDomain(),
|
activity = queryPort.findActivity(creatorId, now).toDomain(),
|
||||||
sns = queryPort.findSns(creatorId).toDomain()
|
sns = queryPort.findSns(creatorId).toDomain()
|
||||||
@@ -193,7 +197,8 @@ class CreatorChannelHomeQueryService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||||
nickname = nickname.removeDeletedNicknamePrefix(),
|
memberId = memberId,
|
||||||
|
nickname = if (isLocalizedName) nickname else nickname.removeDeletedNicknamePrefix(),
|
||||||
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||||
can = can,
|
can = can,
|
||||||
message = message,
|
message = message,
|
||||||
|
|||||||
+1
@@ -43,6 +43,7 @@ data class CreatorChannelLive(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonation(
|
data class CreatorChannelDonation(
|
||||||
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImageUrl: String,
|
val profileImageUrl: String,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
|
|||||||
+10
-5
@@ -1,13 +1,14 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.home.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.home.port.out
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.content.ContentType
|
import kr.co.vividnext.sodalive.content.ContentType
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelHomeQueryPort {
|
interface CreatorChannelHomeQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
@@ -32,7 +33,8 @@ interface CreatorChannelHomeQueryPort {
|
|||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
viewerId: Long?,
|
viewerId: Long?,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
limit: Int = 8
|
limit: Int = 8,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorChannelDonationRecord>
|
): List<CreatorChannelDonationRecord>
|
||||||
|
|
||||||
fun findSchedules(
|
fun findSchedules(
|
||||||
@@ -66,7 +68,7 @@ interface CreatorChannelHomeQueryPort {
|
|||||||
limit: Int = 8
|
limit: Int = 8
|
||||||
): List<CreatorChannelSeriesRecord>
|
): List<CreatorChannelSeriesRecord>
|
||||||
|
|
||||||
fun findFanTalkSummary(creatorId: Long, viewerId: Long?): CreatorChannelFanTalkSummaryRecord
|
fun findFanTalkSummary(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelFanTalkSummaryRecord
|
||||||
|
|
||||||
fun findActivity(creatorId: Long, now: LocalDateTime): CreatorChannelActivityRecord
|
fun findActivity(creatorId: Long, now: LocalDateTime): CreatorChannelActivityRecord
|
||||||
|
|
||||||
@@ -112,11 +114,13 @@ data class CreatorChannelAudioContentRecord(
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelDonationRecord(
|
data class CreatorChannelDonationRecord(
|
||||||
|
val memberId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImagePath: String?,
|
val profileImagePath: String?,
|
||||||
val can: Int,
|
val can: Int,
|
||||||
val message: String,
|
val message: String,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelScheduleRecord(
|
data class CreatorChannelScheduleRecord(
|
||||||
@@ -148,7 +152,8 @@ data class CreatorChannelFanTalkRecord(
|
|||||||
val profileImagePath: String?,
|
val profileImagePath: String?,
|
||||||
val content: String,
|
val content: String,
|
||||||
val languageCode: String?,
|
val languageCode: String?,
|
||||||
val createdAt: LocalDateTime
|
val createdAt: LocalDateTime,
|
||||||
|
val isLocalizedName: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CreatorChannelActivityRecord(
|
data class CreatorChannelActivityRecord(
|
||||||
|
|||||||
+6
-2
@@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.content.order.QOrder.order
|
|||||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
@@ -20,6 +21,7 @@ import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
|||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelAudioContentRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelAudioContentRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelLiveRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.live.port.out.CreatorChannelLiveRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -27,17 +29,19 @@ import java.time.LocalDateTime
|
|||||||
class DefaultCreatorChannelLiveQueryRepository(
|
class DefaultCreatorChannelLiveQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelLiveQueryRepository {
|
) : CreatorChannelLiveQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelLiveCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelCreatorRecord::class.java,
|
CreatorChannelCreatorRecord::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.role,
|
member.role,
|
||||||
member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.live.application
|
package kr.co.vividnext.sodalive.v2.creator.channel.live.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -30,7 +30,6 @@ class CreatorChannelLiveQueryService(
|
|||||||
private val queryPolicy: CreatorChannelLiveReplayQueryPolicy,
|
private val queryPolicy: CreatorChannelLiveReplayQueryPolicy,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val messageSource: SodaMessageSource,
|
private val messageSource: SodaMessageSource,
|
||||||
private val langContext: LangContext,
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
@@ -40,17 +39,18 @@ class CreatorChannelLiveQueryService(
|
|||||||
sort: ContentSort,
|
sort: ContentSort,
|
||||||
page: Int,
|
page: Int,
|
||||||
size: Int,
|
size: Int,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelLiveTab {
|
): CreatorChannelLiveTab {
|
||||||
val livePage = queryPolicy.createPage(page, size)
|
val livePage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,12 +1,13 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.creator.channel.live.port.out
|
package kr.co.vividnext.sodalive.v2.creator.channel.live.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelLiveQueryPort {
|
interface CreatorChannelLiveQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -11,11 +11,13 @@ import kr.co.vividnext.sodalive.content.order.QOrder
|
|||||||
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
|
import kr.co.vividnext.sodalive.content.series.translation.QSeriesTranslation
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesCreatorRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesCreatorRecord
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesRecord
|
import kr.co.vividnext.sodalive.v2.creator.channel.series.port.out.CreatorChannelSeriesRecord
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -23,17 +25,19 @@ import java.time.LocalDateTime
|
|||||||
class DefaultCreatorChannelSeriesQueryRepository(
|
class DefaultCreatorChannelSeriesQueryRepository(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorChannelSeriesQueryRepository {
|
) : CreatorChannelSeriesQueryRepository {
|
||||||
override fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelSeriesCreatorRecord? {
|
override fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelSeriesCreatorRecord? {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "channelSeriesCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
CreatorChannelSeriesCreatorRecord::class.java,
|
CreatorChannelSeriesCreatorRecord::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.role,
|
member.role,
|
||||||
member.nickname
|
creatorName.displayName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.id.eq(creatorId),
|
member.id.eq(creatorId),
|
||||||
member.isActive.isTrue
|
member.isActive.isTrue
|
||||||
|
|||||||
+5
-3
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.creator.channel.series.application
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -37,18 +38,19 @@ class CreatorChannelSeriesQueryService(
|
|||||||
sort: String?,
|
sort: String?,
|
||||||
page: Int?,
|
page: Int?,
|
||||||
size: Int?,
|
size: Int?,
|
||||||
now: LocalDateTime = LocalDateTime.now()
|
now: LocalDateTime = LocalDateTime.now(),
|
||||||
|
lang: Lang
|
||||||
): CreatorChannelSeriesTab {
|
): CreatorChannelSeriesTab {
|
||||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||||
val seriesPage = queryPolicy.createPage(page, size)
|
val seriesPage = queryPolicy.createPage(page, size)
|
||||||
val queryPort = queryPortProvider.getObject()
|
val queryPort = queryPortProvider.getObject()
|
||||||
val viewerId = viewer.id!!
|
val viewerId = viewer.id!!
|
||||||
val creator = queryPort.findCreator(creatorId, viewerId)
|
val creator = queryPort.findCreator(creatorId, viewerId, lang)
|
||||||
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
?: throw SodaException(messageKey = "member.validation.user_not_found")
|
||||||
|
|
||||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||||
val messageTemplate = messageSource
|
val messageTemplate = messageSource
|
||||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
.getMessage("explorer.creator.blocked_access", lang)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -2,12 +2,13 @@ package kr.co.vividnext.sodalive.v2.creator.channel.series.port.out
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorChannelSeriesQueryPort {
|
interface CreatorChannelSeriesQueryPort {
|
||||||
fun findCreator(creatorId: Long, viewerId: Long?): CreatorChannelSeriesCreatorRecord?
|
fun findCreator(creatorId: Long, viewerId: Long?, lang: Lang): CreatorChannelSeriesCreatorRecord?
|
||||||
|
|
||||||
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
fun existsBlockedBetween(viewerId: Long, creatorId: Long): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.gift.application
|
package kr.co.vividnext.sodalive.v2.gift.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.Gift
|
||||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
|
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftCategoryRepository
|
||||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDelivery
|
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDelivery
|
||||||
@@ -112,7 +114,8 @@ class GiftQueryService(
|
|||||||
private val memberRepository: MemberRepository,
|
private val memberRepository: MemberRepository,
|
||||||
private val giftMailboxRepository: GiftMailboxRepository,
|
private val giftMailboxRepository: GiftMailboxRepository,
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String,
|
||||||
|
private val creatorLocalizedNameService: CreatorLocalizedNameService
|
||||||
) {
|
) {
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getFormOptions() = GiftFormOptionsResult(
|
fun getFormOptions() = GiftFormOptionsResult(
|
||||||
@@ -134,7 +137,7 @@ class GiftQueryService(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getGifts(memberId: Long, type: GiftListType, page: Int, size: Int): GiftListResult {
|
fun getGifts(memberId: Long, type: GiftListType, page: Int, size: Int, lang: Lang): GiftListResult {
|
||||||
val normalizedPage = page.coerceAtLeast(0)
|
val normalizedPage = page.coerceAtLeast(0)
|
||||||
val normalizedSize = if (size <= 0) DEFAULT_PAGE_SIZE else size
|
val normalizedSize = if (size <= 0) DEFAULT_PAGE_SIZE else size
|
||||||
val pageable = PageRequest.of(normalizedPage, normalizedSize, Sort.by(Sort.Direction.DESC, "createdAt", "id"))
|
val pageable = PageRequest.of(normalizedPage, normalizedSize, Sort.by(Sort.Direction.DESC, "createdAt", "id"))
|
||||||
@@ -156,13 +159,14 @@ class GiftQueryService(
|
|||||||
val membersById = memberRepository.findAllById(
|
val membersById = memberRepository.findAllById(
|
||||||
gifts.content.flatMap { listOf(it.senderMemberId, it.recipientMemberId) }.toSet()
|
gifts.content.flatMap { listOf(it.senderMemberId, it.recipientMemberId) }.toSet()
|
||||||
).associateBy { it.id ?: throw invalidRequest() }
|
).associateBy { it.id ?: throw invalidRequest() }
|
||||||
|
val names = creatorLocalizedNameService.findLocalizedNames(membersById.keys, lang)
|
||||||
val deliveriesByGiftId = deliveryRepository.findByGiftIdIn(
|
val deliveriesByGiftId = deliveryRepository.findByGiftIdIn(
|
||||||
gifts.content.mapNotNull { it.id }
|
gifts.content.mapNotNull { it.id }
|
||||||
).associateBy { it.giftId }
|
).associateBy { it.giftId }
|
||||||
|
|
||||||
return GiftListResult(
|
return GiftListResult(
|
||||||
totalCount = gifts.totalElements,
|
totalCount = gifts.totalElements,
|
||||||
items = gifts.content.map { it.toGiftListItem(memberId, membersById, deliveriesByGiftId) },
|
items = gifts.content.map { it.toGiftListItem(memberId, membersById, deliveriesByGiftId, names) },
|
||||||
page = normalizedPage,
|
page = normalizedPage,
|
||||||
size = normalizedSize,
|
size = normalizedSize,
|
||||||
hasNext = gifts.hasNext()
|
hasNext = gifts.hasNext()
|
||||||
@@ -170,12 +174,13 @@ class GiftQueryService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun getGiftDetail(memberId: Long, applicationNo: String): GiftDetailResult {
|
fun getGiftDetail(memberId: Long, applicationNo: String, lang: Lang): GiftDetailResult {
|
||||||
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
val gift = giftRepository.findByApplicationNo(applicationNo) ?: throw invalidRequest()
|
||||||
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
val delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||||
val direction = gift.directionFor(memberId)
|
val direction = gift.directionFor(memberId)
|
||||||
val sender = memberRepository.findByIdOrNull(gift.senderMemberId) ?: throw invalidRequest()
|
val sender = memberRepository.findByIdOrNull(gift.senderMemberId) ?: throw invalidRequest()
|
||||||
val recipient = memberRepository.findByIdOrNull(gift.recipientMemberId) ?: throw invalidRequest()
|
val recipient = memberRepository.findByIdOrNull(gift.recipientMemberId) ?: throw invalidRequest()
|
||||||
|
val names = creatorLocalizedNameService.findLocalizedNames(listOf(gift.senderMemberId, gift.recipientMemberId), lang)
|
||||||
val recipientAddress = if (direction == GiftDirection.RECEIVED) delivery.toRecipientAddressResult() else null
|
val recipientAddress = if (direction == GiftDirection.RECEIVED) delivery.toRecipientAddressResult() else null
|
||||||
|
|
||||||
return GiftDetailResult(
|
return GiftDetailResult(
|
||||||
@@ -184,8 +189,10 @@ class GiftQueryService(
|
|||||||
status = gift.status,
|
status = gift.status,
|
||||||
statusName = gift.status.displayName,
|
statusName = gift.status.displayName,
|
||||||
giftInfo = GiftDetailInfoResult(
|
giftInfo = GiftDetailInfoResult(
|
||||||
recipientCreatorNickname = recipient.nickname.takeIf { direction == GiftDirection.SENT },
|
recipientCreatorNickname = (names[gift.recipientMemberId] ?: recipient.nickname)
|
||||||
senderNickname = sender.nickname.takeIf { direction == GiftDirection.RECEIVED },
|
.takeIf { direction == GiftDirection.SENT },
|
||||||
|
senderNickname = (names[gift.senderMemberId] ?: sender.nickname)
|
||||||
|
.takeIf { direction == GiftDirection.RECEIVED },
|
||||||
sizeName = gift.sizeCode.displayName,
|
sizeName = gift.sizeCode.displayName,
|
||||||
categoryName = gift.categoryNameSnapshot,
|
categoryName = gift.categoryNameSnapshot,
|
||||||
applicationNo = gift.applicationNo.takeIf { direction == GiftDirection.SENT },
|
applicationNo = gift.applicationNo.takeIf { direction == GiftDirection.SENT },
|
||||||
@@ -212,7 +219,8 @@ class GiftQueryService(
|
|||||||
private fun Gift.toGiftListItem(
|
private fun Gift.toGiftListItem(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
membersById: Map<Long, Member>,
|
membersById: Map<Long, Member>,
|
||||||
deliveriesByGiftId: Map<Long, GiftDelivery>
|
deliveriesByGiftId: Map<Long, GiftDelivery>,
|
||||||
|
names: Map<Long, String>
|
||||||
): GiftListItemResult {
|
): GiftListItemResult {
|
||||||
val direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED
|
val direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED
|
||||||
val counterpartId = if (direction == GiftDirection.SENT) recipientMemberId else senderMemberId
|
val counterpartId = if (direction == GiftDirection.SENT) recipientMemberId else senderMemberId
|
||||||
@@ -229,7 +237,7 @@ class GiftQueryService(
|
|||||||
categoryName = categoryNameSnapshot,
|
categoryName = categoryNameSnapshot,
|
||||||
sizeName = sizeCode.displayName,
|
sizeName = sizeCode.displayName,
|
||||||
counterpartMemberId = counterpartId,
|
counterpartMemberId = counterpartId,
|
||||||
counterpartNickname = counterpart.nickname,
|
counterpartNickname = names[counterpartId] ?: counterpart.nickname,
|
||||||
counterpartProfileImageUrl = counterpart.profileImageUrl(),
|
counterpartProfileImageUrl = counterpart.profileImageUrl(),
|
||||||
recipientAddressRegistered = recipientAddressRegistered,
|
recipientAddressRegistered = recipientAddressRegistered,
|
||||||
recipientAddressDeadlineAt = delivery.recipientAddressDeadlineAt.takeUnless { recipientAddressRegistered },
|
recipientAddressDeadlineAt = delivery.recipientAddressDeadlineAt.takeUnless { recipientAddressRegistered },
|
||||||
|
|||||||
+55
-24
@@ -11,6 +11,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.QCreatorCommun
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.comment.QCreatorCommunityComment.creatorCommunityComment
|
||||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
|
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.like.QCreatorCommunityLike.creatorCommunityLike
|
||||||
import kr.co.vividnext.sodalive.extensions.toUtcIso
|
import kr.co.vividnext.sodalive.extensions.toUtcIso
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
import kr.co.vividnext.sodalive.live.room.GenderRestriction
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
@@ -20,6 +21,7 @@ import kr.co.vividnext.sodalive.member.block.QBlockMember
|
|||||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.adapter.out.persistence.QHomeFollowingNewsInbox.homeFollowingNewsInbox
|
import kr.co.vividnext.sodalive.v2.home.following.adapter.out.persistence.QHomeFollowingNewsInbox.homeFollowingNewsInbox
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCommunityPostNews
|
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCommunityPostNews
|
||||||
@@ -46,12 +48,14 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) : HomeFollowingQueryRepository {
|
) : HomeFollowingQueryRepository {
|
||||||
override fun findFollowingCreators(memberId: Long, limit: Int): List<HomeFollowingCreator> {
|
override fun findFollowingCreators(memberId: Long, limit: Int, lang: Lang): List<HomeFollowingCreator> {
|
||||||
val creator = QMember("followingCreator")
|
val creator = QMember("followingCreator")
|
||||||
|
val creatorName = CreatorNameJoin(creator, lang, "followingCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(creator.id, creator.nickname, creator.profileImage)
|
.select(creator.id, creatorName.displayName, creator.profileImage)
|
||||||
.from(creatorFollowing)
|
.from(creatorFollowing)
|
||||||
.join(creatorFollowing.creator, creator)
|
.join(creatorFollowing.creator, creator)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
creatorFollowing.member.id.eq(memberId),
|
creatorFollowing.member.id.eq(memberId),
|
||||||
creatorFollowing.isActive.isTrue,
|
creatorFollowing.isActive.isTrue,
|
||||||
@@ -65,7 +69,7 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
.map { row ->
|
.map { row ->
|
||||||
HomeFollowingCreator(
|
HomeFollowingCreator(
|
||||||
creatorId = row.get(creator.id)!!,
|
creatorId = row.get(creator.id)!!,
|
||||||
creatorNickname = row.get(creator.nickname)!!,
|
creatorNickname = row.get(creatorName.displayName)!!,
|
||||||
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage))
|
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -76,13 +80,23 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
effectiveViewerGender: Gender,
|
effectiveViewerGender: Gender,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingLive> {
|
): List<HomeFollowingLive> {
|
||||||
val creator = QMember("onAirCreator")
|
val creator = QMember("onAirCreator")
|
||||||
|
val creatorName = CreatorNameJoin(creator, lang, "followingOnAirCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(liveRoom.id, creator.profileImage, creator.nickname, liveRoom.title, liveRoom.beginDateTime)
|
.select(
|
||||||
|
liveRoom.id,
|
||||||
|
creator.profileImage,
|
||||||
|
creatorName.displayName,
|
||||||
|
liveRoom.title,
|
||||||
|
liveRoom.beginDateTime,
|
||||||
|
creator.id
|
||||||
|
)
|
||||||
.from(liveRoom)
|
.from(liveRoom)
|
||||||
.join(liveRoom.member, creator)
|
.join(liveRoom.member, creator)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||||
.where(
|
.where(
|
||||||
creatorFollowing.member.id.eq(memberId),
|
creatorFollowing.member.id.eq(memberId),
|
||||||
@@ -104,9 +118,10 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
HomeFollowingLive(
|
HomeFollowingLive(
|
||||||
liveId = row.get(liveRoom.id)!!,
|
liveId = row.get(liveRoom.id)!!,
|
||||||
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage)),
|
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage)),
|
||||||
creatorNickname = row.get(creator.nickname)!!,
|
creatorNickname = row.get(creatorName.displayName)!!,
|
||||||
title = row.get(liveRoom.title)!!,
|
title = row.get(liveRoom.title)!!,
|
||||||
startedAtUtc = row.get(liveRoom.beginDateTime)!!.toUtcIso()
|
startedAtUtc = row.get(liveRoom.beginDateTime)!!.toUtcIso(),
|
||||||
|
creatorMemberId = row.get(creator.id)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +132,8 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
effectiveViewerGender: Gender,
|
effectiveViewerGender: Gender,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingSchedule> {
|
): List<HomeFollowingSchedule> {
|
||||||
val window = monthlyScheduleWindow(now)
|
val window = monthlyScheduleWindow(now)
|
||||||
val liveSchedules = findLiveSchedules(
|
val liveSchedules = findLiveSchedules(
|
||||||
@@ -125,9 +141,10 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
canViewAdultContent,
|
canViewAdultContent,
|
||||||
effectiveViewerGender,
|
effectiveViewerGender,
|
||||||
isViewerCreator,
|
isViewerCreator,
|
||||||
window
|
window,
|
||||||
|
lang
|
||||||
)
|
)
|
||||||
val audioSchedules = findAudioSchedules(memberId, canViewAdultContent, window)
|
val audioSchedules = findAudioSchedules(memberId, canViewAdultContent, window, lang)
|
||||||
return (liveSchedules + audioSchedules)
|
return (liveSchedules + audioSchedules)
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
compareBy<HomeFollowingSchedule> { it.scheduledAtUtc }
|
compareBy<HomeFollowingSchedule> { it.scheduledAtUtc }
|
||||||
@@ -141,17 +158,20 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
memberId: Long,
|
memberId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
nowUtc: LocalDateTime,
|
nowUtc: LocalDateTime,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingNews> {
|
): List<HomeFollowingNews> {
|
||||||
val creator = QMember("newsCreator")
|
val creator = QMember("newsCreator")
|
||||||
|
val creatorName = CreatorNameJoin(creator, lang, "followingNewsCreatorName")
|
||||||
val newsAudioContent = QAudioContent("recentNewsAudioContent")
|
val newsAudioContent = QAudioContent("recentNewsAudioContent")
|
||||||
val newsCommunity = QCreatorCommunity("recentNewsCommunity")
|
val newsCommunity = QCreatorCommunity("recentNewsCommunity")
|
||||||
val rows = queryFactory
|
val rows = queryFactory
|
||||||
.select(
|
.select(
|
||||||
homeFollowingNewsInbox.id,
|
homeFollowingNewsInbox.id,
|
||||||
homeFollowingNewsInbox.newsType,
|
homeFollowingNewsInbox.newsType,
|
||||||
|
creator.id,
|
||||||
creator.profileImage,
|
creator.profileImage,
|
||||||
creator.nickname,
|
creatorName.displayName,
|
||||||
homeFollowingNewsInbox.targetId,
|
homeFollowingNewsInbox.targetId,
|
||||||
homeFollowingNewsInbox.visibleFromAtUtc,
|
homeFollowingNewsInbox.visibleFromAtUtc,
|
||||||
homeFollowingNewsInbox.rank,
|
homeFollowingNewsInbox.rank,
|
||||||
@@ -163,6 +183,7 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
)
|
)
|
||||||
.from(homeFollowingNewsInbox)
|
.from(homeFollowingNewsInbox)
|
||||||
.join(creator).on(creator.id.eq(homeFollowingNewsInbox.creatorId))
|
.join(creator).on(creator.id.eq(homeFollowingNewsInbox.creatorId))
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(newsAudioContent).on(
|
.leftJoin(newsAudioContent).on(
|
||||||
homeFollowingNewsInbox.newsType.`in`(FollowingNewsType.AUDIO_CONTENT, FollowingNewsType.CONTENT_RANKING),
|
homeFollowingNewsInbox.newsType.`in`(FollowingNewsType.AUDIO_CONTENT, FollowingNewsType.CONTENT_RANKING),
|
||||||
newsAudioContent.id.eq(homeFollowingNewsInbox.targetId)
|
newsAudioContent.id.eq(homeFollowingNewsInbox.targetId)
|
||||||
@@ -190,11 +211,14 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
.map { it.get(homeFollowingNewsInbox.targetId)!! }
|
.map { it.get(homeFollowingNewsInbox.targetId)!! }
|
||||||
val likeCounts = communityLikeCounts(communityPostIds)
|
val likeCounts = communityLikeCounts(communityPostIds)
|
||||||
val commentCounts = communityCommentCounts(communityPostIds)
|
val commentCounts = communityCommentCounts(communityPostIds)
|
||||||
return rows.map { row -> row.toHomeFollowingNews(creator, newsAudioContent, newsCommunity, likeCounts, commentCounts) }
|
return rows.map { row ->
|
||||||
|
row.toHomeFollowingNews(creator, creatorName, newsAudioContent, newsCommunity, likeCounts, commentCounts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Tuple.toHomeFollowingNews(
|
private fun Tuple.toHomeFollowingNews(
|
||||||
newsCreator: QMember,
|
newsCreator: QMember,
|
||||||
|
creatorName: CreatorNameJoin,
|
||||||
newsAudioContent: QAudioContent,
|
newsAudioContent: QAudioContent,
|
||||||
newsCommunity: QCreatorCommunity,
|
newsCommunity: QCreatorCommunity,
|
||||||
likeCounts: Map<Long, Int>,
|
likeCounts: Map<Long, Int>,
|
||||||
@@ -203,12 +227,13 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
val type = get(homeFollowingNewsInbox.newsType)!!
|
val type = get(homeFollowingNewsInbox.newsType)!!
|
||||||
val targetId = get(homeFollowingNewsInbox.targetId)!!
|
val targetId = get(homeFollowingNewsInbox.targetId)!!
|
||||||
val creatorProfileImageUrl = profileImageUrl(get(newsCreator.profileImage))
|
val creatorProfileImageUrl = profileImageUrl(get(newsCreator.profileImage))
|
||||||
val creatorNickname = get(newsCreator.nickname)!!
|
val creatorNickname = get(creatorName.displayName)!!
|
||||||
val visibleFromAtUtc = get(homeFollowingNewsInbox.visibleFromAtUtc)!!.toUtcIso()
|
val visibleFromAtUtc = get(homeFollowingNewsInbox.visibleFromAtUtc)!!.toUtcIso()
|
||||||
val rank = get(homeFollowingNewsInbox.rank)
|
val rank = get(homeFollowingNewsInbox.rank)
|
||||||
|
|
||||||
return HomeFollowingNews(
|
return HomeFollowingNews(
|
||||||
newsId = get(homeFollowingNewsInbox.id)!!.toString(),
|
newsId = get(homeFollowingNewsInbox.id)!!.toString(),
|
||||||
|
creatorMemberId = get(newsCreator.id)!!,
|
||||||
type = type,
|
type = type,
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
creatorRanking = if (type == FollowingNewsType.CREATOR_RANKING && rank != null) {
|
creatorRanking = if (type == FollowingNewsType.CREATOR_RANKING && rank != null) {
|
||||||
@@ -264,21 +289,24 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
effectiveViewerGender: Gender,
|
effectiveViewerGender: Gender,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
window: ScheduleWindow
|
window: ScheduleWindow,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingSchedule> {
|
): List<HomeFollowingSchedule> {
|
||||||
val creator = QMember("scheduleLiveCreator")
|
val creator = QMember("scheduleLiveCreator")
|
||||||
|
val creatorName = CreatorNameJoin(creator, lang, "followingLiveScheduleCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
liveRoom.id,
|
liveRoom.id,
|
||||||
creator.id,
|
creator.id,
|
||||||
creator.profileImage,
|
creator.profileImage,
|
||||||
creator.nickname,
|
creatorName.displayName,
|
||||||
liveRoom.title,
|
liveRoom.title,
|
||||||
liveRoom.beginDateTime,
|
liveRoom.beginDateTime,
|
||||||
liveRoom.channelName
|
liveRoom.channelName
|
||||||
)
|
)
|
||||||
.from(liveRoom)
|
.from(liveRoom)
|
||||||
.join(liveRoom.member, creator)
|
.join(liveRoom.member, creator)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||||
.where(
|
.where(
|
||||||
creatorFollowing.member.id.eq(memberId),
|
creatorFollowing.member.id.eq(memberId),
|
||||||
@@ -294,26 +322,29 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
notBlockedCreatorCondition(memberId, creator.id)
|
notBlockedCreatorCondition(memberId, creator.id)
|
||||||
)
|
)
|
||||||
.fetch()
|
.fetch()
|
||||||
.map { row -> row.toLiveSchedule(creator) }
|
.map { row -> row.toLiveSchedule(creator, creatorName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAudioSchedules(
|
private fun findAudioSchedules(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
window: ScheduleWindow
|
window: ScheduleWindow,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingSchedule> {
|
): List<HomeFollowingSchedule> {
|
||||||
val creator = QMember("scheduleAudioCreator")
|
val creator = QMember("scheduleAudioCreator")
|
||||||
|
val creatorName = CreatorNameJoin(creator, lang, "followingAudioScheduleCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
audioContent.id,
|
audioContent.id,
|
||||||
creator.id,
|
creator.id,
|
||||||
creator.profileImage,
|
creator.profileImage,
|
||||||
creator.nickname,
|
creatorName.displayName,
|
||||||
audioContent.title,
|
audioContent.title,
|
||||||
audioContent.releaseDate
|
audioContent.releaseDate
|
||||||
)
|
)
|
||||||
.from(audioContent)
|
.from(audioContent)
|
||||||
.join(audioContent.member, creator)
|
.join(audioContent.member, creator)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||||
.where(
|
.where(
|
||||||
creatorFollowing.member.id.eq(memberId),
|
creatorFollowing.member.id.eq(memberId),
|
||||||
@@ -328,17 +359,17 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
notBlockedCreatorCondition(memberId, creator.id)
|
notBlockedCreatorCondition(memberId, creator.id)
|
||||||
)
|
)
|
||||||
.fetch()
|
.fetch()
|
||||||
.map { row -> row.toAudioSchedule(creator) }
|
.map { row -> row.toAudioSchedule(creator, creatorName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Tuple.toLiveSchedule(creator: QMember): HomeFollowingSchedule {
|
private fun Tuple.toLiveSchedule(creator: QMember, creatorName: CreatorNameJoin): HomeFollowingSchedule {
|
||||||
val liveId = get(liveRoom.id)!!
|
val liveId = get(liveRoom.id)!!
|
||||||
val channelName = get(liveRoom.channelName)
|
val channelName = get(liveRoom.channelName)
|
||||||
return HomeFollowingSchedule(
|
return HomeFollowingSchedule(
|
||||||
scheduleId = "${CreatorActivityType.LIVE}:$liveId",
|
scheduleId = "${CreatorActivityType.LIVE}:$liveId",
|
||||||
creatorId = get(creator.id)!!,
|
creatorId = get(creator.id)!!,
|
||||||
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
||||||
creatorNickname = get(creator.nickname)!!,
|
creatorNickname = get(creatorName.displayName)!!,
|
||||||
title = get(liveRoom.title)!!,
|
title = get(liveRoom.title)!!,
|
||||||
type = CreatorActivityType.LIVE,
|
type = CreatorActivityType.LIVE,
|
||||||
targetId = liveId,
|
targetId = liveId,
|
||||||
@@ -347,13 +378,13 @@ class DefaultHomeFollowingQueryRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Tuple.toAudioSchedule(creator: QMember): HomeFollowingSchedule {
|
private fun Tuple.toAudioSchedule(creator: QMember, creatorName: CreatorNameJoin): HomeFollowingSchedule {
|
||||||
val contentId = get(audioContent.id)!!
|
val contentId = get(audioContent.id)!!
|
||||||
return HomeFollowingSchedule(
|
return HomeFollowingSchedule(
|
||||||
scheduleId = "${CreatorActivityType.AUDIO}:$contentId",
|
scheduleId = "${CreatorActivityType.AUDIO}:$contentId",
|
||||||
creatorId = get(creator.id)!!,
|
creatorId = get(creator.id)!!,
|
||||||
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
||||||
creatorNickname = get(creator.nickname)!!,
|
creatorNickname = get(creatorName.displayName)!!,
|
||||||
title = get(audioContent.title)!!,
|
title = get(audioContent.title)!!,
|
||||||
type = CreatorActivityType.AUDIO,
|
type = CreatorActivityType.AUDIO,
|
||||||
targetId = contentId,
|
targetId = contentId,
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.home.following.application
|
package kr.co.vividnext.sodalive.v2.home.following.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
@@ -19,34 +19,34 @@ import java.time.LocalDateTime
|
|||||||
class HomeFollowingQueryService(
|
class HomeFollowingQueryService(
|
||||||
private val queryPort: HomeFollowingQueryPort,
|
private val queryPort: HomeFollowingQueryPort,
|
||||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||||
private val langContext: LangContext,
|
|
||||||
private val creatorCommunityTranslationService: CreatorCommunityTranslationService,
|
private val creatorCommunityTranslationService: CreatorCommunityTranslationService,
|
||||||
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(Clock.systemUTC()) }
|
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(Clock.systemUTC()) }
|
||||||
) {
|
) {
|
||||||
fun findHomeFollowing(member: Member): HomeFollowing {
|
fun findHomeFollowing(member: Member, lang: Lang): HomeFollowing {
|
||||||
val memberId = requireNotNull(member.id)
|
val memberId = requireNotNull(member.id)
|
||||||
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(member)
|
val canViewAdultContent = memberContentPreferenceService.canViewAdultContent(member)
|
||||||
val effectiveViewerGender = member.effectiveGender()
|
val effectiveViewerGender = member.effectiveGender()
|
||||||
val isViewerCreator = member.role == MemberRole.CREATOR
|
val isViewerCreator = member.role == MemberRole.CREATOR
|
||||||
val now = nowProvider()
|
val now = nowProvider()
|
||||||
val recentNews = queryPort.findRecentNews(memberId, canViewAdultContent, now, RECENT_NEWS_LIMIT)
|
val recentNews = queryPort.findRecentNews(memberId, canViewAdultContent, now, RECENT_NEWS_LIMIT, lang)
|
||||||
val communityPostIds = recentNews.mapNotNull { news ->
|
val communityPostIds = recentNews.mapNotNull { news ->
|
||||||
if (news.type == FollowingNewsType.COMMUNITY_POST) news.communityPost?.postId else null
|
if (news.type == FollowingNewsType.COMMUNITY_POST) news.communityPost?.postId else null
|
||||||
}
|
}
|
||||||
val displayContents = if (communityPostIds.isEmpty()) {
|
val displayContents = if (communityPostIds.isEmpty()) {
|
||||||
emptyMap()
|
emptyMap()
|
||||||
} else {
|
} else {
|
||||||
creatorCommunityTranslationService.findDisplayContents(communityPostIds, langContext.lang.code)
|
creatorCommunityTranslationService.findDisplayContents(communityPostIds, lang.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
return HomeFollowing(
|
return HomeFollowing(
|
||||||
followingCreators = queryPort.findFollowingCreators(memberId, FOLLOWING_CREATORS_LIMIT),
|
followingCreators = queryPort.findFollowingCreators(memberId, FOLLOWING_CREATORS_LIMIT, lang),
|
||||||
onAirLives = queryPort.findOnAirLives(
|
onAirLives = queryPort.findOnAirLives(
|
||||||
memberId,
|
memberId,
|
||||||
canViewAdultContent,
|
canViewAdultContent,
|
||||||
effectiveViewerGender,
|
effectiveViewerGender,
|
||||||
isViewerCreator,
|
isViewerCreator,
|
||||||
ON_AIR_LIVES_LIMIT
|
ON_AIR_LIVES_LIMIT,
|
||||||
|
lang
|
||||||
),
|
),
|
||||||
recentChats = emptyList(),
|
recentChats = emptyList(),
|
||||||
monthlySchedules = queryPort.findMonthlySchedules(
|
monthlySchedules = queryPort.findMonthlySchedules(
|
||||||
@@ -55,7 +55,8 @@ class HomeFollowingQueryService(
|
|||||||
effectiveViewerGender,
|
effectiveViewerGender,
|
||||||
isViewerCreator,
|
isViewerCreator,
|
||||||
now,
|
now,
|
||||||
MONTHLY_SCHEDULES_LIMIT
|
MONTHLY_SCHEDULES_LIMIT,
|
||||||
|
lang
|
||||||
),
|
),
|
||||||
recentNews = recentNews.map { news ->
|
recentNews = recentNews.map { news ->
|
||||||
if (news.type != FollowingNewsType.COMMUNITY_POST) return@map news
|
if (news.type != FollowingNewsType.COMMUNITY_POST) return@map news
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ data class HomeFollowingLive(
|
|||||||
val creatorProfileImageUrl: String,
|
val creatorProfileImageUrl: String,
|
||||||
val creatorNickname: String,
|
val creatorNickname: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val startedAtUtc: String
|
val startedAtUtc: String,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HomeFollowingSchedule(
|
data class HomeFollowingSchedule(
|
||||||
@@ -45,7 +46,8 @@ data class HomeFollowingNews(
|
|||||||
val audioContent: HomeFollowingContentNews? = null,
|
val audioContent: HomeFollowingContentNews? = null,
|
||||||
val photoContent: HomeFollowingContentNews? = null,
|
val photoContent: HomeFollowingContentNews? = null,
|
||||||
val contentRanking: HomeFollowingContentRankingNews? = null,
|
val contentRanking: HomeFollowingContentRankingNews? = null,
|
||||||
val communityPost: HomeFollowingCommunityPostNews? = null
|
val communityPost: HomeFollowingCommunityPostNews? = null,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HomeFollowingCreatorRankingNews(
|
data class HomeFollowingCreatorRankingNews(
|
||||||
|
|||||||
+8
-4
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.home.following.port.out
|
package kr.co.vividnext.sodalive.v2.home.following.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreator
|
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreator
|
||||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingLive
|
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingLive
|
||||||
@@ -8,14 +9,15 @@ import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingSchedule
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface HomeFollowingQueryPort {
|
interface HomeFollowingQueryPort {
|
||||||
fun findFollowingCreators(memberId: Long, limit: Int): List<HomeFollowingCreator>
|
fun findFollowingCreators(memberId: Long, limit: Int, lang: Lang): List<HomeFollowingCreator>
|
||||||
|
|
||||||
fun findOnAirLives(
|
fun findOnAirLives(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
effectiveViewerGender: Gender,
|
effectiveViewerGender: Gender,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingLive>
|
): List<HomeFollowingLive>
|
||||||
|
|
||||||
fun findMonthlySchedules(
|
fun findMonthlySchedules(
|
||||||
@@ -24,13 +26,15 @@ interface HomeFollowingQueryPort {
|
|||||||
effectiveViewerGender: Gender,
|
effectiveViewerGender: Gender,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
now: LocalDateTime,
|
now: LocalDateTime,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingSchedule>
|
): List<HomeFollowingSchedule>
|
||||||
|
|
||||||
fun findRecentNews(
|
fun findRecentNews(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
canViewAdultContent: Boolean,
|
canViewAdultContent: Boolean,
|
||||||
nowUtc: LocalDateTime,
|
nowUtc: LocalDateTime,
|
||||||
limit: Int
|
limit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFollowingNews>
|
): List<HomeFollowingNews>
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-5
@@ -1,5 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
|
import com.querydsl.jpa.JPAExpressions
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence.QCreatorRankingSnapshot.creatorRankingSnapshot
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
||||||
@@ -9,7 +15,8 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class DefaultCreatorRankingSnapshotRepository(
|
class DefaultCreatorRankingSnapshotRepository(
|
||||||
private val repository: CreatorRankingSnapshotRepository
|
private val repository: CreatorRankingSnapshotRepository,
|
||||||
|
private val queryFactory: JPAQueryFactory
|
||||||
) : CreatorRankingSnapshotPort {
|
) : CreatorRankingSnapshotPort {
|
||||||
override fun findSnapshotsByAggregationPeriod(
|
override fun findSnapshotsByAggregationPeriod(
|
||||||
aggregationStartAtUtc: LocalDateTime,
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
@@ -31,9 +38,28 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
|
|
||||||
override fun findLatestVisibleSnapshots(
|
override fun findLatestVisibleSnapshots(
|
||||||
rankingType: CreatorRankingType,
|
rankingType: CreatorRankingType,
|
||||||
nowUtc: LocalDateTime
|
nowUtc: LocalDateTime,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorRankingSnapshotRecord> {
|
): List<CreatorRankingSnapshotRecord> {
|
||||||
return repository.findLatestVisibleSnapshots(rankingType.name, nowUtc).map { it.toRecord() }
|
val snapshot = creatorRankingSnapshot
|
||||||
|
val latest = QCreatorRankingSnapshot("latestVisibleCreatorRanking")
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "rankingCreatorName")
|
||||||
|
val displayName = creatorName.displayName.coalesce(snapshot.nickname)
|
||||||
|
return queryFactory
|
||||||
|
.select(snapshot, displayName)
|
||||||
|
.from(snapshot)
|
||||||
|
.leftJoin(member).on(member.id.eq(snapshot.creatorId))
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
|
.where(
|
||||||
|
snapshot.rankingType.eq(rankingType),
|
||||||
|
snapshot.visibleFromAtUtc.eq(
|
||||||
|
JPAExpressions.select(latest.visibleFromAtUtc.max()).from(latest)
|
||||||
|
.where(latest.rankingType.eq(rankingType), latest.visibleFromAtUtc.loe(nowUtc))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(snapshot.rankNo.asc())
|
||||||
|
.fetch()
|
||||||
|
.map { it.get(snapshot)!!.toRecord(it.get(displayName)!!) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findPreviousVisibleSnapshots(
|
override fun findPreviousVisibleSnapshots(
|
||||||
@@ -64,14 +90,14 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
|
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CreatorRankingSnapshot.toRecord(): CreatorRankingSnapshotRecord {
|
private fun CreatorRankingSnapshot.toRecord(displayNickname: String = nickname): CreatorRankingSnapshotRecord {
|
||||||
return CreatorRankingSnapshotRecord(
|
return CreatorRankingSnapshotRecord(
|
||||||
rankingType = rankingType,
|
rankingType = rankingType,
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = nickname,
|
nickname = displayNickname,
|
||||||
profileImageUrl = profileImageUrl,
|
profileImageUrl = profileImageUrl,
|
||||||
rankNo = rankNo,
|
rankNo = rankNo,
|
||||||
finalScore = finalScore,
|
finalScore = finalScore,
|
||||||
|
|||||||
+6
-5
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.application
|
package kr.co.vividnext.sodalive.v2.ranking.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingItem
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingItem
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
||||||
@@ -24,11 +25,11 @@ class CreatorRankingQueryService(
|
|||||||
) {
|
) {
|
||||||
private val log = LoggerFactory.getLogger(javaClass)
|
private val log = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
fun getCreatorRankings(viewerMemberId: Long?): CreatorRankingResult {
|
fun getCreatorRankings(viewerMemberId: Long?, lang: Lang): CreatorRankingResult {
|
||||||
val startedAt = System.currentTimeMillis()
|
val startedAt = System.currentTimeMillis()
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val nowUtc = nowUtc()
|
val nowUtc = nowUtc()
|
||||||
val latestSnapshots = findLatestVisibleSnapshots(nowUtc)
|
val latestSnapshots = findLatestVisibleSnapshots(nowUtc, lang)
|
||||||
val latestItems = latestSnapshots.toRankedItems()
|
val latestItems = latestSnapshots.toRankedItems()
|
||||||
if (latestItems.isEmpty()) {
|
if (latestItems.isEmpty()) {
|
||||||
return@runCatching QueryLogResult(CreatorRankingResult(showRankChange = false, items = emptyList()), 0)
|
return@runCatching QueryLogResult(CreatorRankingResult(showRankChange = false, items = emptyList()), 0)
|
||||||
@@ -69,15 +70,15 @@ class CreatorRankingQueryService(
|
|||||||
}.getOrThrow().result
|
}.getOrThrow().result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findLatestVisibleSnapshots(nowUtc: LocalDateTime): List<CreatorRankingSnapshotRecord> {
|
private fun findLatestVisibleSnapshots(nowUtc: LocalDateTime, lang: Lang): List<CreatorRankingSnapshotRecord> {
|
||||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc)
|
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc, lang)
|
||||||
if (latestSnapshots.isNotEmpty()) return latestSnapshots
|
if (latestSnapshots.isNotEmpty()) return latestSnapshots
|
||||||
|
|
||||||
runCatching { snapshotJobService.refreshLastCompletedWeekByFallback() }
|
runCatching { snapshotJobService.refreshLastCompletedWeekByFallback() }
|
||||||
.onFailure { ex ->
|
.onFailure { ex ->
|
||||||
log.warn("event=creator_ranking_query_fallback_failure error={}", ex.message, ex)
|
log.warn("event=creator_ranking_query_fallback_failure error={}", ex.message, ex)
|
||||||
}
|
}
|
||||||
return snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc)
|
return snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc, lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class QueryLogResult(
|
private data class QueryLogResult(
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.port.out
|
package kr.co.vividnext.sodalive.v2.ranking.port.out
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ interface CreatorRankingSnapshotPort {
|
|||||||
|
|
||||||
fun findLatestVisibleSnapshots(
|
fun findLatestVisibleSnapshots(
|
||||||
rankingType: CreatorRankingType,
|
rankingType: CreatorRankingType,
|
||||||
nowUtc: LocalDateTime
|
nowUtc: LocalDateTime,
|
||||||
|
lang: Lang
|
||||||
): List<CreatorRankingSnapshotRecord>
|
): List<CreatorRankingSnapshotRecord>
|
||||||
|
|
||||||
fun findPreviousVisibleSnapshots(
|
fun findPreviousVisibleSnapshots(
|
||||||
|
|||||||
+56
-22
@@ -32,6 +32,7 @@ import kr.co.vividnext.sodalive.member.QMember.member
|
|||||||
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
import kr.co.vividnext.sodalive.member.block.QBlockMember
|
||||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing
|
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScoreSpec
|
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScoreSpec
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
import kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendedSectionType
|
||||||
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeAiCharacterRecommendationRecord
|
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeAiCharacterRecommendationRecord
|
||||||
@@ -62,22 +63,26 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultLives: Boolean,
|
includeAdultLives: Boolean,
|
||||||
isViewerCreator: Boolean,
|
isViewerCreator: Boolean,
|
||||||
effectiveViewerGender: Gender?
|
effectiveViewerGender: Gender?,
|
||||||
|
lang: Lang
|
||||||
): List<HomeLiveRecommendationRecord> {
|
): List<HomeLiveRecommendationRecord> {
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "homeLiveCreatorName")
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
HomeLiveRecommendationRecord::class.java,
|
HomeLiveRecommendationRecord::class.java,
|
||||||
liveRoom.id,
|
liveRoom.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage,
|
member.profileImage,
|
||||||
liveRoom.title,
|
liveRoom.title,
|
||||||
liveRoom.price,
|
liveRoom.price,
|
||||||
liveRoom.beginDateTime
|
liveRoom.beginDateTime,
|
||||||
|
member.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(liveRoom)
|
.from(liveRoom)
|
||||||
.join(liveRoom.member, member)
|
.join(liveRoom.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
liveRoom.isActive.isTrue,
|
liveRoom.isActive.isTrue,
|
||||||
liveRoom.channelName.isNotNull,
|
liveRoom.channelName.isNotNull,
|
||||||
@@ -138,11 +143,12 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
override fun findRecentlyActiveCreators(
|
override fun findRecentlyActiveCreators(
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultActivities: Boolean
|
includeAdultActivities: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<RecentlyActiveCreatorRecord> {
|
): List<RecentlyActiveCreatorRecord> {
|
||||||
val sql = """
|
val sql = """
|
||||||
select ranked.creator_id,
|
select ranked.creator_id,
|
||||||
ranked.creator_nickname,
|
coalesce(creator_name.display_name, ranked.creator_nickname),
|
||||||
ranked.creator_profile_image,
|
ranked.creator_profile_image,
|
||||||
ranked.activity_type,
|
ranked.activity_type,
|
||||||
ranked.activity_at,
|
ranked.activity_at,
|
||||||
@@ -200,6 +206,9 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
and m.role = 'CREATOR'
|
and m.role = 'CREATOR'
|
||||||
) activities
|
) activities
|
||||||
) ranked
|
) ranked
|
||||||
|
join member display_owner on display_owner.id = ranked.creator_id
|
||||||
|
left join creator_localized_name creator_name on creator_name.member_id = display_owner.id
|
||||||
|
and creator_name.lang = :lang and creator_name.is_active = true and display_owner.role = 'CREATOR'
|
||||||
where ranked.creator_rank = 1
|
where ranked.creator_rank = 1
|
||||||
and ${notBlockedCreatorSql("ranked.creator_id")}
|
and ${notBlockedCreatorSql("ranked.creator_id")}
|
||||||
order by ranked.activity_at desc, ranked.target_sort_id desc
|
order by ranked.activity_at desc, ranked.target_sort_id desc
|
||||||
@@ -210,6 +219,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
.setParameter("liveReplayTheme", LIVE_REPLAY_THEME)
|
.setParameter("liveReplayTheme", LIVE_REPLAY_THEME)
|
||||||
.setParameter("memberId", memberId)
|
.setParameter("memberId", memberId)
|
||||||
.setParameter("includeAdultActivities", includeAdultActivities)
|
.setParameter("includeAdultActivities", includeAdultActivities)
|
||||||
|
.setParameter("lang", lang.name)
|
||||||
.setParameter("limit", limit)
|
.setParameter("limit", limit)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -232,7 +242,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultContents: Boolean
|
includeAdultContents: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<RecentDebutCreatorRecord> {
|
): List<RecentDebutCreatorRecord> {
|
||||||
val sql = """
|
val sql = """
|
||||||
with creator_debut as (
|
with creator_debut as (
|
||||||
@@ -328,9 +339,11 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
group by communication.creator_id
|
group by communication.creator_id
|
||||||
)
|
)
|
||||||
select m.id as creator_id,
|
select m.id as creator_id,
|
||||||
m.nickname as creator_nickname,
|
coalesce(creator_name.display_name, m.nickname) as creator_nickname,
|
||||||
m.profile_image as creator_profile_image
|
m.profile_image as creator_profile_image
|
||||||
from member m
|
from member m
|
||||||
|
left join creator_localized_name creator_name on creator_name.member_id = m.id
|
||||||
|
and creator_name.lang = :lang and creator_name.is_active = true and m.role = 'CREATOR'
|
||||||
join creator_debut cd on cd.creator_id = m.id
|
join creator_debut cd on cd.creator_id = m.id
|
||||||
left join follow_stats fs on fs.creator_id = m.id
|
left join follow_stats fs on fs.creator_id = m.id
|
||||||
left join content_stats cs on cs.creator_id = m.id
|
left join content_stats cs on cs.creator_id = m.id
|
||||||
@@ -354,6 +367,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
|
|
||||||
val query = entityManager.createNativeQuery(sql)
|
val query = entityManager.createNativeQuery(sql)
|
||||||
.setRecommendationQueryParameters(now, limit)
|
.setRecommendationQueryParameters(now, limit)
|
||||||
|
.setParameter("lang", lang.name)
|
||||||
.setParameter("offset", offset)
|
.setParameter("offset", offset)
|
||||||
.setParameter("memberId", memberId)
|
.setParameter("memberId", memberId)
|
||||||
.setParameter("includeAdultContents", includeAdultContents)
|
.setParameter("includeAdultContents", includeAdultContents)
|
||||||
@@ -437,7 +451,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
select ec.content_id as content_id,
|
select ec.content_id as content_id,
|
||||||
m.id as creator_id,
|
m.id as creator_id,
|
||||||
m.nickname as creator_nickname,
|
coalesce(creator_name.display_name, m.nickname) as creator_nickname,
|
||||||
m.profile_image as creator_profile_image,
|
m.profile_image as creator_profile_image,
|
||||||
ec.title as title,
|
ec.title as title,
|
||||||
ec.price as price,
|
ec.price as price,
|
||||||
@@ -447,6 +461,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
ec.is_original_series as is_original_series
|
ec.is_original_series as is_original_series
|
||||||
from eligible_contents ec
|
from eligible_contents ec
|
||||||
join member m on m.id = ec.creator_id
|
join member m on m.id = ec.creator_id
|
||||||
|
left join creator_localized_name creator_name on creator_name.member_id = m.id
|
||||||
|
and creator_name.lang = :lang and creator_name.is_active = true and m.role = 'CREATOR'
|
||||||
join creator_debut cd on cd.creator_id = ec.creator_id
|
join creator_debut cd on cd.creator_id = ec.creator_id
|
||||||
where ec.active_rank = 1
|
where ec.active_rank = 1
|
||||||
and m.is_active = true
|
and m.is_active = true
|
||||||
@@ -468,6 +484,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
|
|
||||||
val query = entityManager.createNativeQuery(sql)
|
val query = entityManager.createNativeQuery(sql)
|
||||||
.setParameter("now", now)
|
.setParameter("now", now)
|
||||||
|
.setParameter("lang", Lang.fromCode(locale).name)
|
||||||
.setParameter("limit", limit)
|
.setParameter("limit", limit)
|
||||||
.setParameter("offset", offset)
|
.setParameter("offset", offset)
|
||||||
.setParameter("memberId", memberId)
|
.setParameter("memberId", memberId)
|
||||||
@@ -832,20 +849,23 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
|
|
||||||
override fun findCheerCreatorRecommendationDetails(
|
override fun findCheerCreatorRecommendationDetails(
|
||||||
creatorIds: List<Long>,
|
creatorIds: List<Long>,
|
||||||
memberId: Long?
|
memberId: Long?,
|
||||||
|
lang: Lang
|
||||||
): List<HomeCheerCreatorRecommendationRecord> {
|
): List<HomeCheerCreatorRecommendationRecord> {
|
||||||
if (creatorIds.isEmpty()) return emptyList()
|
if (creatorIds.isEmpty()) return emptyList()
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "homeCheerCreatorName")
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
Projections.constructor(
|
Projections.constructor(
|
||||||
HomeCheerCreatorRecommendationRecord::class.java,
|
HomeCheerCreatorRecommendationRecord::class.java,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage
|
member.profileImage
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.from(member)
|
.from(member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.where(
|
.where(
|
||||||
member.isActive.isTrue,
|
member.isActive.isTrue,
|
||||||
member.id.`in`(creatorIds),
|
member.id.`in`(creatorIds),
|
||||||
@@ -859,9 +879,11 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
override fun findPopularCommunityRecommendationDetails(
|
override fun findPopularCommunityRecommendationDetails(
|
||||||
communityIds: List<Long>,
|
communityIds: List<Long>,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultCommunities: Boolean
|
includeAdultCommunities: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<HomePopularCommunityRecommendationRecord> {
|
): List<HomePopularCommunityRecommendationRecord> {
|
||||||
if (communityIds.isEmpty()) return emptyList()
|
if (communityIds.isEmpty()) return emptyList()
|
||||||
|
val creatorName = CreatorNameJoin(member, lang, "homeCommunityCreatorName")
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(
|
.select(
|
||||||
@@ -869,7 +891,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
HomePopularCommunityRecommendationRecord::class.java,
|
HomePopularCommunityRecommendationRecord::class.java,
|
||||||
creatorCommunity.id,
|
creatorCommunity.id,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
creatorName.displayName,
|
||||||
member.profileImage,
|
member.profileImage,
|
||||||
creatorCommunity.imagePath,
|
creatorCommunity.imagePath,
|
||||||
creatorCommunity.audioPath,
|
creatorCommunity.audioPath,
|
||||||
@@ -884,6 +906,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
.from(creatorCommunity)
|
.from(creatorCommunity)
|
||||||
.join(creatorCommunity.member, member)
|
.join(creatorCommunity.member, member)
|
||||||
|
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||||
.leftJoin(creatorCommunityLike).on(
|
.leftJoin(creatorCommunityLike).on(
|
||||||
creatorCommunityLike.creatorCommunity.id.eq(creatorCommunity.id),
|
creatorCommunityLike.creatorCommunity.id.eq(creatorCommunity.id),
|
||||||
creatorCommunityLike.isActive.isTrue
|
creatorCommunityLike.isActive.isTrue
|
||||||
@@ -905,6 +928,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
creatorCommunity.id,
|
creatorCommunity.id,
|
||||||
member.id,
|
member.id,
|
||||||
member.nickname,
|
member.nickname,
|
||||||
|
creatorName.name.displayName,
|
||||||
member.profileImage,
|
member.profileImage,
|
||||||
creatorCommunity.imagePath,
|
creatorCommunity.imagePath,
|
||||||
creatorCommunity.audioPath,
|
creatorCommunity.audioPath,
|
||||||
@@ -919,7 +943,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
genreLimit: Int,
|
genreLimit: Int,
|
||||||
creatorLimit: Int
|
creatorLimit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeGenreCreatorRecommendationGroup> {
|
): List<HomeGenreCreatorRecommendationGroup> {
|
||||||
val groups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
val groups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||||
val selectedGenreIds = mutableSetOf<Long>()
|
val selectedGenreIds = mutableSetOf<Long>()
|
||||||
@@ -931,7 +956,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
viewedTargets.forEach { target ->
|
viewedTargets.forEach { target ->
|
||||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||||
selectedGenreIds.add(target.id)
|
selectedGenreIds.add(target.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,7 +966,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
includeAdultGenres = includeAdultGenres,
|
includeAdultGenres = includeAdultGenres,
|
||||||
genreLimit = genreLimit,
|
genreLimit = genreLimit,
|
||||||
creatorLimit = creatorLimit
|
creatorLimit = creatorLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
|
|
||||||
return groups
|
return groups
|
||||||
@@ -993,7 +1019,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
genreLimit: Int,
|
genreLimit: Int,
|
||||||
creatorLimit: Int
|
creatorLimit: Int,
|
||||||
|
lang: Lang
|
||||||
) {
|
) {
|
||||||
val fullTargets = findFallbackGenreRecommendationTargets(
|
val fullTargets = findFallbackGenreRecommendationTargets(
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
@@ -1004,7 +1031,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fullTargets.forEach { target ->
|
fullTargets.forEach { target ->
|
||||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||||
selectedGenreIds.add(target.id)
|
selectedGenreIds.add(target.id)
|
||||||
}
|
}
|
||||||
val partialTargets = findFallbackGenreRecommendationTargets(
|
val partialTargets = findFallbackGenreRecommendationTargets(
|
||||||
@@ -1016,7 +1043,7 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
partialTargets.forEach { target ->
|
partialTargets.forEach { target ->
|
||||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||||
selectedGenreIds.add(target.id)
|
selectedGenreIds.add(target.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1126,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
target: GenreRecommendationTarget,
|
target: GenreRecommendationTarget,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
creatorLimit: Int
|
creatorLimit: Int,
|
||||||
|
lang: Lang
|
||||||
): HomeGenreCreatorRecommendationGroup {
|
): HomeGenreCreatorRecommendationGroup {
|
||||||
return HomeGenreCreatorRecommendationGroup(
|
return HomeGenreCreatorRecommendationGroup(
|
||||||
genreId = target.id,
|
genreId = target.id,
|
||||||
@@ -1109,7 +1137,8 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
genreId = target.id,
|
genreId = target.id,
|
||||||
memberId = memberId,
|
memberId = memberId,
|
||||||
includeAdultGenres = includeAdultGenres,
|
includeAdultGenres = includeAdultGenres,
|
||||||
creatorLimit = creatorLimit
|
creatorLimit = creatorLimit,
|
||||||
|
lang = lang
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1152,11 +1181,12 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
genreId: Long,
|
genreId: Long,
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
creatorLimit: Int
|
creatorLimit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeGenreCreatorRecommendationRecord> {
|
): List<HomeGenreCreatorRecommendationRecord> {
|
||||||
val sql = """
|
val sql = """
|
||||||
select candidates.creator_id,
|
select candidates.creator_id,
|
||||||
candidates.creator_nickname,
|
coalesce(creator_name.display_name, candidates.creator_nickname),
|
||||||
candidates.creator_profile_image
|
candidates.creator_profile_image
|
||||||
from (
|
from (
|
||||||
select m.id as creator_id,
|
select m.id as creator_id,
|
||||||
@@ -1190,12 +1220,16 @@ class DefaultHomeRecommendationQueryRepository(
|
|||||||
)
|
)
|
||||||
group by m.id, m.nickname, m.profile_image
|
group by m.id, m.nickname, m.profile_image
|
||||||
) candidates
|
) candidates
|
||||||
|
join member display_owner on display_owner.id = candidates.creator_id
|
||||||
|
left join creator_localized_name creator_name on creator_name.member_id = display_owner.id
|
||||||
|
and creator_name.lang = :lang and creator_name.is_active = true and display_owner.role = 'CREATOR'
|
||||||
order by rand() asc
|
order by rand() asc
|
||||||
limit :creatorLimit
|
limit :creatorLimit
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val query = entityManager.createNativeQuery(sql)
|
val query = entityManager.createNativeQuery(sql)
|
||||||
.setParameter("genreId", genreId)
|
.setParameter("genreId", genreId)
|
||||||
|
.setParameter("lang", lang.name)
|
||||||
.setParameter("memberId", memberId)
|
.setParameter("memberId", memberId)
|
||||||
.setParameter("includeAdultGenres", includeAdultGenres)
|
.setParameter("includeAdultGenres", includeAdultGenres)
|
||||||
.setParameter("creatorLimit", creatorLimit)
|
.setParameter("creatorLimit", creatorLimit)
|
||||||
|
|||||||
+24
-17
@@ -1,7 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.recommendation.application
|
package kr.co.vividnext.sodalive.v2.recommendation.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
|
||||||
import kr.co.vividnext.sodalive.member.Gender
|
import kr.co.vividnext.sodalive.member.Gender
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.translation.application.CreatorCommunityTranslationService
|
||||||
@@ -33,7 +32,6 @@ class HomeRecommendationQueryService(
|
|||||||
private val cheerCreatorSnapshotFallbackService: CheerCreatorSnapshotFallbackPort? = null,
|
private val cheerCreatorSnapshotFallbackService: CheerCreatorSnapshotFallbackPort? = null,
|
||||||
private val popularCommunitySnapshotFallbackService: PopularCommunitySnapshotFallbackPort? = null,
|
private val popularCommunitySnapshotFallbackService: PopularCommunitySnapshotFallbackPort? = null,
|
||||||
private val clock: Clock = Clock.systemUTC(),
|
private val clock: Clock = Clock.systemUTC(),
|
||||||
private val langContext: LangContext,
|
|
||||||
private val creatorCommunityTranslationService: CreatorCommunityTranslationService
|
private val creatorCommunityTranslationService: CreatorCommunityTranslationService
|
||||||
) {
|
) {
|
||||||
private val snapshotWindowPolicy = RecommendationSnapshotWindowPolicy()
|
private val snapshotWindowPolicy = RecommendationSnapshotWindowPolicy()
|
||||||
@@ -44,7 +42,8 @@ class HomeRecommendationQueryService(
|
|||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultLives: Boolean = false,
|
includeAdultLives: Boolean = false,
|
||||||
isViewerCreator: Boolean = false,
|
isViewerCreator: Boolean = false,
|
||||||
effectiveViewerGender: Gender? = null
|
effectiveViewerGender: Gender? = null,
|
||||||
|
lang: Lang
|
||||||
): List<HomeLiveRecommendationRecord> {
|
): List<HomeLiveRecommendationRecord> {
|
||||||
return queryPort.findLiveRecommendations(
|
return queryPort.findLiveRecommendations(
|
||||||
offset,
|
offset,
|
||||||
@@ -52,7 +51,8 @@ class HomeRecommendationQueryService(
|
|||||||
memberId,
|
memberId,
|
||||||
includeAdultLives,
|
includeAdultLives,
|
||||||
isViewerCreator,
|
isViewerCreator,
|
||||||
effectiveViewerGender
|
effectiveViewerGender,
|
||||||
|
lang
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +68,10 @@ class HomeRecommendationQueryService(
|
|||||||
fun findRecentlyActiveCreators(
|
fun findRecentlyActiveCreators(
|
||||||
limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT,
|
limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultActivities: Boolean = false
|
includeAdultActivities: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<RecentlyActiveCreatorRecord> {
|
): List<RecentlyActiveCreatorRecord> {
|
||||||
return queryPort.findRecentlyActiveCreators(limit, memberId, includeAdultActivities)
|
return queryPort.findRecentlyActiveCreators(limit, memberId, includeAdultActivities, lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findRecentDebutCreators(
|
fun findRecentDebutCreators(
|
||||||
@@ -78,9 +79,10 @@ class HomeRecommendationQueryService(
|
|||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Int = DEFAULT_RECENT_DEBUT_CREATOR_LIMIT,
|
limit: Int = DEFAULT_RECENT_DEBUT_CREATOR_LIMIT,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultContents: Boolean = false
|
includeAdultContents: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<RecentDebutCreatorRecord> {
|
): List<RecentDebutCreatorRecord> {
|
||||||
return queryPort.findRecentDebutCreators(now, offset, limit, memberId, includeAdultContents)
|
return queryPort.findRecentDebutCreators(now, offset, limit, memberId, includeAdultContents, lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findFirstAudioContents(
|
fun findFirstAudioContents(
|
||||||
@@ -88,9 +90,10 @@ class HomeRecommendationQueryService(
|
|||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Int = DEFAULT_FIRST_AUDIO_CONTENT_LIMIT,
|
limit: Int = DEFAULT_FIRST_AUDIO_CONTENT_LIMIT,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultContents: Boolean = false
|
includeAdultContents: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<HomeFirstAudioContentRecord> {
|
): List<HomeFirstAudioContentRecord> {
|
||||||
return queryPort.findFirstAudioContents(now, offset, limit, memberId, includeAdultContents, langContext.lang.code)
|
return queryPort.findFirstAudioContents(now, offset, limit, memberId, includeAdultContents, lang.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findAiCharacterRecommendations(
|
fun findAiCharacterRecommendations(
|
||||||
@@ -128,10 +131,11 @@ class HomeRecommendationQueryService(
|
|||||||
|
|
||||||
fun findCheerCreatorRecommendations(
|
fun findCheerCreatorRecommendations(
|
||||||
limit: Int = DEFAULT_CHEER_CREATOR_LIMIT,
|
limit: Int = DEFAULT_CHEER_CREATOR_LIMIT,
|
||||||
memberId: Long? = null
|
memberId: Long? = null,
|
||||||
|
lang: Lang
|
||||||
): List<HomeCheerCreatorRecommendationRecord> {
|
): List<HomeCheerCreatorRecommendationRecord> {
|
||||||
val snapshots = findCheerCreatorSnapshotsWithFallback()
|
val snapshots = findCheerCreatorSnapshotsWithFallback()
|
||||||
val detailsById = queryPort.findCheerCreatorRecommendationDetails(snapshots.map { it.targetId }, memberId)
|
val detailsById = queryPort.findCheerCreatorRecommendationDetails(snapshots.map { it.targetId }, memberId, lang)
|
||||||
.associateBy { it.creatorId }
|
.associateBy { it.creatorId }
|
||||||
|
|
||||||
return snapshots.mapNotNull { detailsById[it.targetId] }.take(limit)
|
return snapshots.mapNotNull { detailsById[it.targetId] }.take(limit)
|
||||||
@@ -155,13 +159,15 @@ class HomeRecommendationQueryService(
|
|||||||
fun findPopularCommunityRecommendations(
|
fun findPopularCommunityRecommendations(
|
||||||
limit: Int = DEFAULT_POPULAR_COMMUNITY_LIMIT,
|
limit: Int = DEFAULT_POPULAR_COMMUNITY_LIMIT,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultCommunities: Boolean = false
|
includeAdultCommunities: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<HomePopularCommunityRecommendationRecord> {
|
): List<HomePopularCommunityRecommendationRecord> {
|
||||||
val snapshots = findPopularCommunitySnapshotsWithFallback()
|
val snapshots = findPopularCommunitySnapshotsWithFallback()
|
||||||
val detailsById = queryPort.findPopularCommunityRecommendationDetails(
|
val detailsById = queryPort.findPopularCommunityRecommendationDetails(
|
||||||
snapshots.map { it.targetId },
|
snapshots.map { it.targetId },
|
||||||
memberId,
|
memberId,
|
||||||
includeAdultCommunities
|
includeAdultCommunities,
|
||||||
|
lang
|
||||||
)
|
)
|
||||||
.associateBy { it.communityId }
|
.associateBy { it.communityId }
|
||||||
val selectedCreatorIds = mutableSetOf<Long>()
|
val selectedCreatorIds = mutableSetOf<Long>()
|
||||||
@@ -173,7 +179,7 @@ class HomeRecommendationQueryService(
|
|||||||
|
|
||||||
val displayContents = creatorCommunityTranslationService.findDisplayContents(
|
val displayContents = creatorCommunityTranslationService.findDisplayContents(
|
||||||
recommendations.map { it.communityId },
|
recommendations.map { it.communityId },
|
||||||
langContext.lang.code
|
lang.code
|
||||||
)
|
)
|
||||||
return recommendations.map { recommendation ->
|
return recommendations.map { recommendation ->
|
||||||
displayContents[recommendation.communityId]
|
displayContents[recommendation.communityId]
|
||||||
@@ -201,13 +207,14 @@ class HomeRecommendationQueryService(
|
|||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
genreLimit: Int = DEFAULT_GENRE_CREATOR_GENRE_LIMIT,
|
genreLimit: Int = DEFAULT_GENRE_CREATOR_GENRE_LIMIT,
|
||||||
creatorLimit: Int = DEFAULT_GENRE_CREATOR_CREATOR_LIMIT
|
creatorLimit: Int = DEFAULT_GENRE_CREATOR_CREATOR_LIMIT,
|
||||||
|
lang: Lang
|
||||||
): List<HomeGenreCreatorRecommendationGroup> {
|
): List<HomeGenreCreatorRecommendationGroup> {
|
||||||
val selectedCreatorIds = mutableSetOf<Long>()
|
val selectedCreatorIds = mutableSetOf<Long>()
|
||||||
val partialFallbackGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
val partialFallbackGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||||
val selectedGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
val selectedGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||||
|
|
||||||
queryPort.findGenreCreatorRecommendations(memberId, includeAdultGenres, genreLimit, creatorLimit)
|
queryPort.findGenreCreatorRecommendations(memberId, includeAdultGenres, genreLimit, creatorLimit, lang)
|
||||||
.forEach { group ->
|
.forEach { group ->
|
||||||
if (selectedGroups.size >= genreLimit) return@forEach
|
if (selectedGroups.size >= genreLimit) return@forEach
|
||||||
|
|
||||||
|
|||||||
+14
-7
@@ -12,7 +12,8 @@ interface HomeRecommendationQueryPort {
|
|||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultLives: Boolean = false,
|
includeAdultLives: Boolean = false,
|
||||||
isViewerCreator: Boolean = false,
|
isViewerCreator: Boolean = false,
|
||||||
effectiveViewerGender: Gender? = null
|
effectiveViewerGender: Gender? = null,
|
||||||
|
lang: Lang
|
||||||
): List<HomeLiveRecommendationRecord>
|
): List<HomeLiveRecommendationRecord>
|
||||||
|
|
||||||
fun findHomeBanners(
|
fun findHomeBanners(
|
||||||
@@ -25,7 +26,8 @@ interface HomeRecommendationQueryPort {
|
|||||||
fun findRecentlyActiveCreators(
|
fun findRecentlyActiveCreators(
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultActivities: Boolean = false
|
includeAdultActivities: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<RecentlyActiveCreatorRecord>
|
): List<RecentlyActiveCreatorRecord>
|
||||||
|
|
||||||
fun findRecentDebutCreators(
|
fun findRecentDebutCreators(
|
||||||
@@ -33,7 +35,8 @@ interface HomeRecommendationQueryPort {
|
|||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultContents: Boolean = false
|
includeAdultContents: Boolean = false,
|
||||||
|
lang: Lang
|
||||||
): List<RecentDebutCreatorRecord>
|
): List<RecentDebutCreatorRecord>
|
||||||
|
|
||||||
fun findFirstAudioContents(
|
fun findFirstAudioContents(
|
||||||
@@ -72,20 +75,23 @@ interface HomeRecommendationQueryPort {
|
|||||||
|
|
||||||
fun findCheerCreatorRecommendationDetails(
|
fun findCheerCreatorRecommendationDetails(
|
||||||
creatorIds: List<Long>,
|
creatorIds: List<Long>,
|
||||||
memberId: Long? = null
|
memberId: Long? = null,
|
||||||
|
lang: Lang
|
||||||
): List<HomeCheerCreatorRecommendationRecord>
|
): List<HomeCheerCreatorRecommendationRecord>
|
||||||
|
|
||||||
fun findPopularCommunityRecommendationDetails(
|
fun findPopularCommunityRecommendationDetails(
|
||||||
communityIds: List<Long>,
|
communityIds: List<Long>,
|
||||||
memberId: Long? = null,
|
memberId: Long? = null,
|
||||||
includeAdultCommunities: Boolean
|
includeAdultCommunities: Boolean,
|
||||||
|
lang: Lang
|
||||||
): List<HomePopularCommunityRecommendationRecord>
|
): List<HomePopularCommunityRecommendationRecord>
|
||||||
|
|
||||||
fun findGenreCreatorRecommendations(
|
fun findGenreCreatorRecommendations(
|
||||||
memberId: Long?,
|
memberId: Long?,
|
||||||
includeAdultGenres: Boolean,
|
includeAdultGenres: Boolean,
|
||||||
genreLimit: Int,
|
genreLimit: Int,
|
||||||
creatorLimit: Int
|
creatorLimit: Int,
|
||||||
|
lang: Lang
|
||||||
): List<HomeGenreCreatorRecommendationGroup>
|
): List<HomeGenreCreatorRecommendationGroup>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +101,8 @@ data class HomeLiveRecommendationRecord(
|
|||||||
val creatorProfileImage: String?,
|
val creatorProfileImage: String?,
|
||||||
val title: String,
|
val title: String,
|
||||||
val price: Int,
|
val price: Int,
|
||||||
val beginDateTime: LocalDateTime
|
val beginDateTime: LocalDateTime,
|
||||||
|
val creatorMemberId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
data class HomeBannerRecommendationRecord(
|
data class HomeBannerRecommendationRecord(
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.dto
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessageType
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
data class UserCreatorChatMessageRow(
|
||||||
|
val messageId: Long,
|
||||||
|
val messageType: UserCreatorChatMessageType,
|
||||||
|
val createdAt: LocalDateTime?,
|
||||||
|
val textMessage: String?,
|
||||||
|
val voiceMessage: String?,
|
||||||
|
val senderId: Long,
|
||||||
|
val senderNickname: String,
|
||||||
|
val senderProfileImage: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UserCreatorChatOpponentProfile(val nickname: String, val profileImage: String?)
|
||||||
+29
-9
@@ -1,23 +1,43 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessage
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessage
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatRoom
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatRoom
|
||||||
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessageRow
|
||||||
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.data.jpa.repository.Query
|
||||||
|
import org.springframework.data.repository.query.Param
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface UserCreatorChatMessageRepository : JpaRepository<UserCreatorChatMessage, Long> {
|
interface UserCreatorChatMessageRepository : JpaRepository<UserCreatorChatMessage, Long> {
|
||||||
fun findByChatRoomAndIsActiveTrueOrderByIdDesc(
|
@Query(
|
||||||
chatRoom: UserCreatorChatRoom,
|
"""
|
||||||
|
select new kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessageRow(
|
||||||
|
m.id, m.messageType, m.createdAt, m.textMessage, m.voiceMessage,
|
||||||
|
sender.id, coalesce(senderName.displayName, sender.nickname), sender.profileImage
|
||||||
|
)
|
||||||
|
from UserCreatorChatMessage m
|
||||||
|
join m.participant participant
|
||||||
|
join participant.member sender
|
||||||
|
left join CreatorLocalizedNameEntity senderName
|
||||||
|
on senderName.memberId = sender.id
|
||||||
|
and senderName.lang = :lang
|
||||||
|
and senderName.isActive = true
|
||||||
|
and sender.role = kr.co.vividnext.sodalive.member.MemberRole.CREATOR
|
||||||
|
where m.chatRoom = :chatRoom
|
||||||
|
and m.isActive = true
|
||||||
|
and (:cursor is null or m.id < :cursor)
|
||||||
|
order by m.id desc
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun findMessageHistory(
|
||||||
|
@Param("chatRoom") chatRoom: UserCreatorChatRoom,
|
||||||
|
@Param("cursor") cursor: Long?,
|
||||||
|
@Param("lang") lang: Lang,
|
||||||
pageable: Pageable
|
pageable: Pageable
|
||||||
): List<UserCreatorChatMessage>
|
): List<UserCreatorChatMessageRow>
|
||||||
|
|
||||||
fun findByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(
|
|
||||||
chatRoom: UserCreatorChatRoom,
|
|
||||||
id: Long,
|
|
||||||
pageable: Pageable
|
|
||||||
): List<UserCreatorChatMessage>
|
|
||||||
|
|
||||||
fun existsByChatRoomAndIsActiveTrueAndIdLessThan(chatRoom: UserCreatorChatRoom, id: Long): Boolean
|
fun existsByChatRoomAndIsActiveTrueAndIdLessThan(chatRoom: UserCreatorChatRoom, id: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatParticipant
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatParticipant
|
||||||
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatOpponentProfile
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.data.repository.query.Param
|
import org.springframework.data.repository.query.Param
|
||||||
@@ -33,4 +35,27 @@ interface UserCreatorChatParticipantRepository : JpaRepository<UserCreatorChatPa
|
|||||||
@Param("roomId") roomId: Long,
|
@Param("roomId") roomId: Long,
|
||||||
@Param("memberId") memberId: Long
|
@Param("memberId") memberId: Long
|
||||||
): UserCreatorChatParticipant?
|
): UserCreatorChatParticipant?
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
select new kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatOpponentProfile(
|
||||||
|
coalesce(opponentName.displayName, opponent.nickname), opponent.profileImage
|
||||||
|
)
|
||||||
|
from UserCreatorChatParticipant p
|
||||||
|
join p.member opponent
|
||||||
|
left join CreatorLocalizedNameEntity opponentName
|
||||||
|
on opponentName.memberId = opponent.id
|
||||||
|
and opponentName.lang = :lang
|
||||||
|
and opponentName.isActive = true
|
||||||
|
and opponent.role = kr.co.vividnext.sodalive.member.MemberRole.CREATOR
|
||||||
|
where p.isActive = true
|
||||||
|
and p.chatRoom.id = :roomId
|
||||||
|
and opponent.id <> :memberId
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
fun findActiveOpponentProfile(
|
||||||
|
@Param("roomId") roomId: Long,
|
||||||
|
@Param("memberId") memberId: Long,
|
||||||
|
@Param("lang") lang: Lang
|
||||||
|
): UserCreatorChatOpponentProfile?
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.repository
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListQueryDto
|
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListQueryDto
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatRoom
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatRoom
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
@@ -41,7 +42,7 @@ interface UserCreatorChatRoomRepository : JpaRepository<UserCreatorChatRoom, Lon
|
|||||||
select new kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListQueryDto(
|
select new kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListQueryDto(
|
||||||
r.id,
|
r.id,
|
||||||
'DM',
|
'DM',
|
||||||
opponent.member.nickname,
|
coalesce(opponentName.displayName, opponentMember.nickname),
|
||||||
opponent.member.profileImage,
|
opponent.member.profileImage,
|
||||||
m.textMessage,
|
m.textMessage,
|
||||||
str(m.messageType),
|
str(m.messageType),
|
||||||
@@ -51,6 +52,12 @@ interface UserCreatorChatRoomRepository : JpaRepository<UserCreatorChatRoom, Lon
|
|||||||
join r.participants mine
|
join r.participants mine
|
||||||
join r.participants opponent
|
join r.participants opponent
|
||||||
join r.messages m
|
join r.messages m
|
||||||
|
join opponent.member opponentMember
|
||||||
|
left join CreatorLocalizedNameEntity opponentName
|
||||||
|
on opponentName.memberId = opponentMember.id
|
||||||
|
and opponentName.lang = :lang
|
||||||
|
and opponentName.isActive = true
|
||||||
|
and opponentMember.role = kr.co.vividnext.sodalive.member.MemberRole.CREATOR
|
||||||
where r.isActive = true
|
where r.isActive = true
|
||||||
and mine.isActive = true
|
and mine.isActive = true
|
||||||
and mine.member.id = :memberId
|
and mine.member.id = :memberId
|
||||||
@@ -90,6 +97,7 @@ interface UserCreatorChatRoomRepository : JpaRepository<UserCreatorChatRoom, Lon
|
|||||||
@Param("cursorChatType") cursorChatType: String?,
|
@Param("cursorChatType") cursorChatType: String?,
|
||||||
@Param("cursorRoomId") cursorRoomId: Long?,
|
@Param("cursorRoomId") cursorRoomId: Long?,
|
||||||
@Param("followedCreatorsOnly") followedCreatorsOnly: Boolean,
|
@Param("followedCreatorsOnly") followedCreatorsOnly: Boolean,
|
||||||
|
@Param("lang") lang: Lang,
|
||||||
pageable: Pageable
|
pageable: Pageable
|
||||||
): List<ChatRoomListQueryDto>
|
): List<ChatRoomListQueryDto>
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-10
@@ -8,11 +8,14 @@ import kr.co.vividnext.sodalive.fcm.FcmDeepLinkValue
|
|||||||
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
import kr.co.vividnext.sodalive.fcm.FcmEvent
|
||||||
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
import kr.co.vividnext.sodalive.fcm.FcmEventType
|
||||||
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
import kr.co.vividnext.sodalive.fcm.notification.PushNotificationCategory
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberKind
|
import kr.co.vividnext.sodalive.member.MemberKind
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessage
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessage
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessageType
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessageType
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatParticipant
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatParticipant
|
||||||
@@ -21,6 +24,7 @@ import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.CreateUserCreatorChatRoom
|
|||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.SendUserCreatorChatMessageResponse
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.SendUserCreatorChatMessageResponse
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.SendUserCreatorVoiceMessageRequest
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.SendUserCreatorVoiceMessageRequest
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessageItemDto
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessageItemDto
|
||||||
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessageRow
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessagesPageResponse
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatMessagesPageResponse
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatRoomOpenResponse
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.dto.UserCreatorChatRoomOpenResponse
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatMessageRepository
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatMessageRepository
|
||||||
@@ -52,6 +56,8 @@ class UserCreatorChatService(
|
|||||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService,
|
||||||
|
private val langContext: LangContext,
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.bucket}")
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
private val bucket: String,
|
private val bucket: String,
|
||||||
@@ -80,7 +86,7 @@ class UserCreatorChatService(
|
|||||||
fun openRoom(member: Member, roomId: Long, limit: Int = 20): UserCreatorChatRoomOpenResponse {
|
fun openRoom(member: Member, roomId: Long, limit: Int = 20): UserCreatorChatRoomOpenResponse {
|
||||||
val room = findRoom(roomId)
|
val room = findRoom(roomId)
|
||||||
requireParticipant(roomId, member.id!!)
|
requireParticipant(roomId, member.id!!)
|
||||||
val opponent = participantRepository.findActiveOpponent(roomId, member.id!!)?.member
|
val opponent = participantRepository.findActiveOpponentProfile(roomId, member.id!!, langContext.lang)
|
||||||
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
?: throw SodaException(messageKey = "chat.room.invalid_access")
|
||||||
val opponentProfilePath = opponent.profileImage ?: "profile/default-profile.png"
|
val opponentProfilePath = opponent.profileImage ?: "profile/default-profile.png"
|
||||||
val page = getMessages(member, roomId, cursor = null, limit = limit)
|
val page = getMessages(member, roomId, cursor = null, limit = limit)
|
||||||
@@ -98,12 +104,8 @@ class UserCreatorChatService(
|
|||||||
val room = findRoom(roomId)
|
val room = findRoom(roomId)
|
||||||
requireParticipant(roomId, member.id!!)
|
requireParticipant(roomId, member.id!!)
|
||||||
val pageable = PageRequest.of(0, limit.coerceIn(1, 100))
|
val pageable = PageRequest.of(0, limit.coerceIn(1, 100))
|
||||||
val fetched = if (cursor != null) {
|
val fetched = messageRepository.findMessageHistory(room, cursor, langContext.lang, pageable)
|
||||||
messageRepository.findByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(room, cursor, pageable)
|
val nextCursor = fetched.minOfOrNull { it.messageId }
|
||||||
} else {
|
|
||||||
messageRepository.findByChatRoomAndIsActiveTrueOrderByIdDesc(room, pageable)
|
|
||||||
}
|
|
||||||
val nextCursor = fetched.minByOrNull { it.id ?: Long.MAX_VALUE }?.id
|
|
||||||
val hasMore = nextCursor?.let { messageRepository.existsByChatRoomAndIsActiveTrueAndIdLessThan(room, it) } ?: false
|
val hasMore = nextCursor?.let { messageRepository.existsByChatRoomAndIsActiveTrueAndIdLessThan(room, it) } ?: false
|
||||||
return UserCreatorChatMessagesPageResponse(
|
return UserCreatorChatMessagesPageResponse(
|
||||||
messages = fetched.sortedBy { it.createdAt }.map { toMessageItemDto(it, member) },
|
messages = fetched.sortedBy { it.createdAt }.map { toMessageItemDto(it, member) },
|
||||||
@@ -113,19 +115,25 @@ class UserCreatorChatService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun sendTextMessageByWebSocket(memberId: Long, roomId: Long, textMessage: String): UserCreatorChatMessageItemDto {
|
fun sendTextMessageByWebSocket(
|
||||||
|
memberId: Long,
|
||||||
|
roomId: Long,
|
||||||
|
textMessage: String,
|
||||||
|
lang: Lang
|
||||||
|
): UserCreatorChatMessageItemDto {
|
||||||
if (textMessage.isBlank()) throw SodaException(messageKey = "common.error.invalid_request")
|
if (textMessage.isBlank()) throw SodaException(messageKey = "common.error.invalid_request")
|
||||||
val senderParticipant = validateParticipant(roomId, memberId)
|
val senderParticipant = validateParticipant(roomId, memberId)
|
||||||
val sender = senderParticipant.member
|
val sender = senderParticipant.member
|
||||||
val context = resolveSendContext(sender, roomId)
|
val context = resolveSendContext(sender, roomId)
|
||||||
val message = saveTextMessage(context, textMessage)
|
val message = saveTextMessage(context, textMessage)
|
||||||
val senderMessage = toMessageItemDto(message, sender)
|
val senderMessage = toMessageItemDto(message, sender)
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOf(sender.id!!), lang)
|
||||||
val opponent = context.opponentParticipant.member
|
val opponent = context.opponentParticipant.member
|
||||||
val deliveredRealtime = deliverRealtime(message, opponent)
|
val deliveredRealtime = deliverRealtime(message, opponent)
|
||||||
if (!deliveredRealtime) {
|
if (!deliveredRealtime) {
|
||||||
publishMessagePush(message, sender, opponent)
|
publishMessagePush(message, sender, opponent)
|
||||||
}
|
}
|
||||||
return senderMessage
|
return senderMessage.copy(senderNickname = names[sender.id] ?: senderMessage.senderNickname)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -185,7 +193,9 @@ class UserCreatorChatService(
|
|||||||
opponentParticipant: UserCreatorChatParticipant
|
opponentParticipant: UserCreatorChatParticipant
|
||||||
): SendUserCreatorChatMessageResponse {
|
): SendUserCreatorChatMessageResponse {
|
||||||
val opponent = opponentParticipant.member
|
val opponent = opponentParticipant.member
|
||||||
val item = toMessageItemDto(message, member)
|
val neutralItem = toMessageItemDto(message, member)
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOf(neutralItem.senderId), langContext.lang)
|
||||||
|
val item = neutralItem.copy(senderNickname = names[neutralItem.senderId] ?: neutralItem.senderNickname)
|
||||||
val deliveredRealtime = deliverRealtime(message, opponent)
|
val deliveredRealtime = deliverRealtime(message, opponent)
|
||||||
if (deliveredRealtime) {
|
if (deliveredRealtime) {
|
||||||
return SendUserCreatorChatMessageResponse(message = item, deliveredRealtime = true, pushSent = false)
|
return SendUserCreatorChatMessageResponse(message = item, deliveredRealtime = true, pushSent = false)
|
||||||
@@ -239,6 +249,7 @@ class UserCreatorChatService(
|
|||||||
messageKey = messageKey,
|
messageKey = messageKey,
|
||||||
senderMemberId = sender.id,
|
senderMemberId = sender.id,
|
||||||
args = listOf(sender.nickname),
|
args = listOf(sender.nickname),
|
||||||
|
nameArgumentMemberIds = mapOf(0 to sender.id!!),
|
||||||
recipients = listOf(opponent.id!!),
|
recipients = listOf(opponent.id!!),
|
||||||
deepLinkValue = FcmDeepLinkValue.CHAT,
|
deepLinkValue = FcmDeepLinkValue.CHAT,
|
||||||
deepLinkId = message.chatRoom.id
|
deepLinkId = message.chatRoom.id
|
||||||
@@ -298,6 +309,21 @@ class UserCreatorChatService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toMessageItemDto(message: UserCreatorChatMessageRow, member: Member): UserCreatorChatMessageItemDto {
|
||||||
|
val profilePath = message.senderProfileImage ?: "profile/default-profile.png"
|
||||||
|
return UserCreatorChatMessageItemDto(
|
||||||
|
messageId = message.messageId,
|
||||||
|
messageType = message.messageType.name,
|
||||||
|
mine = message.senderId == member.id,
|
||||||
|
createdAt = message.createdAt?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli() ?: 0L,
|
||||||
|
textMessage = message.textMessage,
|
||||||
|
voiceMessageUrl = message.voiceMessage?.let { "$cloudFrontHost/$it" },
|
||||||
|
senderId = message.senderId,
|
||||||
|
senderNickname = message.senderNickname,
|
||||||
|
senderProfileImageUrl = "$cloudFrontHost/$profilePath"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private data class SendContext(
|
private data class SendContext(
|
||||||
val room: UserCreatorChatRoom,
|
val room: UserCreatorChatRoom,
|
||||||
val senderParticipant: UserCreatorChatParticipant,
|
val senderParticipant: UserCreatorChatParticipant,
|
||||||
|
|||||||
+23
-3
@@ -1,6 +1,9 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.v2.creatorname.application.CreatorLocalizedNameService
|
||||||
import org.springframework.data.redis.connection.Message
|
import org.springframework.data.redis.connection.Message
|
||||||
import org.springframework.data.redis.connection.MessageListener
|
import org.springframework.data.redis.connection.MessageListener
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate
|
import org.springframework.data.redis.core.StringRedisTemplate
|
||||||
@@ -16,7 +19,8 @@ class UserCreatorChatRoomMessageBroker(
|
|||||||
private val stringRedisTemplate: StringRedisTemplate,
|
private val stringRedisTemplate: StringRedisTemplate,
|
||||||
private val sessionRegistry: UserCreatorChatWebSocketSessionRegistry,
|
private val sessionRegistry: UserCreatorChatWebSocketSessionRegistry,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
listenerContainer: RedisMessageListenerContainer
|
listenerContainer: RedisMessageListenerContainer,
|
||||||
|
private val localizedNameService: CreatorLocalizedNameService
|
||||||
) : MessageListener {
|
) : MessageListener {
|
||||||
init {
|
init {
|
||||||
listenerContainer.addMessageListener(this, ChannelTopic(ROOM_CHANNEL))
|
listenerContainer.addMessageListener(this, ChannelTopic(ROOM_CHANNEL))
|
||||||
@@ -36,9 +40,25 @@ class UserCreatorChatRoomMessageBroker(
|
|||||||
String(message.body, StandardCharsets.UTF_8),
|
String(message.body, StandardCharsets.UTF_8),
|
||||||
UserCreatorChatRoomPublishedMessage::class.java
|
UserCreatorChatRoomPublishedMessage::class.java
|
||||||
)
|
)
|
||||||
sessionRegistry.findSessions(published.roomId, published.memberId)
|
val envelope = objectMapper.readTree(published.payload)
|
||||||
|
val senderId = envelope.path("payload").path("senderId").takeIf { it.isIntegralNumber }?.asLong()
|
||||||
|
val sessions = sessionRegistry.findSessions(published.roomId, published.memberId)
|
||||||
.filter { session -> session.isOpen }
|
.filter { session -> session.isOpen }
|
||||||
.forEach { session -> sendMessage(session, published.payload) }
|
sessions.groupBy { it.attributes[UserCreatorChatWebSocketAuthInterceptor.LANG_ATTRIBUTE] as? Lang ?: Lang.KO }
|
||||||
|
.forEach { (lang, languageSessions) ->
|
||||||
|
val names = localizedNameService.findLocalizedNames(listOfNotNull(senderId), lang)
|
||||||
|
val nickname = senderId?.let { names[it] }
|
||||||
|
languageSessions.forEach { session ->
|
||||||
|
val payload = if (nickname != null) {
|
||||||
|
val localized = envelope.deepCopy<ObjectNode>()
|
||||||
|
(localized["payload"] as ObjectNode).put("senderNickname", nickname)
|
||||||
|
objectMapper.writeValueAsString(localized)
|
||||||
|
} else {
|
||||||
|
published.payload
|
||||||
|
}
|
||||||
|
sendMessage(session, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMessage(session: WebSocketSession, payload: String) {
|
private fun sendMessage(session: WebSocketSession, payload: String) {
|
||||||
|
|||||||
+4
@@ -1,8 +1,10 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.jwt.JwtFilter
|
import kr.co.vividnext.sodalive.jwt.JwtFilter
|
||||||
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
import kr.co.vividnext.sodalive.jwt.TokenProvider
|
||||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||||
|
import org.springframework.http.HttpHeaders
|
||||||
import org.springframework.http.server.ServerHttpRequest
|
import org.springframework.http.server.ServerHttpRequest
|
||||||
import org.springframework.http.server.ServerHttpResponse
|
import org.springframework.http.server.ServerHttpResponse
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
@@ -35,6 +37,7 @@ class UserCreatorChatWebSocketAuthInterceptor(
|
|||||||
|
|
||||||
attributes[MEMBER_ID_ATTRIBUTE] = memberId
|
attributes[MEMBER_ID_ATTRIBUTE] = memberId
|
||||||
attributes[AUTHENTICATION_ATTRIBUTE] = authentication
|
attributes[AUTHENTICATION_ATTRIBUTE] = authentication
|
||||||
|
attributes[LANG_ATTRIBUTE] = Lang.fromAcceptLanguage(request.headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +61,7 @@ class UserCreatorChatWebSocketAuthInterceptor(
|
|||||||
companion object {
|
companion object {
|
||||||
const val MEMBER_ID_ATTRIBUTE = "memberId"
|
const val MEMBER_ID_ATTRIBUTE = "memberId"
|
||||||
const val AUTHENTICATION_ATTRIBUTE = "authentication"
|
const val AUTHENTICATION_ATTRIBUTE = "authentication"
|
||||||
|
const val LANG_ATTRIBUTE = "lang"
|
||||||
private const val BEARER_PREFIX = "Bearer "
|
private const val BEARER_PREFIX = "Bearer "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService
|
import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.socket.CloseStatus
|
import org.springframework.web.socket.CloseStatus
|
||||||
@@ -60,7 +61,8 @@ class UserCreatorChatWebSocketHandler(
|
|||||||
val message = service.sendTextMessageByWebSocket(
|
val message = service.sendTextMessageByWebSocket(
|
||||||
memberId = memberId(session),
|
memberId = memberId(session),
|
||||||
roomId = request.roomId,
|
roomId = request.roomId,
|
||||||
textMessage = textMessage
|
textMessage = textMessage,
|
||||||
|
lang = session.attributes[UserCreatorChatWebSocketAuthInterceptor.LANG_ATTRIBUTE] as? Lang ?: Lang.KO
|
||||||
)
|
)
|
||||||
sendEnvelope(session, UserCreatorChatWebSocketMessageType.SEND_ACK, request.requestId, request.roomId, message)
|
sendEnvelope(session, UserCreatorChatWebSocketMessageType.SEND_ACK, request.requestId, request.roomId, message)
|
||||||
} catch (e: SodaException) {
|
} catch (e: SodaException) {
|
||||||
|
|||||||
+10
-3
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.content.all.adapter.`in`.web
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
||||||
@@ -14,6 +15,7 @@ import kr.co.vividnext.sodalive.v2.api.content.all.application.MainContentAllFac
|
|||||||
import kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponse
|
import kr.co.vividnext.sodalive.v2.api.content.all.dto.MainContentAllTabResponse
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllType
|
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllType
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -41,6 +43,11 @@ class MainContentAllControllerTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var langContext: LangContext
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpLanguage() {
|
||||||
|
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||||
|
}
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var sodaMessageSource: SodaMessageSource
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
@@ -57,7 +64,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
|||||||
@DisplayName("전체 탭 조회는 비회원에게 200 OK와 기본 응답을 반환한다")
|
@DisplayName("전체 탭 조회는 비회원에게 200 OK와 기본 응답을 반환한다")
|
||||||
fun shouldAllowAnonymousAndUseDefaultType() {
|
fun shouldAllowAnonymousAndUseDefaultType() {
|
||||||
Mockito.doReturn(response(MainContentAllType.AUDIO, ContentSort.LATEST)).`when`(facade)
|
Mockito.doReturn(response(MainContentAllType.AUDIO, ContentSort.LATEST)).`when`(facade)
|
||||||
.getContents(null, null, null, null, null, null)
|
.getContents(null, null, null, null, null, null, Lang.KO)
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v2/audio/contents"))
|
mockMvc.perform(get("/api/v2/audio/contents"))
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
@@ -76,7 +83,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
|||||||
role = MemberRole.USER
|
role = MemberRole.USER
|
||||||
).apply { id = 10L }
|
).apply { id = 10L }
|
||||||
Mockito.doReturn(response(MainContentAllType.SERIES, ContentSort.POPULAR)).`when`(facade)
|
Mockito.doReturn(response(MainContentAllType.SERIES, ContentSort.POPULAR)).`when`(facade)
|
||||||
.getContents("SERIES", "POPULAR", "MON", 1, 30, member)
|
.getContents("SERIES", "POPULAR", "MON", 1, 30, member, Lang.KO)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
get("/api/v2/audio/contents")
|
get("/api/v2/audio/contents")
|
||||||
@@ -92,7 +99,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.data.type").value("SERIES"))
|
.andExpect(jsonPath("$.data.type").value("SERIES"))
|
||||||
.andExpect(jsonPath("$.data.sort").value("POPULAR"))
|
.andExpect(jsonPath("$.data.sort").value("POPULAR"))
|
||||||
|
|
||||||
Mockito.verify(facade).getContents("SERIES", "POPULAR", "MON", 1, 30, member)
|
Mockito.verify(facade).getContents("SERIES", "POPULAR", "MON", 1, 30, member, Lang.KO)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun response(type: MainContentAllType, sort: ContentSort): MainContentAllTabResponse {
|
private fun response(type: MainContentAllType, sort: ContentSort): MainContentAllTabResponse {
|
||||||
|
|||||||
+108
-3
@@ -10,12 +10,16 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDays
|
|||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import org.hamcrest.Matchers.contains
|
||||||
import org.hamcrest.Matchers.nullValue
|
import org.hamcrest.Matchers.nullValue
|
||||||
|
import org.hibernate.SessionFactory
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
import org.springframework.test.annotation.DirtiesContext
|
import org.springframework.test.annotation.DirtiesContext
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import org.springframework.test.context.ContextConfiguration
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
@@ -30,6 +34,7 @@ import javax.persistence.EntityManager
|
|||||||
properties = [
|
properties = [
|
||||||
"cloud.aws.cloud-front.host=https://cdn.test",
|
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||||
"spring.cache.type=none",
|
"spring.cache.type=none",
|
||||||
|
"spring.jpa.properties.hibernate.generate_statistics=true",
|
||||||
"spring.datasource.url=jdbc:h2:mem:main-content-all-e2e;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
"spring.datasource.url=jdbc:h2:mem:main-content-all-e2e;MODE=MySQL;NON_KEYWORDS=VALUE;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -39,8 +44,107 @@ import javax.persistence.EntityManager
|
|||||||
class MainContentAllEndToEndTest @Autowired constructor(
|
class MainContentAllEndToEndTest @Autowired constructor(
|
||||||
private val mockMvc: MockMvc,
|
private val mockMvc: MockMvc,
|
||||||
private val entityManager: EntityManager,
|
private val entityManager: EntityManager,
|
||||||
private val transactionTemplate: TransactionTemplate
|
private val transactionTemplate: TransactionTemplate,
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
) {
|
) {
|
||||||
|
@Test
|
||||||
|
@DisplayName("전체 콘텐츠 API는 JA 별칭을 우선하고 KO 별칭 미등록 시 일본어 원본을, 별칭 없는 작성자는 현재 원본을 반환한다")
|
||||||
|
fun shouldSelectLocalizedCreatorNamesWithoutChangingAudioTitles() {
|
||||||
|
transactionTemplate.executeWithoutResult { saveMember("main-all-resource-id-control", MemberRole.USER) }
|
||||||
|
val localized = createAudioFixture("main-all-localized")
|
||||||
|
val fallback = createAudioFixture("main-all-fallback")
|
||||||
|
transactionTemplate.executeWithoutResult {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"""
|
||||||
|
insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at)
|
||||||
|
values (?, 'JA', ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
""".trimIndent(),
|
||||||
|
localized.creatorId,
|
||||||
|
"日本語クリエイター"
|
||||||
|
)
|
||||||
|
entityManager.find(Member::class.java, localized.creatorId).nickname = "改名さくら"
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v2/audio/contents").param("type", "AUDIO").header("Accept-Language", "ko"))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.totalCount").value(2))
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${localized.audioContentId})].creatorNickname")
|
||||||
|
.value(contains("改名さくら"))
|
||||||
|
)
|
||||||
|
|
||||||
|
val statistics = entityManager.entityManagerFactory.unwrap(SessionFactory::class.java).statistics
|
||||||
|
statistics.clear()
|
||||||
|
mockMvc.perform(get("/api/v2/audio/contents").param("type", "AUDIO").header("Accept-Language", "ja"))
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.totalCount").value(2))
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${localized.audioContentId})].creatorNickname")
|
||||||
|
.value(contains("日本語クリエイター"))
|
||||||
|
)
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${fallback.audioContentId})].creatorNickname")
|
||||||
|
.value(contains("main-all-fallback-creator"))
|
||||||
|
)
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${localized.audioContentId})].title")
|
||||||
|
.value(contains("main-all-localized-audio"))
|
||||||
|
)
|
||||||
|
.andExpect(jsonPath("$.data.audios[0].creatorId").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.data.audios[0].creatorMemberId").doesNotExist())
|
||||||
|
|
||||||
|
val aliasQueries = statistics.queries.filter {
|
||||||
|
Regex("""(?is)^select\s+\w+\.memberId,\s*\w+\.displayName\s+from\s+CreatorLocalizedNameEntity\b""")
|
||||||
|
.containsMatchIn(it)
|
||||||
|
}
|
||||||
|
val aliasQueryCount = aliasQueries.sumOf { statistics.getQueryStatistics(it).executionCount }
|
||||||
|
assertEquals(
|
||||||
|
0L,
|
||||||
|
aliasQueryCount,
|
||||||
|
"Standalone alias SELECTs (total queries=${statistics.queryExecutionCount}): $aliasQueries"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("영어 활성 이름도 JOIN으로 읽으며 비활성 이름은 원본으로 fallback하고 인기순 집계를 유지한다")
|
||||||
|
fun shouldReadActiveEnglishCreatorNameAndFallbackWhenInactive() {
|
||||||
|
val active = createAudioFixture("main-all-english-active")
|
||||||
|
val inactive = createAudioFixture("main-all-english-inactive")
|
||||||
|
transactionTemplate.executeWithoutResult {
|
||||||
|
listOf(Triple(active.creatorId, "English creator", true), Triple(inactive.creatorId, "Inactive name", false))
|
||||||
|
.forEach { (creatorId, name, isActive) ->
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"""
|
||||||
|
insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at)
|
||||||
|
values (?, 'EN', ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
""".trimIndent(),
|
||||||
|
creatorId,
|
||||||
|
name,
|
||||||
|
isActive
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/audio/contents").param("type", "AUDIO").param("sort", "POPULAR").header("Accept-Language", "en")
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.totalCount").value(2))
|
||||||
|
.andExpect(jsonPath("$.data.audios.length()").value(2))
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${active.audioContentId})].creatorNickname")
|
||||||
|
.value(contains("English creator"))
|
||||||
|
)
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${inactive.audioContentId})].creatorNickname")
|
||||||
|
.value(contains("main-all-english-inactive-creator"))
|
||||||
|
)
|
||||||
|
.andExpect(
|
||||||
|
jsonPath("$.data.audios[?(@.audioContentId == ${active.audioContentId})].title")
|
||||||
|
.value(contains("main-all-english-active-audio"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("전체 탭 AUDIO API는 controller-service-repository를 거쳐 오디오 응답과 빈 series를 반환한다")
|
@DisplayName("전체 탭 AUDIO API는 controller-service-repository를 거쳐 오디오 응답과 빈 series를 반환한다")
|
||||||
fun shouldReturnAudioContentsThroughControllerServiceAndRepository() {
|
fun shouldReturnAudioContentsThroughControllerServiceAndRepository() {
|
||||||
@@ -196,7 +300,7 @@ class MainContentAllEndToEndTest @Autowired constructor(
|
|||||||
entityManager.flush()
|
entityManager.flush()
|
||||||
entityManager.clear()
|
entityManager.clear()
|
||||||
|
|
||||||
AudioFixture(audioContentId = audio.id!!)
|
AudioFixture(audioContentId = audio.id!!, creatorId = creator.id!!)
|
||||||
}!!
|
}!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +412,8 @@ class MainContentAllEndToEndTest @Autowired constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private data class AudioFixture(
|
private data class AudioFixture(
|
||||||
val audioContentId: Long
|
val audioContentId: Long,
|
||||||
|
val creatorId: Long
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class SeriesFixture(
|
private data class SeriesFixture(
|
||||||
|
|||||||
+5
-3
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.all.application
|
package kr.co.vividnext.sodalive.v2.api.content.all.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
@@ -36,7 +37,7 @@ class MainContentAllFacadeTest {
|
|||||||
page = MainContentPage(1, 30),
|
page = MainContentPage(1, 30),
|
||||||
hasNext = false
|
hasNext = false
|
||||||
)
|
)
|
||||||
).`when`(queryService).getContents("FREE", "PRICE_LOW", "MON", 1, 30, member)
|
).`when`(queryService).getContents("FREE", "PRICE_LOW", "MON", 1, 30, member, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getContents(
|
val response = facade.getContents(
|
||||||
type = "FREE",
|
type = "FREE",
|
||||||
@@ -44,10 +45,11 @@ class MainContentAllFacadeTest {
|
|||||||
dayOfWeek = "MON",
|
dayOfWeek = "MON",
|
||||||
page = 1,
|
page = 1,
|
||||||
size = 30,
|
size = 30,
|
||||||
member = member
|
member = member,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
Mockito.verify(queryService).getContents("FREE", "PRICE_LOW", "MON", 1, 30, member)
|
Mockito.verify(queryService).getContents("FREE", "PRICE_LOW", "MON", 1, 30, member, Lang.KO)
|
||||||
assertEquals(MainContentAllType.FREE, response.type)
|
assertEquals(MainContentAllType.FREE, response.type)
|
||||||
assertEquals(ContentSort.PRICE_LOW, response.sort)
|
assertEquals(ContentSort.PRICE_LOW, response.sort)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -27,6 +27,7 @@ class MainContentAllTabResponseTest {
|
|||||||
audios = emptyList(),
|
audios = emptyList(),
|
||||||
series = listOf(
|
series = listOf(
|
||||||
MainContentAllSeries(
|
MainContentAllSeries(
|
||||||
|
creatorMemberId = 100L,
|
||||||
seriesId = 10L,
|
seriesId = 10L,
|
||||||
title = "시리즈",
|
title = "시리즈",
|
||||||
coverImageUrl = "https://cdn/series.jpg",
|
coverImageUrl = "https://cdn/series.jpg",
|
||||||
@@ -58,6 +59,7 @@ class MainContentAllTabResponseTest {
|
|||||||
totalCount = 1,
|
totalCount = 1,
|
||||||
audios = listOf(
|
audios = listOf(
|
||||||
MainContentAllAudio(
|
MainContentAllAudio(
|
||||||
|
creatorMemberId = 100L,
|
||||||
audioContentId = 1L,
|
audioContentId = 1L,
|
||||||
title = "audio",
|
title = "audio",
|
||||||
imageUrl = "https://cdn/audio.jpg",
|
imageUrl = "https://cdn/audio.jpg",
|
||||||
@@ -69,6 +71,7 @@ class MainContentAllTabResponseTest {
|
|||||||
creatorNickname = "creator"
|
creatorNickname = "creator"
|
||||||
),
|
),
|
||||||
MainContentAllAudio(
|
MainContentAllAudio(
|
||||||
|
creatorMemberId = 100L,
|
||||||
audioContentId = 2L,
|
audioContentId = 2L,
|
||||||
title = "free audio",
|
title = "free audio",
|
||||||
imageUrl = "https://cdn/free-audio.jpg",
|
imageUrl = "https://cdn/free-audio.jpg",
|
||||||
@@ -80,6 +83,7 @@ class MainContentAllTabResponseTest {
|
|||||||
creatorNickname = "creator"
|
creatorNickname = "creator"
|
||||||
),
|
),
|
||||||
MainContentAllAudio(
|
MainContentAllAudio(
|
||||||
|
creatorMemberId = 100L,
|
||||||
audioContentId = 3L,
|
audioContentId = 3L,
|
||||||
title = "point unavailable audio",
|
title = "point unavailable audio",
|
||||||
imageUrl = "https://cdn/point-unavailable-audio.jpg",
|
imageUrl = "https://cdn/point-unavailable-audio.jpg",
|
||||||
|
|||||||
+10
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.content.overview.adapter.`in`.web
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
||||||
@@ -13,6 +14,7 @@ import kr.co.vividnext.sodalive.member.MemberRole
|
|||||||
import kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewFacade
|
import kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewFacade
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponse
|
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewPageResponse
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewType
|
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewType
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -41,6 +43,11 @@ class ContentOverviewControllerTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var langContext: LangContext
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpLanguage() {
|
||||||
|
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||||
|
}
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var sodaMessageSource: SodaMessageSource
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
@@ -62,7 +69,7 @@ class ContentOverviewControllerTest @Autowired constructor(
|
|||||||
fun shouldPassAuthenticatedMemberAndQueryParameters() {
|
fun shouldPassAuthenticatedMemberAndQueryParameters() {
|
||||||
val member = member(id = 10L)
|
val member = member(id = 10L)
|
||||||
Mockito.doReturn(emptyResponse(ContentOverviewType.FIRST_AUDIO_CONTENT)).`when`(facade)
|
Mockito.doReturn(emptyResponse(ContentOverviewType.FIRST_AUDIO_CONTENT)).`when`(facade)
|
||||||
.getContents(eqValue("FIRST_AUDIO_CONTENT"), eqValue(1), eqValue(30), eqValue(member))
|
.getContents(eqValue("FIRST_AUDIO_CONTENT"), eqValue(1), eqValue(30), eqValue(member), eqValue(Lang.KO))
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
get("/api/v2/contents")
|
get("/api/v2/contents")
|
||||||
@@ -75,7 +82,8 @@ class ContentOverviewControllerTest @Autowired constructor(
|
|||||||
.andExpect(jsonPath("$.success").value(true))
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
.andExpect(jsonPath("$.data.type").value("FIRST_AUDIO_CONTENT"))
|
.andExpect(jsonPath("$.data.type").value("FIRST_AUDIO_CONTENT"))
|
||||||
|
|
||||||
Mockito.verify(facade).getContents(eqValue("FIRST_AUDIO_CONTENT"), eqValue(1), eqValue(30), eqValue(member))
|
Mockito.verify(facade)
|
||||||
|
.getContents(eqValue("FIRST_AUDIO_CONTENT"), eqValue(1), eqValue(30), eqValue(member), eqValue(Lang.KO))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> eqValue(value: T): T {
|
private fun <T> eqValue(value: T): T {
|
||||||
|
|||||||
+7
-4
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.overview.application
|
package kr.co.vividnext.sodalive.v2.api.content.overview.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||||
@@ -31,9 +32,9 @@ class ContentOverviewFacadeTest {
|
|||||||
fun shouldReturnNewAndHotPage() {
|
fun shouldReturnNewAndHotPage() {
|
||||||
val member = member(id = 10L)
|
val member = member(id = 10L)
|
||||||
Mockito.doReturn((1L..21L).map { audioCard(it) }).`when`(audioRecommendationQueryService)
|
Mockito.doReturn((1L..21L).map { audioCard(it) }).`when`(audioRecommendationQueryService)
|
||||||
.findNewAndHotAudios(member, offset = 0L, limit = 21)
|
.findNewAndHotAudios(member, offset = 0L, limit = 21, lang = Lang.KO)
|
||||||
|
|
||||||
val response = facade.getContents("NEW_AND_HOT_AUDIO", page = 0, size = 20, member = member)
|
val response = facade.getContents("NEW_AND_HOT_AUDIO", page = 0, size = 20, member = member, lang = Lang.KO)
|
||||||
|
|
||||||
assertEquals(ContentOverviewType.NEW_AND_HOT_AUDIO, response.type)
|
assertEquals(ContentOverviewType.NEW_AND_HOT_AUDIO, response.type)
|
||||||
assertEquals((1L..20L).toList(), response.items.map { it.contentId })
|
assertEquals((1L..20L).toList(), response.items.map { it.contentId })
|
||||||
@@ -54,10 +55,11 @@ class ContentOverviewFacadeTest {
|
|||||||
eqValue(20L),
|
eqValue(20L),
|
||||||
eqValue(21),
|
eqValue(21),
|
||||||
eqValue(member.id),
|
eqValue(member.id),
|
||||||
eqValue(true)
|
eqValue(true),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = facade.getContents("FIRST_AUDIO_CONTENT", page = 1, size = 20, member = member)
|
val response = facade.getContents("FIRST_AUDIO_CONTENT", page = 1, size = 20, member = member, lang = Lang.KO)
|
||||||
|
|
||||||
assertEquals(ContentOverviewType.FIRST_AUDIO_CONTENT, response.type)
|
assertEquals(ContentOverviewType.FIRST_AUDIO_CONTENT, response.type)
|
||||||
assertEquals(listOf(1L, 2L), response.items.map { it.contentId })
|
assertEquals(listOf(1L, 2L), response.items.map { it.contentId })
|
||||||
@@ -79,6 +81,7 @@ class ContentOverviewFacadeTest {
|
|||||||
|
|
||||||
private fun audioCard(id: Long): AudioCard {
|
private fun audioCard(id: Long): AudioCard {
|
||||||
return AudioCard(
|
return AudioCard(
|
||||||
|
creatorMemberId = id + 100L,
|
||||||
audioContentId = id,
|
audioContentId = id,
|
||||||
title = "audio$id",
|
title = "audio$id",
|
||||||
duration = "00:01",
|
duration = "00:01",
|
||||||
|
|||||||
+1
@@ -120,6 +120,7 @@ class ContentOverviewPageResponseTest {
|
|||||||
|
|
||||||
private fun audioCard(price: Int, isPointAvailable: Boolean): AudioCard {
|
private fun audioCard(price: Int, isPointAvailable: Boolean): AudioCard {
|
||||||
return AudioCard(
|
return AudioCard(
|
||||||
|
creatorMemberId = 10L,
|
||||||
audioContentId = price.toLong() + 1,
|
audioContentId = price.toLong() + 1,
|
||||||
title = "audio",
|
title = "audio",
|
||||||
duration = "00:01",
|
duration = "00:01",
|
||||||
|
|||||||
+5
-3
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.ranking.application
|
package kr.co.vividnext.sodalive.v2.api.content.ranking.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryService
|
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryService
|
||||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
||||||
@@ -23,6 +24,7 @@ class AudioRankingFacadeTest {
|
|||||||
type = AudioRankingType.RISING,
|
type = AudioRankingType.RISING,
|
||||||
items = listOf(
|
items = listOf(
|
||||||
AudioRankingItem(
|
AudioRankingItem(
|
||||||
|
creatorMemberId = 10L,
|
||||||
contentId = 1L,
|
contentId = 1L,
|
||||||
title = "audio",
|
title = "audio",
|
||||||
creatorNickname = "creator",
|
creatorNickname = "creator",
|
||||||
@@ -33,11 +35,11 @@ class AudioRankingFacadeTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
Mockito.doReturn(ranking).`when`(queryService).getRankings(AudioRankingType.RISING, member)
|
Mockito.doReturn(ranking).`when`(queryService).getRankings(AudioRankingType.RISING, member, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getRankings(AudioRankingType.RISING, member)
|
val response = facade.getRankings(AudioRankingType.RISING, member, Lang.KO)
|
||||||
|
|
||||||
Mockito.verify(queryService).getRankings(AudioRankingType.RISING, member)
|
Mockito.verify(queryService).getRankings(AudioRankingType.RISING, member, Lang.KO)
|
||||||
assertEquals(true, response.showRankChange)
|
assertEquals(true, response.showRankChange)
|
||||||
assertEquals(AudioRankingType.RISING, response.type)
|
assertEquals(AudioRankingType.RISING, response.type)
|
||||||
assertEquals(1, response.items.size)
|
assertEquals(1, response.items.size)
|
||||||
|
|||||||
+1
@@ -19,6 +19,7 @@ class AudioRankingResponseTest {
|
|||||||
type = AudioRankingType.RISING,
|
type = AudioRankingType.RISING,
|
||||||
items = listOf(
|
items = listOf(
|
||||||
AudioRankingItem(
|
AudioRankingItem(
|
||||||
|
creatorMemberId = 10L,
|
||||||
contentId = 1001L,
|
contentId = 1001L,
|
||||||
title = "rising audio",
|
title = "rising audio",
|
||||||
creatorNickname = "creator",
|
creatorNickname = "creator",
|
||||||
|
|||||||
+10
-3
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.content.recommendation.adapter.`in`.web
|
|||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
import kr.co.vividnext.sodalive.configs.SecurityConfig
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
import kr.co.vividnext.sodalive.jwt.JwtAccessDeniedHandler
|
||||||
@@ -12,6 +13,7 @@ import kr.co.vividnext.sodalive.member.MemberAdapter
|
|||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacade
|
import kr.co.vividnext.sodalive.v2.api.content.recommendation.application.AudioRecommendationFacade
|
||||||
import kr.co.vividnext.sodalive.v2.api.content.recommendation.dto.AudioRecommendationsResponse
|
import kr.co.vividnext.sodalive.v2.api.content.recommendation.dto.AudioRecommendationsResponse
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -39,6 +41,11 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var langContext: LangContext
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpLanguage() {
|
||||||
|
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||||
|
}
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var sodaMessageSource: SodaMessageSource
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
@@ -54,7 +61,7 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("오디오 추천 조회는 비회원에게 200 OK와 ApiResponse.ok wrapper를 반환한다")
|
@DisplayName("오디오 추천 조회는 비회원에게 200 OK와 ApiResponse.ok wrapper를 반환한다")
|
||||||
fun shouldReturnRecommendationsForAnonymous() {
|
fun shouldReturnRecommendationsForAnonymous() {
|
||||||
Mockito.doReturn(emptyResponse()).`when`(facade).getRecommendations(null)
|
Mockito.doReturn(emptyResponse()).`when`(facade).getRecommendations(null, Lang.KO)
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v2/audio/recommendations"))
|
mockMvc.perform(get("/api/v2/audio/recommendations"))
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
@@ -74,13 +81,13 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
|||||||
nickname = "viewer",
|
nickname = "viewer",
|
||||||
role = MemberRole.USER
|
role = MemberRole.USER
|
||||||
).apply { id = 10L }
|
).apply { id = 10L }
|
||||||
Mockito.doReturn(emptyResponse()).`when`(facade).getRecommendations(eqValue(member))
|
Mockito.doReturn(emptyResponse()).`when`(facade).getRecommendations(eqValue(member), eqValue(Lang.KO))
|
||||||
|
|
||||||
mockMvc.perform(get("/api/v2/audio/recommendations").with(user(MemberAdapter(member))))
|
mockMvc.perform(get("/api/v2/audio/recommendations").with(user(MemberAdapter(member))))
|
||||||
.andExpect(status().isOk)
|
.andExpect(status().isOk)
|
||||||
.andExpect(jsonPath("$.success").value(true))
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
|
||||||
Mockito.verify(facade).getRecommendations(eqValue(member))
|
Mockito.verify(facade).getRecommendations(eqValue(member), eqValue(Lang.KO))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emptyResponse(): AudioRecommendationsResponse {
|
private fun emptyResponse(): AudioRecommendationsResponse {
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.content.recommendation.application
|
package kr.co.vividnext.sodalive.v2.api.content.recommendation.application
|
||||||
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner
|
import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
||||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
||||||
@@ -20,9 +21,9 @@ class AudioRecommendationFacadeTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("facade는 도메인 추천 결과를 모든 공개 응답 필드로 변환한다")
|
@DisplayName("facade는 도메인 추천 결과를 모든 공개 응답 필드로 변환한다")
|
||||||
fun shouldConvertDomainRecommendationsToResponse() {
|
fun shouldConvertDomainRecommendationsToResponse() {
|
||||||
Mockito.doReturn(domain()).`when`(queryService).getRecommendations(null)
|
Mockito.doReturn(domain()).`when`(queryService).getRecommendations(null, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getRecommendations(null)
|
val response = facade.getRecommendations(null, Lang.KO)
|
||||||
|
|
||||||
assertEquals(1, response.banners.size)
|
assertEquals(1, response.banners.size)
|
||||||
assertEquals(1, response.originalSeries.size)
|
assertEquals(1, response.originalSeries.size)
|
||||||
@@ -49,6 +50,7 @@ class AudioRecommendationFacadeTest {
|
|||||||
|
|
||||||
private fun domain(): AudioRecommendations {
|
private fun domain(): AudioRecommendations {
|
||||||
val card = AudioCard(
|
val card = AudioCard(
|
||||||
|
creatorMemberId = 10L,
|
||||||
audioContentId = 1L,
|
audioContentId = 1L,
|
||||||
title = "audio",
|
title = "audio",
|
||||||
duration = "00:01",
|
duration = "00:01",
|
||||||
|
|||||||
+15
-4
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -11,6 +12,7 @@ import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelA
|
|||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelAudioThemeResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.dto.CreatorChannelAudioThemeResponse
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.common.dto.CreatorChannelAudioContentResponse
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -47,6 +49,11 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var langContext: LangContext
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpLanguage() {
|
||||||
|
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||||
|
}
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var sodaMessageSource: SodaMessageSource
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
@@ -88,7 +95,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
|||||||
eqValue(null),
|
eqValue(null),
|
||||||
eqValue(null),
|
eqValue(null),
|
||||||
eqValue(null),
|
eqValue(null),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -118,7 +126,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
|||||||
eqValue(null),
|
eqValue(null),
|
||||||
eqValue(null),
|
eqValue(null),
|
||||||
eqValue(null),
|
eqValue(null),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +142,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
|||||||
eqValue(999L),
|
eqValue(999L),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -158,7 +168,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
|||||||
eqValue(999L),
|
eqValue(999L),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||||
@@ -33,7 +34,8 @@ class CreatorChannelAudioFacadeTest {
|
|||||||
themeId = 10L,
|
themeId = 10L,
|
||||||
page = 0,
|
page = 0,
|
||||||
size = 20,
|
size = 20,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = facade.getAudioTab(
|
val response = facade.getAudioTab(
|
||||||
@@ -43,7 +45,8 @@ class CreatorChannelAudioFacadeTest {
|
|||||||
themeId = 10L,
|
themeId = 10L,
|
||||||
page = 0,
|
page = 0,
|
||||||
size = 20,
|
size = 20,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(3, response.audioContentCount)
|
assertEquals(3, response.audioContentCount)
|
||||||
|
|||||||
+29
-11
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.`in`.web
|
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.`in`.web
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.CountryContext
|
import kr.co.vividnext.sodalive.common.CountryContext
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -14,6 +15,7 @@ import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChan
|
|||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityRepliesResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityRepliesResponse
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityReplyResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityReplyResponse
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityTabResponse
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@@ -50,6 +52,11 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var langContext: LangContext
|
private lateinit var langContext: LangContext
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUpLanguage() {
|
||||||
|
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||||
|
}
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var sodaMessageSource: SodaMessageSource
|
private lateinit var sodaMessageSource: SodaMessageSource
|
||||||
|
|
||||||
@@ -89,7 +96,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(1),
|
eqValue(1),
|
||||||
eqValue(20),
|
eqValue(20),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -117,7 +125,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(1),
|
eqValue(1),
|
||||||
eqValue(20),
|
eqValue(20),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +139,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -148,7 +158,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +180,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
Mockito.doReturn(createDetailResponse()).`when`(facade).getCommunityPostDetail(
|
Mockito.doReturn(createDetailResponse()).`when`(facade).getCommunityPostDetail(
|
||||||
eqValue(101L),
|
eqValue(101L),
|
||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -195,7 +207,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(0),
|
eqValue(0),
|
||||||
eqValue(20),
|
eqValue(20),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -222,7 +235,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -240,7 +254,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +268,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(0),
|
eqValue(0),
|
||||||
eqValue(20),
|
eqValue(20),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -278,7 +294,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
|
|
||||||
mockMvc.perform(
|
mockMvc.perform(
|
||||||
@@ -296,7 +313,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
|||||||
eqValue(viewer),
|
eqValue(viewer),
|
||||||
eqValue(-1),
|
eqValue(-1),
|
||||||
eqValue(100),
|
eqValue(100),
|
||||||
anyValue(LocalDateTime.now())
|
anyValue(LocalDateTime.now()),
|
||||||
|
eqValue(Lang.KO)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+64
-1
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired
|
|||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import org.springframework.test.context.ContextConfiguration
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
@@ -44,7 +45,8 @@ import javax.persistence.EntityManager
|
|||||||
class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
||||||
private val mockMvc: MockMvc,
|
private val mockMvc: MockMvc,
|
||||||
private val entityManager: EntityManager,
|
private val entityManager: EntityManager,
|
||||||
private val transactionTemplate: TransactionTemplate
|
private val transactionTemplate: TransactionTemplate,
|
||||||
|
private val jdbcTemplate: JdbcTemplate
|
||||||
) {
|
) {
|
||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var audioContentCloudFront: AudioContentCloudFront
|
private lateinit var audioContentCloudFront: AudioContentCloudFront
|
||||||
@@ -52,6 +54,67 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
|||||||
@MockBean
|
@MockBean
|
||||||
private lateinit var creatorCommunityTranslationService: CreatorCommunityTranslationService
|
private lateinit var creatorCommunityTranslationService: CreatorCommunityTranslationService
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("커뮤니티 상세의 JA 이름은 크리에이터 댓글과 최신 답글에만 적용하고 USER 이름은 유지한다")
|
||||||
|
fun shouldLocalizeCreatorAuthorsAndLatestRepliesWithoutChangingUserNames() {
|
||||||
|
val fixture = transactionTemplate.execute {
|
||||||
|
val viewer = saveMember("community-localized-viewer", MemberRole.USER)
|
||||||
|
val creator = saveMember("community-localized-creator", MemberRole.CREATOR)
|
||||||
|
savePreference(viewer, isAdultContentVisible = true)
|
||||||
|
val post = saveCommunity(creator, isFixed = false, price = 0, content = "localized-name community")
|
||||||
|
val userComment = saveCommunityComment(viewer, post)
|
||||||
|
val creatorComment = saveCommunityComment(creator, post)
|
||||||
|
saveCommunityComment(creator, post, parent = userComment)
|
||||||
|
entityManager.flush()
|
||||||
|
listOf(creator.id!! to "日本語の投稿者", viewer.id!! to "USERには使わない名前").forEach { (memberId, name) ->
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"""
|
||||||
|
insert into creator_localized_name (member_id, lang, display_name, is_active, created_at, updated_at)
|
||||||
|
values (?, 'JA', ?, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
""".trimIndent(),
|
||||||
|
memberId,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
updateCommentCreatedAt(userComment.id!!, LocalDateTime.of(2026, 10, 14, 10, 0))
|
||||||
|
updateCommentCreatedAt(creatorComment.id!!, LocalDateTime.of(2026, 10, 14, 11, 0))
|
||||||
|
entityManager.clear()
|
||||||
|
PostFixture(viewer = viewer, postId = post.id!!)
|
||||||
|
}!!
|
||||||
|
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.success").value(true))
|
||||||
|
.andExpect(jsonPath("$.data.creatorNickname").value("日本語の投稿者"))
|
||||||
|
.andExpect(jsonPath("$.data.content").value("localized-name community"))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments.length()").value(2))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[0].writerNickname").value("日本語の投稿者"))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[1].writerId").value(fixture.viewer.id))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[1].writerNickname").value("community-localized-viewer"))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[1].latestReply.writerNickname").value("日本語の投稿者"))
|
||||||
|
|
||||||
|
transactionTemplate.executeWithoutResult {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"update creator_localized_name set display_name = ? where member_id = ? and lang = 'JA'",
|
||||||
|
"deleted_表示名そのまま",
|
||||||
|
entityManager.find(CreatorCommunity::class.java, fixture.postId).member!!.id!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mockMvc.perform(
|
||||||
|
get("/api/v2/creator-channels/community-posts/${fixture.postId}")
|
||||||
|
.header("Accept-Language", "ja")
|
||||||
|
.with(user(MemberAdapter(fixture.viewer)))
|
||||||
|
)
|
||||||
|
.andExpect(status().isOk)
|
||||||
|
.andExpect(jsonPath("$.data.creatorNickname").value("deleted_表示名そのまま"))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[0].writerNickname").value("deleted_表示名そのまま"))
|
||||||
|
.andExpect(jsonPath("$.data.comments.comments[1].latestReply.writerNickname").value("deleted_表示名そのまま"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("커뮤니티 탭 API는 E2E로 정렬, fallback, 성인 필터, 유료 미디어 접근 정책을 반환한다")
|
@DisplayName("커뮤니티 탭 API는 E2E로 정렬, fallback, 성인 필터, 유료 미디어 접근 정책을 반환한다")
|
||||||
fun shouldReturnCommunityTabThroughControllerServiceAndRepository() {
|
fun shouldReturnCommunityTabThroughControllerServiceAndRepository() {
|
||||||
|
|||||||
+11
-8
@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.v2.api.creator.channel.community.application
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityTabResponse
|
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityTabResponse
|
||||||
@@ -71,7 +72,8 @@ class CreatorChannelCommunityFacadeTest {
|
|||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = -1,
|
page = -1,
|
||||||
size = 100,
|
size = 100,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
val response = facade.getCommunityTab(
|
val response = facade.getCommunityTab(
|
||||||
@@ -79,7 +81,8 @@ class CreatorChannelCommunityFacadeTest {
|
|||||||
viewer = viewer,
|
viewer = viewer,
|
||||||
page = -1,
|
page = -1,
|
||||||
size = 100,
|
size = 100,
|
||||||
now = now
|
now = now,
|
||||||
|
lang = Lang.KO
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(2, response.communityPostCount)
|
assertEquals(2, response.communityPostCount)
|
||||||
@@ -99,9 +102,9 @@ class CreatorChannelCommunityFacadeTest {
|
|||||||
val facade = CreatorChannelCommunityFacade(service)
|
val facade = CreatorChannelCommunityFacade(service)
|
||||||
val viewer = createMember(id = 10L)
|
val viewer = createMember(id = 10L)
|
||||||
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
||||||
Mockito.doReturn(createDetail()).`when`(service).getCommunityPostDetail(101L, viewer, now)
|
Mockito.doReturn(createDetail()).`when`(service).getCommunityPostDetail(101L, viewer, now, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getCommunityPostDetail(101L, viewer, now)
|
val response = facade.getCommunityPostDetail(101L, viewer, now, Lang.KO)
|
||||||
|
|
||||||
assertEquals(101L, response.postId)
|
assertEquals(101L, response.postId)
|
||||||
assertTrue(response.isLiked)
|
assertTrue(response.isLiked)
|
||||||
@@ -119,9 +122,9 @@ class CreatorChannelCommunityFacadeTest {
|
|||||||
val facade = CreatorChannelCommunityFacade(service)
|
val facade = CreatorChannelCommunityFacade(service)
|
||||||
val viewer = createMember(id = 10L)
|
val viewer = createMember(id = 10L)
|
||||||
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
||||||
Mockito.doReturn(createComments()).`when`(service).getCommunityComments(101L, viewer, -1, 100, now)
|
Mockito.doReturn(createComments()).`when`(service).getCommunityComments(101L, viewer, -1, 100, now, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getCommunityComments(101L, viewer, -1, 100, now)
|
val response = facade.getCommunityComments(101L, viewer, -1, 100, now, Lang.KO)
|
||||||
|
|
||||||
assertEquals(1, response.commentCount)
|
assertEquals(1, response.commentCount)
|
||||||
assertEquals(0, response.page)
|
assertEquals(0, response.page)
|
||||||
@@ -139,9 +142,9 @@ class CreatorChannelCommunityFacadeTest {
|
|||||||
val facade = CreatorChannelCommunityFacade(service)
|
val facade = CreatorChannelCommunityFacade(service)
|
||||||
val viewer = createMember(id = 10L)
|
val viewer = createMember(id = 10L)
|
||||||
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
val now = LocalDateTime.of(2026, 7, 6, 12, 0)
|
||||||
Mockito.doReturn(createReplies()).`when`(service).getCommunityReplies(201L, viewer, -1, 100, now)
|
Mockito.doReturn(createReplies()).`when`(service).getCommunityReplies(201L, viewer, -1, 100, now, Lang.KO)
|
||||||
|
|
||||||
val response = facade.getCommunityReplies(201L, viewer, -1, 100, now)
|
val response = facade.getCommunityReplies(201L, viewer, -1, 100, now, Lang.KO)
|
||||||
|
|
||||||
assertEquals(1, response.replyCount)
|
assertEquals(1, response.replyCount)
|
||||||
assertEquals(201L, response.replies.first().commentId)
|
assertEquals(201L, response.replies.first().commentId)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user