feat(creatorname): v2 조회와 채팅에 언어별 이름을 적용한다
This commit is contained in:
+5
-2
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.all.adapter.`in`.web
|
||||
|
||||
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.v2.api.content.all.application.MainContentAllFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -12,7 +13,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/audio/contents")
|
||||
class MainContentAllController(
|
||||
private val facade: MainContentAllFacade
|
||||
private val facade: MainContentAllFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getContents(
|
||||
@@ -30,7 +32,8 @@ class MainContentAllController(
|
||||
dayOfWeek = dayOfWeek,
|
||||
page = page,
|
||||
size = size,
|
||||
member = member
|
||||
member = member,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,5 +1,6 @@
|
||||
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.v2.api.content.all.dto.MainContentAllTabResponse
|
||||
import kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryService
|
||||
@@ -15,17 +16,18 @@ class MainContentAllFacade(
|
||||
dayOfWeek: String?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
member: Member?
|
||||
member: Member?,
|
||||
lang: Lang
|
||||
): MainContentAllTabResponse {
|
||||
return MainContentAllTabResponse.from(
|
||||
queryService.getContents(
|
||||
type = type,
|
||||
sort = sort,
|
||||
dayOfWeek = dayOfWeek,
|
||||
page = page,
|
||||
size = size,
|
||||
member = member
|
||||
)
|
||||
val contents = queryService.getContents(
|
||||
type = type,
|
||||
sort = sort,
|
||||
dayOfWeek = dayOfWeek,
|
||||
page = page,
|
||||
size = size,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.content.overview.application.ContentOverviewFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/contents")
|
||||
class ContentOverviewController(
|
||||
private val facade: ContentOverviewFacade
|
||||
private val facade: ContentOverviewFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getContents(
|
||||
@@ -22,7 +24,7 @@ class ContentOverviewController(
|
||||
@RequestParam(required = false) size: Int?,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = 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 {
|
||||
|
||||
+14
-9
@@ -1,5 +1,6 @@
|
||||
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.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.api.content.overview.dto.ContentOverviewItemResponse
|
||||
@@ -21,42 +22,46 @@ class ContentOverviewFacade(
|
||||
private val cloudFrontHost: String,
|
||||
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 resolvedPage = queryPolicy.createPage(page, size)
|
||||
|
||||
return when (resolvedType) {
|
||||
ContentOverviewType.NEW_AND_HOT_AUDIO -> getNewAndHotContents(member, resolvedPage)
|
||||
ContentOverviewType.FIRST_AUDIO_CONTENT -> getFirstAudioContents(member, resolvedPage)
|
||||
ContentOverviewType.NEW_AND_HOT_AUDIO -> getNewAndHotContents(member, resolvedPage, lang)
|
||||
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(
|
||||
member = member,
|
||||
offset = page.offset,
|
||||
limit = page.size + 1
|
||||
limit = page.size + 1,
|
||||
lang = lang
|
||||
)
|
||||
val items = queryPolicy.pageItems(fetched, page)
|
||||
return ContentOverviewPageResponse(
|
||||
type = ContentOverviewType.NEW_AND_HOT_AUDIO,
|
||||
items = queryPolicy.pageItems(fetched, page).map { ContentOverviewItemResponse.fromNewAndHot(it) },
|
||||
items = items.map(ContentOverviewItemResponse::fromNewAndHot),
|
||||
page = page.page,
|
||||
size = page.size,
|
||||
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(
|
||||
now = LocalDateTime.now(),
|
||||
offset = page.offset,
|
||||
limit = page.size + 1,
|
||||
memberId = member.id,
|
||||
includeAdultContents = memberContentPreferenceService.canViewAdultContent(member)
|
||||
includeAdultContents = memberContentPreferenceService.canViewAdultContent(member),
|
||||
lang = lang
|
||||
)
|
||||
val items = queryPolicy.pageItems(fetched, page)
|
||||
return ContentOverviewPageResponse(
|
||||
type = ContentOverviewType.FIRST_AUDIO_CONTENT,
|
||||
items = queryPolicy.pageItems(fetched, page).map {
|
||||
items = items.map {
|
||||
ContentOverviewItemResponse.fromFirstAudioContent(
|
||||
audio = it,
|
||||
coverImage = it.coverImage.toCdnUrl(cloudFrontHost),
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.ranking.adapter.`in`.web
|
||||
|
||||
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.v2.api.content.ranking.application.AudioRankingFacade
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||
@@ -13,13 +14,14 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/audio/rankings")
|
||||
class AudioRankingController(
|
||||
private val facade: AudioRankingFacade
|
||||
private val facade: AudioRankingFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getRankings(
|
||||
@RequestParam(defaultValue = "WEEKLY_POPULAR") type: AudioRankingType,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = 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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.content.ranking.dto.AudioRankingResponse
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryService
|
||||
@@ -10,7 +11,8 @@ import org.springframework.stereotype.Component
|
||||
class AudioRankingFacade(
|
||||
private val queryService: AudioRankingQueryService
|
||||
) {
|
||||
fun getRankings(type: AudioRankingType, member: Member?): AudioRankingResponse {
|
||||
return AudioRankingResponse.from(queryService.getRankings(type, member))
|
||||
fun getRankings(type: AudioRankingType, member: Member?, lang: Lang): AudioRankingResponse {
|
||||
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
|
||||
|
||||
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.v2.api.content.recommendation.application.AudioRecommendationFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/audio/recommendations")
|
||||
class AudioRecommendationController(
|
||||
private val audioRecommendationFacade: AudioRecommendationFacade
|
||||
private val audioRecommendationFacade: AudioRecommendationFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getRecommendations(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = 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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.content.recommendation.dto.AudioRecommendationsResponse
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.application.AudioRecommendationQueryService
|
||||
@@ -9,7 +10,8 @@ import org.springframework.stereotype.Component
|
||||
class AudioRecommendationFacade(
|
||||
private val queryService: AudioRecommendationQueryService
|
||||
) {
|
||||
fun getRecommendations(member: Member?): AudioRecommendationsResponse {
|
||||
return AudioRecommendationsResponse.from(queryService.getRecommendations(member))
|
||||
fun getRecommendations(member: Member?, lang: Lang): AudioRecommendationsResponse {
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.audio.application.CreatorChannelAudioFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelAudioController(
|
||||
private val creatorChannelAudioFacade: CreatorChannelAudioFacade
|
||||
private val creatorChannelAudioFacade: CreatorChannelAudioFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/audio")
|
||||
fun getAudioTab(
|
||||
@@ -32,7 +34,8 @@ class CreatorChannelAudioController(
|
||||
sort = sort,
|
||||
themeId = themeId,
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
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.creator.channel.audio.application.CreatorChannelAudioQueryService
|
||||
@@ -19,7 +20,8 @@ class CreatorChannelAudioFacade(
|
||||
themeId: Long?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelAudioTabResponse {
|
||||
return CreatorChannelAudioTabResponse.from(
|
||||
creatorChannelAudioQueryService.getAudioTab(
|
||||
@@ -29,7 +31,8 @@ class CreatorChannelAudioFacade(
|
||||
themeId = themeId,
|
||||
page = page,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.application.CreatorChannelCommunityFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelCommunityController(
|
||||
private val creatorChannelCommunityFacade: CreatorChannelCommunityFacade
|
||||
private val creatorChannelCommunityFacade: CreatorChannelCommunityFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/community")
|
||||
fun getCommunityTab(
|
||||
@@ -28,7 +30,8 @@ class CreatorChannelCommunityController(
|
||||
creatorId = creatorId,
|
||||
viewer = requireMember(member),
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -41,7 +44,8 @@ class CreatorChannelCommunityController(
|
||||
ApiResponse.ok(
|
||||
creatorChannelCommunityFacade.getCommunityPostDetail(
|
||||
postId = postId,
|
||||
viewer = requireMember(member)
|
||||
viewer = requireMember(member),
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -58,7 +62,8 @@ class CreatorChannelCommunityController(
|
||||
postId = postId,
|
||||
viewer = requireMember(member),
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -75,7 +80,8 @@ class CreatorChannelCommunityController(
|
||||
commentId = commentId,
|
||||
viewer = requireMember(member),
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+39
-34
@@ -1,5 +1,6 @@
|
||||
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.v2.api.creator.channel.community.dto.CreatorChannelCommunityCommentsResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityPostDetailResponse
|
||||
@@ -20,31 +21,33 @@ class CreatorChannelCommunityFacade(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityTabResponse {
|
||||
return CreatorChannelCommunityTabResponse.from(
|
||||
creatorChannelCommunityQueryService.getCommunityTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now
|
||||
)
|
||||
val tab = creatorChannelCommunityQueryService.getCommunityTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now,
|
||||
lang = lang
|
||||
)
|
||||
return CreatorChannelCommunityTabResponse.from(tab)
|
||||
}
|
||||
|
||||
fun getCommunityPostDetail(
|
||||
postId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityPostDetailResponse {
|
||||
return CreatorChannelCommunityPostDetailResponse.from(
|
||||
creatorChannelCommunityQueryService.getCommunityPostDetail(
|
||||
postId = postId,
|
||||
viewer = viewer,
|
||||
now = now
|
||||
)
|
||||
val detail = creatorChannelCommunityQueryService.getCommunityPostDetail(
|
||||
postId = postId,
|
||||
viewer = viewer,
|
||||
now = now,
|
||||
lang = lang
|
||||
)
|
||||
return CreatorChannelCommunityPostDetailResponse.from(detail)
|
||||
}
|
||||
|
||||
fun getCommunityComments(
|
||||
@@ -52,17 +55,18 @@ class CreatorChannelCommunityFacade(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityCommentsResponse {
|
||||
return CreatorChannelCommunityCommentsResponse.from(
|
||||
creatorChannelCommunityQueryService.getCommunityComments(
|
||||
postId = postId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now
|
||||
)
|
||||
val comments = creatorChannelCommunityQueryService.getCommunityComments(
|
||||
postId = postId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now,
|
||||
lang = lang
|
||||
)
|
||||
return CreatorChannelCommunityCommentsResponse.from(comments)
|
||||
}
|
||||
|
||||
fun getCommunityReplies(
|
||||
@@ -70,16 +74,17 @@ class CreatorChannelCommunityFacade(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityRepliesResponse {
|
||||
return CreatorChannelCommunityRepliesResponse.from(
|
||||
creatorChannelCommunityQueryService.getCommunityReplies(
|
||||
commentId = commentId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now
|
||||
)
|
||||
val replies = creatorChannelCommunityQueryService.getCommunityReplies(
|
||||
commentId = commentId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application.CreatorChannelDonationFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelDonationController(
|
||||
private val creatorChannelDonationFacade: CreatorChannelDonationFacade
|
||||
private val creatorChannelDonationFacade: CreatorChannelDonationFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/donations")
|
||||
fun getDonationTab(
|
||||
@@ -28,7 +30,8 @@ class CreatorChannelDonationController(
|
||||
creatorId = creatorId,
|
||||
viewer = requireMember(member),
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+17
-8
@@ -1,8 +1,10 @@
|
||||
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.v2.api.creator.channel.donation.dto.CreatorChannelDonationTabResponse
|
||||
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.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
@@ -10,22 +12,29 @@ import java.time.LocalDateTime
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class CreatorChannelDonationFacade(
|
||||
private val creatorChannelDonationQueryService: CreatorChannelDonationQueryService
|
||||
private val creatorChannelDonationQueryService: CreatorChannelDonationQueryService,
|
||||
private val creatorLocalizedNameService: CreatorLocalizedNameService
|
||||
) {
|
||||
fun getDonationTab(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelDonationTabResponse {
|
||||
val tab = creatorChannelDonationQueryService.getDonationTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now,
|
||||
lang = lang
|
||||
)
|
||||
val names = creatorLocalizedNameService.findCurrentNames(tab.rankings.map { it.userId }.toSet(), lang)
|
||||
return CreatorChannelDonationTabResponse.from(
|
||||
creatorChannelDonationQueryService.getDonationTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.fantalk.application.CreatorChannelFanTalkFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelFanTalkController(
|
||||
private val creatorChannelFanTalkFacade: CreatorChannelFanTalkFacade
|
||||
private val creatorChannelFanTalkFacade: CreatorChannelFanTalkFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/fan-talks")
|
||||
fun getFanTalkTab(
|
||||
@@ -28,7 +30,8 @@ class CreatorChannelFanTalkController(
|
||||
creatorId = creatorId,
|
||||
viewer = requireMember(member),
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+11
-9
@@ -1,5 +1,6 @@
|
||||
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.v2.api.creator.channel.fantalk.dto.CreatorChannelFanTalkTabResponse
|
||||
import kr.co.vividnext.sodalive.v2.creator.channel.fantalk.application.CreatorChannelFanTalkQueryService
|
||||
@@ -17,16 +18,17 @@ class CreatorChannelFanTalkFacade(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelFanTalkTabResponse {
|
||||
return CreatorChannelFanTalkTabResponse.from(
|
||||
creatorChannelFanTalkQueryService.getFanTalkTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
now = now
|
||||
)
|
||||
val tab = creatorChannelFanTalkQueryService.getFanTalkTab(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
page = page,
|
||||
size = size,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.home.application.CreatorChannelHomeFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelHomeController(
|
||||
private val creatorChannelHomeFacade: CreatorChannelHomeFacade
|
||||
private val creatorChannelHomeFacade: CreatorChannelHomeFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/home")
|
||||
fun getHome(
|
||||
@@ -23,7 +25,8 @@ class CreatorChannelHomeController(
|
||||
ApiResponse.ok(
|
||||
creatorChannelHomeFacade.getHome(
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
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.creator.channel.home.application.CreatorChannelHomeQueryService
|
||||
@@ -16,14 +17,10 @@ class CreatorChannelHomeFacade(
|
||||
fun getHome(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
lang: Lang
|
||||
): CreatorChannelHomeResponse {
|
||||
return CreatorChannelHomeResponse.from(
|
||||
creatorChannelHomeQueryService.getHome(
|
||||
creatorId = creatorId,
|
||||
viewer = viewer,
|
||||
now = now
|
||||
)
|
||||
)
|
||||
val home = creatorChannelHomeQueryService.getHome(creatorId = creatorId, viewer = viewer, now = now, lang = lang)
|
||||
return CreatorChannelHomeResponse.from(home)
|
||||
}
|
||||
}
|
||||
|
||||
+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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
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.common.domain.ContentSort
|
||||
@@ -15,7 +16,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelLiveController(
|
||||
private val creatorChannelLiveFacade: CreatorChannelLiveFacade
|
||||
private val creatorChannelLiveFacade: CreatorChannelLiveFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/live")
|
||||
fun getLiveTab(
|
||||
@@ -31,7 +33,8 @@ class CreatorChannelLiveController(
|
||||
viewer = requireMember(member),
|
||||
sort = sort,
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
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.common.domain.ContentSort
|
||||
@@ -19,7 +20,8 @@ class CreatorChannelLiveFacade(
|
||||
sort: ContentSort,
|
||||
page: Int,
|
||||
size: Int,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelLiveTabResponse {
|
||||
return CreatorChannelLiveTabResponse.from(
|
||||
creatorChannelLiveQueryService.getLiveTab(
|
||||
@@ -28,7 +30,8 @@ class CreatorChannelLiveFacade(
|
||||
sort = sort,
|
||||
page = page,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.series.application.CreatorChannelSeriesFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -14,7 +15,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/creator-channels")
|
||||
class CreatorChannelSeriesController(
|
||||
private val creatorChannelSeriesFacade: CreatorChannelSeriesFacade
|
||||
private val creatorChannelSeriesFacade: CreatorChannelSeriesFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/{creatorId}/series")
|
||||
fun getSeriesTab(
|
||||
@@ -30,7 +32,8 @@ class CreatorChannelSeriesController(
|
||||
viewer = requireMember(member),
|
||||
sort = sort,
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
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.creator.channel.series.application.CreatorChannelSeriesQueryService
|
||||
@@ -18,7 +19,8 @@ class CreatorChannelSeriesFacade(
|
||||
sort: String?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelSeriesTabResponse {
|
||||
return CreatorChannelSeriesTabResponse.from(
|
||||
creatorChannelSeriesQueryService.getSeriesTab(
|
||||
@@ -27,7 +29,8 @@ class CreatorChannelSeriesFacade(
|
||||
sort = sort,
|
||||
page = page,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
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.GiftApplicationResponse
|
||||
@@ -32,7 +33,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RequestMapping("/api/v2/gifts")
|
||||
class GiftController(
|
||||
private val queryService: GiftQueryService,
|
||||
private val commandService: GiftCommandService
|
||||
private val commandService: GiftCommandService,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/form-options")
|
||||
fun getFormOptions() = ApiResponse.ok(GiftFormOptionsResponse.from(queryService.getFormOptions()))
|
||||
@@ -49,7 +51,8 @@ class GiftController(
|
||||
memberId = member?.id ?: throw SodaException(messageKey = "common.error.bad_credentials"),
|
||||
type = type,
|
||||
page = page,
|
||||
size = size
|
||||
size = size,
|
||||
lang = langContext.lang
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -62,7 +65,8 @@ class GiftController(
|
||||
GiftDetailResponse.from(
|
||||
queryService.getGiftDetail(
|
||||
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
|
||||
|
||||
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.v2.api.home.application.HomeCreatorRankingFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/home/rankings")
|
||||
class CreatorRankingController(
|
||||
private val homeCreatorRankingFacade: HomeCreatorRankingFacade
|
||||
private val homeCreatorRankingFacade: HomeCreatorRankingFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping("/creators")
|
||||
fun getCreatorRankings(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = 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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
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.dto.recommendation.FollowRecommendedCreatorsRequest
|
||||
@@ -18,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RequestMapping("/api/v2/home/recommendations")
|
||||
class HomeRecommendationController(
|
||||
private val homeRecommendationFacade: HomeRecommendationFacade,
|
||||
private val recommendedCreatorFollowService: RecommendedCreatorFollowService
|
||||
private val recommendedCreatorFollowService: RecommendedCreatorFollowService,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getHomeRecommendations(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
ApiResponse.ok(homeRecommendationFacade.getHomeRecommendations(member))
|
||||
ApiResponse.ok(homeRecommendationFacade.getHomeRecommendations(member, langContext.lang))
|
||||
}
|
||||
|
||||
@GetMapping("/lives")
|
||||
@@ -37,7 +39,8 @@ class HomeRecommendationController(
|
||||
homeRecommendationFacade.getLives(
|
||||
requireMember(member),
|
||||
normalizePage(page),
|
||||
normalizeSize(size)
|
||||
normalizeSize(size),
|
||||
langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -52,7 +55,8 @@ class HomeRecommendationController(
|
||||
homeRecommendationFacade.getRecentDebutCreators(
|
||||
requireMember(member),
|
||||
normalizePage(page),
|
||||
normalizeSize(size)
|
||||
normalizeSize(size),
|
||||
langContext.lang
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,5 +1,6 @@
|
||||
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.v2.api.home.dto.ranking.CreatorRankingResponse
|
||||
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingQueryService
|
||||
@@ -9,9 +10,8 @@ import org.springframework.stereotype.Component
|
||||
class HomeCreatorRankingFacade(
|
||||
private val creatorRankingQueryService: CreatorRankingQueryService
|
||||
) {
|
||||
fun getCreatorRankings(member: Member?): CreatorRankingResponse {
|
||||
return CreatorRankingResponse.from(
|
||||
creatorRankingQueryService.getCreatorRankings(viewerMemberId = member?.id)
|
||||
)
|
||||
fun getCreatorRankings(member: Member?, lang: Lang): CreatorRankingResponse {
|
||||
val result = creatorRankingQueryService.getCreatorRankings(viewerMemberId = member?.id, lang = lang)
|
||||
return CreatorRankingResponse.from(result)
|
||||
}
|
||||
}
|
||||
|
||||
+34
-19
@@ -44,21 +44,23 @@ class HomeRecommendationFacade(
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
fun getHomeRecommendations(member: Member?): HomeRecommendationResponse {
|
||||
fun getHomeRecommendations(member: Member?, lang: Lang): HomeRecommendationResponse {
|
||||
val startedAt = System.currentTimeMillis()
|
||||
return runCatching {
|
||||
val now = LocalDateTime.now()
|
||||
val includeAdult = resolveAdultVisibility(member)
|
||||
val bannerLang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
|
||||
|
||||
HomeRecommendationResponse(
|
||||
lives = queryService.findLiveRecommendations(
|
||||
limit = HOME_LIVE_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultLives = includeAdult,
|
||||
isViewerCreator = member?.role == MemberRole.CREATOR,
|
||||
effectiveViewerGender = member?.effectiveGender()
|
||||
).map { it.toItem() },
|
||||
val lives = queryService.findLiveRecommendations(
|
||||
limit = HOME_LIVE_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultLives = includeAdult,
|
||||
isViewerCreator = member?.role == MemberRole.CREATOR,
|
||||
effectiveViewerGender = member?.effectiveGender(),
|
||||
lang = lang
|
||||
)
|
||||
val response = HomeRecommendationResponse(
|
||||
lives = lives.map { it.toItem() },
|
||||
banners = queryService.findHomeBanners(
|
||||
limit = HOME_BANNER_LIMIT,
|
||||
memberId = member?.id,
|
||||
@@ -69,21 +71,24 @@ class HomeRecommendationFacade(
|
||||
recentlyActiveCreators = queryService.findRecentlyActiveCreators(
|
||||
HOME_ACTIVE_CREATOR_LIMIT,
|
||||
member?.id,
|
||||
includeAdult
|
||||
includeAdult,
|
||||
lang
|
||||
)
|
||||
.map { it.toItem() },
|
||||
recentDebutCreators = queryService.findRecentDebutCreators(
|
||||
now,
|
||||
limit = HOME_RECENT_DEBUT_CREATOR_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultContents = includeAdult
|
||||
includeAdultContents = includeAdult,
|
||||
lang = lang
|
||||
)
|
||||
.map { it.toItem() },
|
||||
firstAudioContents = queryService.findFirstAudioContents(
|
||||
now,
|
||||
limit = HOME_FIRST_AUDIO_CONTENT_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultContents = includeAdult
|
||||
includeAdultContents = includeAdult,
|
||||
lang = lang
|
||||
)
|
||||
.map { it.toItem() },
|
||||
aiCharacters = queryService.findAiCharacterRecommendations(
|
||||
@@ -94,16 +99,19 @@ class HomeRecommendationFacade(
|
||||
memberId = member?.id,
|
||||
includeAdultGenres = includeAdult,
|
||||
genreLimit = HOME_GENRE_CREATOR_GENRE_LIMIT,
|
||||
creatorLimit = HOME_GENRE_CREATOR_CREATOR_LIMIT
|
||||
creatorLimit = HOME_GENRE_CREATOR_CREATOR_LIMIT,
|
||||
lang = lang
|
||||
).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() },
|
||||
popularCommunityPosts = queryService.findPopularCommunityRecommendations(
|
||||
limit = HOME_POPULAR_COMMUNITY_LIMIT,
|
||||
memberId = member?.id,
|
||||
includeAdultCommunities = includeAdult
|
||||
includeAdultCommunities = includeAdult,
|
||||
lang = lang
|
||||
).map { it.toItem() }
|
||||
)
|
||||
response
|
||||
}.onSuccess { response ->
|
||||
log.info(
|
||||
"event=home_recommendations_query_success memberId={} elapsedMs={} emptySections={}",
|
||||
@@ -122,7 +130,7 @@ class HomeRecommendationFacade(
|
||||
}.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()
|
||||
return runCatching {
|
||||
val fetched = queryService.findLiveRecommendations(
|
||||
@@ -131,7 +139,8 @@ class HomeRecommendationFacade(
|
||||
memberId = member.id,
|
||||
includeAdultLives = resolveAdultVisibility(member),
|
||||
isViewerCreator = member.role == MemberRole.CREATOR,
|
||||
effectiveViewerGender = member.effectiveGender()
|
||||
effectiveViewerGender = member.effectiveGender(),
|
||||
lang = lang
|
||||
)
|
||||
fetched.toPage(page, size) { it.toItem() }
|
||||
}.onSuccess {
|
||||
@@ -141,7 +150,12 @@ class HomeRecommendationFacade(
|
||||
}.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()
|
||||
return runCatching {
|
||||
val fetched = queryService.findRecentDebutCreators(
|
||||
@@ -149,7 +163,8 @@ class HomeRecommendationFacade(
|
||||
offset = page.toOffset(size),
|
||||
limit = size + 1,
|
||||
memberId = member.id,
|
||||
includeAdultContents = resolveAdultVisibility(member)
|
||||
includeAdultContents = resolveAdultVisibility(member),
|
||||
lang = lang
|
||||
)
|
||||
fetched.toPage(page, size) { it.toItem() }
|
||||
}.onSuccess {
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.home.following.adapter.`in`.web
|
||||
|
||||
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.v2.api.home.following.application.HomeFollowingFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -11,12 +12,13 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/home/following")
|
||||
class HomeFollowingController(
|
||||
private val facade: HomeFollowingFacade
|
||||
private val facade: HomeFollowingFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getFollowingTab(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = 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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.home.following.dto.HomeFollowingTabResponse
|
||||
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
||||
@@ -11,14 +12,15 @@ class HomeFollowingFacade(
|
||||
private val homeFollowingQueryService: HomeFollowingQueryService,
|
||||
private val chatRoomListService: ChatRoomListService
|
||||
) {
|
||||
fun getFollowingTab(member: Member?): HomeFollowingTabResponse {
|
||||
fun getFollowingTab(member: Member?, lang: Lang): HomeFollowingTabResponse {
|
||||
if (member == null) {
|
||||
return HomeFollowingTabResponse.loginRequired()
|
||||
}
|
||||
|
||||
val home = homeFollowingQueryService.findHomeFollowing(member)
|
||||
val home = homeFollowingQueryService.findHomeFollowing(member, lang)
|
||||
val recentChats = chatRoomListService.getRooms(
|
||||
member,
|
||||
lang = lang,
|
||||
filter = "ALL",
|
||||
cursor = null,
|
||||
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.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacade
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
@@ -13,14 +14,15 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/home/on-air-lives")
|
||||
class HomeOnAirLiveController(
|
||||
private val homeOnAirLiveFacade: HomeOnAirLiveFacade
|
||||
private val homeOnAirLiveFacade: HomeOnAirLiveFacade,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getOnAirLives(
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
ApiResponse.ok(homeOnAirLiveFacade.getOnAirLives(requireMember(member), page))
|
||||
ApiResponse.ok(homeOnAirLiveFacade.getOnAirLives(requireMember(member), page, langContext.lang))
|
||||
}
|
||||
|
||||
private fun requireMember(member: Member?): Member {
|
||||
|
||||
+6
-3
@@ -1,5 +1,6 @@
|
||||
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.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
@@ -20,7 +21,7 @@ class HomeOnAirLiveFacade(
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
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 fetched = queryService.findLiveRecommendations(
|
||||
offset = normalizedPage.toLong() * PAGE_SIZE,
|
||||
@@ -28,9 +29,11 @@ class HomeOnAirLiveFacade(
|
||||
memberId = member.id,
|
||||
includeAdultLives = memberContentPreferenceService.canViewAdultContent(member),
|
||||
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(
|
||||
items = items,
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.chat.controller
|
||||
|
||||
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.v2.chat.dto.ChatRoomListPageResponse
|
||||
import kr.co.vividnext.sodalive.v2.chat.service.ChatRoomListService
|
||||
@@ -13,7 +14,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController
|
||||
@RequestMapping("/api/v2/chat/rooms")
|
||||
class ChatRoomListController(
|
||||
private val service: ChatRoomListService
|
||||
private val service: ChatRoomListService,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@GetMapping
|
||||
fun getRooms(
|
||||
@@ -23,6 +25,6 @@ class ChatRoomListController(
|
||||
@RequestParam(defaultValue = "30") limit: Int
|
||||
) = run {
|
||||
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.repository.ChatRoomRepository
|
||||
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.v2.api.home.dto.recommendation.toUtcIso
|
||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
||||
@@ -26,6 +27,7 @@ class ChatRoomListService(
|
||||
) {
|
||||
fun getRooms(
|
||||
member: Member,
|
||||
lang: Lang,
|
||||
filter: String = ChatRoomListFilter.ALL.name,
|
||||
cursor: String? = null,
|
||||
limit: Int = DEFAULT_LIMIT,
|
||||
@@ -50,6 +52,7 @@ class ChatRoomListService(
|
||||
parsedCursor?.chatType,
|
||||
parsedCursor?.roomId,
|
||||
followedCreatorsOnly,
|
||||
lang,
|
||||
pageable
|
||||
)
|
||||
}
|
||||
@@ -70,6 +73,7 @@ class ChatRoomListService(
|
||||
parsedCursor?.chatType,
|
||||
parsedCursor?.roomId,
|
||||
followedCreatorsOnly,
|
||||
lang,
|
||||
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.QSeriesContent.seriesContent
|
||||
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.member
|
||||
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.content.all.domain.MainContentAllAudio
|
||||
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.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
@@ -64,7 +66,10 @@ class DefaultMainContentAllQueryRepository(
|
||||
onlyFree: Boolean,
|
||||
onlyPointAvailable: Boolean
|
||||
): 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()
|
||||
|
||||
val contentIds = rows.map { it.get(audioContent.id)!! }
|
||||
@@ -85,7 +90,8 @@ class DefaultMainContentAllQueryRepository(
|
||||
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
||||
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
||||
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()
|
||||
|
||||
val seriesTranslation = QSeriesTranslation("mainContentAllSeriesTranslation")
|
||||
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "mainContentAllSeriesCreatorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
series.id,
|
||||
series.title,
|
||||
seriesTranslation.renderedPayload,
|
||||
series.coverImage,
|
||||
member.nickname,
|
||||
member.id,
|
||||
creatorName.displayName,
|
||||
series.isOriginal,
|
||||
series.isAdult
|
||||
)
|
||||
.from(series)
|
||||
.join(series.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.leftJoin(seriesTranslation)
|
||||
.on(
|
||||
seriesTranslation.seriesId.eq(series.id),
|
||||
@@ -148,9 +157,10 @@ class DefaultMainContentAllQueryRepository(
|
||||
seriesId = row.get(series.id)!!,
|
||||
title = translatedTitle.takeUnless(String?::isNullOrBlank) ?: row.get(series.title)!!,
|
||||
coverImageUrl = row.get(series.coverImage).toCdnUrl(cloudFrontHost),
|
||||
creatorNickname = row.get(member.nickname)!!,
|
||||
creatorNickname = row.get(creatorName.displayName)!!,
|
||||
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,
|
||||
onlyPaid: Boolean,
|
||||
onlyFree: Boolean,
|
||||
onlyPointAvailable: Boolean
|
||||
onlyPointAvailable: Boolean,
|
||||
creatorName: CreatorNameJoin
|
||||
): List<Tuple> {
|
||||
val query = queryFactory
|
||||
.select(
|
||||
@@ -175,11 +186,12 @@ class DefaultMainContentAllQueryRepository(
|
||||
audioContent.isAdult,
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.member.id,
|
||||
member.nickname,
|
||||
creatorName.displayName,
|
||||
audioContent.releaseDate
|
||||
)
|
||||
.from(audioContent)
|
||||
.join(audioContent.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.join(audioContent.theme, audioContentTheme)
|
||||
.where(audioCondition(memberId, canViewAdultContent, now, onlyPaid, onlyFree, onlyPointAvailable))
|
||||
|
||||
@@ -192,7 +204,7 @@ class DefaultMainContentAllQueryRepository(
|
||||
revenueOrder.audioContent.id.eq(audioContent.id),
|
||||
revenueOrder.isActive.isTrue
|
||||
)
|
||||
.groupByAudioRow()
|
||||
.groupByAudioRow(creatorName)
|
||||
.orderBy(
|
||||
revenueOrder.can.sum().coalesce(0).desc(),
|
||||
audioContent.releaseDate.desc(),
|
||||
@@ -301,7 +313,7 @@ class DefaultMainContentAllQueryRepository(
|
||||
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(
|
||||
audioContent.id,
|
||||
audioContent.title,
|
||||
@@ -311,6 +323,7 @@ class DefaultMainContentAllQueryRepository(
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.member.id,
|
||||
member.nickname,
|
||||
creatorName.name.displayName,
|
||||
audioContent.releaseDate
|
||||
)
|
||||
}
|
||||
|
||||
+14
-7
@@ -1,7 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.all.application
|
||||
|
||||
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.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
@@ -19,8 +19,7 @@ import java.time.LocalDateTime
|
||||
class MainContentAllQueryService(
|
||||
private val queryPort: MainContentAllQueryPort,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val queryPolicy: MainContentAllQueryPolicy = MainContentAllQueryPolicy(),
|
||||
private val langContext: LangContext
|
||||
private val queryPolicy: MainContentAllQueryPolicy = MainContentAllQueryPolicy()
|
||||
) {
|
||||
fun getContents(
|
||||
type: String?,
|
||||
@@ -28,7 +27,8 @@ class MainContentAllQueryService(
|
||||
dayOfWeek: String?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
member: Member?
|
||||
member: Member?,
|
||||
lang: Lang
|
||||
): MainContentAll {
|
||||
val resolvedType = queryPolicy.resolveType(type)
|
||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||
@@ -47,6 +47,7 @@ class MainContentAllQueryService(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
lang = lang,
|
||||
onlyPaid = true
|
||||
)
|
||||
|
||||
@@ -58,6 +59,7 @@ class MainContentAllQueryService(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
lang = lang,
|
||||
onlyFree = true
|
||||
)
|
||||
|
||||
@@ -69,6 +71,7 @@ class MainContentAllQueryService(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
lang = lang,
|
||||
onlyPointAvailable = true
|
||||
)
|
||||
|
||||
@@ -79,7 +82,8 @@ class MainContentAllQueryService(
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now
|
||||
now = now,
|
||||
lang = lang
|
||||
)
|
||||
|
||||
MainContentAllType.ORIGINAL -> getSeriesContents(
|
||||
@@ -90,6 +94,7 @@ class MainContentAllQueryService(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
lang = lang,
|
||||
onlyOriginal = true
|
||||
)
|
||||
}
|
||||
@@ -103,6 +108,7 @@ class MainContentAllQueryService(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
lang: Lang,
|
||||
onlyPaid: Boolean = false,
|
||||
onlyFree: Boolean = false,
|
||||
onlyPointAvailable: Boolean = false
|
||||
@@ -122,7 +128,7 @@ class MainContentAllQueryService(
|
||||
sort = sort,
|
||||
offset = page.offset,
|
||||
limit = page.size + 1,
|
||||
locale = langContext.lang.code,
|
||||
locale = lang.code,
|
||||
onlyPaid = onlyPaid,
|
||||
onlyFree = onlyFree,
|
||||
onlyPointAvailable = onlyPointAvailable
|
||||
@@ -148,6 +154,7 @@ class MainContentAllQueryService(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
lang: Lang,
|
||||
onlyOriginal: Boolean = false
|
||||
): MainContentAll {
|
||||
val totalCount = queryPort.countSeries(memberId, canViewAdultContent, now, onlyOriginal, dayOfWeek)
|
||||
@@ -160,7 +167,7 @@ class MainContentAllQueryService(
|
||||
limit = page.size + 1,
|
||||
onlyOriginal = onlyOriginal,
|
||||
dayOfWeek = dayOfWeek,
|
||||
locale = langContext.lang.code
|
||||
locale = lang.code
|
||||
)
|
||||
|
||||
return MainContentAll(
|
||||
|
||||
@@ -23,7 +23,8 @@ data class MainContentAllAudio(
|
||||
val isPointAvailable: Boolean,
|
||||
val isFirstContent: Boolean,
|
||||
val isOriginalSeries: Boolean,
|
||||
val creatorNickname: String
|
||||
val creatorNickname: String,
|
||||
val creatorMemberId: Long
|
||||
)
|
||||
|
||||
data class MainContentAllSeries(
|
||||
@@ -32,5 +33,6 @@ data class MainContentAllSeries(
|
||||
val coverImageUrl: String?,
|
||||
val creatorNickname: String,
|
||||
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
|
||||
|
||||
import com.querydsl.jpa.JPAExpressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
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.port.out.AudioRankingSnapshotPort
|
||||
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.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
@@ -11,14 +17,34 @@ import java.time.LocalDateTime
|
||||
@Repository
|
||||
class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||
private val repository: AudioRankingSnapshotRepository,
|
||||
private val contentTranslationRepository: ContentTranslationRepository
|
||||
private val contentTranslationRepository: ContentTranslationRepository,
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : AudioRankingSnapshotPort {
|
||||
override fun findLatestVisibleSnapshots(
|
||||
rankingType: AudioRankingType,
|
||||
nowUtc: LocalDateTime,
|
||||
locale: String
|
||||
): 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(
|
||||
@@ -31,7 +57,7 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||
rankingType = rankingType.name,
|
||||
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
|
||||
nowUtc = nowUtc
|
||||
).toRecords(locale)
|
||||
).map { it.toRecord() }.translateTitles(locale)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -50,7 +76,7 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||
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()
|
||||
val translatedTitleByContentId = contentTranslationRepository.findByContentIdInAndLocale(
|
||||
map { it.contentId },
|
||||
@@ -58,19 +84,19 @@ class DefaultAudioRankingSnapshotPersistenceAdapter(
|
||||
).mapNotNull { translation ->
|
||||
translation.renderedPayload.title.takeIf { it.isNotBlank() }?.let { translation.contentId to it }
|
||||
}.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(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
contentId = contentId,
|
||||
title = displayTitle,
|
||||
title = title,
|
||||
creatorMemberId = creatorMemberId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorNickname = displayCreatorNickname,
|
||||
coverImageUrl = coverImageUrl,
|
||||
releaseDate = releaseDate,
|
||||
isAdult = isAdult,
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
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.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
|
||||
@@ -19,7 +19,6 @@ import java.time.ZonedDateTime
|
||||
@Service
|
||||
class AudioRankingQueryService(
|
||||
private val snapshotPort: AudioRankingSnapshotPort,
|
||||
private val langContext: LangContext,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val blockPort: AudioRankingBlockPort,
|
||||
private val jobService: AudioRankingSnapshotJobService,
|
||||
@@ -29,9 +28,9 @@ class AudioRankingQueryService(
|
||||
) {
|
||||
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 locale = langContext.lang.code
|
||||
val locale = lang.code
|
||||
val latestSnapshots = findLatestVisibleSnapshots(type, nowUtc, locale)
|
||||
if (latestSnapshots.isEmpty()) {
|
||||
return AudioRanking(showRankChange = false, type = type, items = emptyList())
|
||||
@@ -114,7 +113,8 @@ class AudioRankingQueryService(
|
||||
rank = rank,
|
||||
rankChange = if (showRankChange && previousRank != null) previousRank - rank else 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 rankChange: Int?,
|
||||
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.CommentedAudio
|
||||
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.port.out.RecommendationSnapshotRecord
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
@@ -120,7 +121,7 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
now: LocalDateTime,
|
||||
locale: String
|
||||
): 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())
|
||||
}
|
||||
return rows.toAudioCards(now, canViewAdultContent, locale)
|
||||
@@ -134,7 +135,7 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
locale: String
|
||||
): List<AudioCard> {
|
||||
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())
|
||||
}
|
||||
return rows.toAudioCards(now, canViewAdultContent, locale)
|
||||
@@ -152,6 +153,7 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
memberId,
|
||||
canViewAdultContent,
|
||||
now,
|
||||
locale,
|
||||
audioContent.isPointAvailable.isTrue.and(audioContent.price.gt(0))
|
||||
) {
|
||||
orderBy(randomTieBreaker.asc()).limit(limit.toLong())
|
||||
@@ -168,7 +170,7 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
): List<AudioCard> {
|
||||
if (contentIds.isEmpty()) return emptyList()
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -444,9 +446,11 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
locale: String,
|
||||
extraCondition: BooleanExpression? = null,
|
||||
customize: com.querydsl.jpa.impl.JPAQuery<Tuple>.() -> com.querydsl.jpa.impl.JPAQuery<Tuple>
|
||||
): List<Tuple> {
|
||||
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "audioRecommendationCreatorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
audioContent.id,
|
||||
@@ -457,10 +461,11 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
audioContent.isAdult,
|
||||
audioContent.isPointAvailable,
|
||||
audioContent.member.id,
|
||||
member.nickname
|
||||
creatorName.displayName
|
||||
)
|
||||
.from(audioContent)
|
||||
.join(audioContent.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.join(audioContent.theme, audioContentTheme)
|
||||
.where(publicAudioCondition(memberId, canViewAdultContent, now), extraCondition)
|
||||
.customize()
|
||||
@@ -478,6 +483,7 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
val firstContentIdByCreatorId = firstAudioContentIds(creatorIds, now, canViewAdultContent)
|
||||
val isOriginalSeriesByContentId = originalSeriesFlags(contentIds)
|
||||
val translatedTitleByContentId = translatedTitles(contentIds, locale)
|
||||
val creatorName = CreatorNameJoin(member, Lang.fromCode(locale), "audioRecommendationCreatorName")
|
||||
return map { row ->
|
||||
val contentId = row.get(audioContent.id)!!
|
||||
val creatorId = row.get(audioContent.member.id)!!
|
||||
@@ -491,7 +497,8 @@ class DefaultAudioRecommendationQueryRepository(
|
||||
isPointAvailable = row.get(audioContent.isPointAvailable)!!,
|
||||
isFirstContent = firstContentIdByCreatorId[creatorId] == contentId,
|
||||
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
|
||||
|
||||
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.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
||||
@@ -18,16 +17,15 @@ import java.time.ZoneId
|
||||
class AudioRecommendationQueryService(
|
||||
private val queryPort: AudioRecommendationQueryPort,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val snapshotFallbackService: AudioRecommendationSnapshotFallbackService,
|
||||
private val langContext: LangContext
|
||||
private val snapshotFallbackService: AudioRecommendationSnapshotFallbackService
|
||||
) {
|
||||
fun getRecommendations(member: Member?): AudioRecommendations {
|
||||
fun getRecommendations(member: Member?, lang: Lang): AudioRecommendations {
|
||||
val now = LocalDateTime.now()
|
||||
val fallbackNow = LocalDateTime.now(KST_ZONE)
|
||||
val canViewAdultContent = canViewAdultContent(member)
|
||||
val visibility = if (canViewAdultContent) AudioRecommendationVisibility.ALL else AudioRecommendationVisibility.SAFE
|
||||
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 newAndHotSectionType = newAndHotSectionType(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 fallbackNow = LocalDateTime.now(KST_ZONE)
|
||||
val canViewAdultContent = canViewAdultContent(member)
|
||||
@@ -90,7 +88,7 @@ class AudioRecommendationQueryService(
|
||||
member.id,
|
||||
canViewAdultContent,
|
||||
now,
|
||||
langContext.lang.code
|
||||
lang.code
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -28,7 +28,8 @@ data class AudioCard(
|
||||
val isPointAvailable: Boolean,
|
||||
val isFirstContent: Boolean,
|
||||
val isOriginalSeries: Boolean,
|
||||
val creatorNickname: String
|
||||
val creatorNickname: String,
|
||||
val creatorMemberId: Long
|
||||
)
|
||||
|
||||
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.creator.admin.content.series.QSeries.series
|
||||
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.block.QBlockMember
|
||||
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.CreatorChannelAudioCreatorRecord
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
@@ -28,17 +30,19 @@ import java.time.LocalDateTime
|
||||
class DefaultCreatorChannelAudioQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelAudioCreatorRecord::class.java,
|
||||
member.id,
|
||||
member.role,
|
||||
member.nickname
|
||||
creatorName.displayName
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
|
||||
+5
-3
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.audio.application
|
||||
|
||||
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.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
@@ -39,18 +40,19 @@ class CreatorChannelAudioQueryService(
|
||||
themeId: Long?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelAudioTab {
|
||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||
val audioPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
|
||||
+58
-23
@@ -4,28 +4,37 @@ import com.querydsl.core.Tuple
|
||||
import com.querydsl.core.types.OrderSpecifier
|
||||
import com.querydsl.core.types.dsl.BooleanExpression
|
||||
import com.querydsl.core.types.dsl.CaseBuilder
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
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.comment.QCreatorCommunityComment.creatorCommunityComment
|
||||
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.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.CreatorChannelCommunityCommentRecord
|
||||
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.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorChannelCommunityQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.select(member.id, member.role, member.nickname)
|
||||
.select(member.id, member.role, creatorName.displayName)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
@@ -35,7 +44,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
return CreatorChannelCommunityCreatorRecord(
|
||||
creatorId = creator.get(member.id)!!,
|
||||
role = creator.get(member.role)!!,
|
||||
nickname = creator.get(member.nickname)!!
|
||||
nickname = creator.get(creatorName.displayName)!!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,11 +80,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
viewerId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityPostRecord> {
|
||||
val creatorName = CreatorNameJoin(member, lang, "channelCommunityPostCreatorName")
|
||||
val rows = queryFactory
|
||||
.selectCommunityPostRow()
|
||||
.selectCommunityPostRow(creatorName)
|
||||
.from(creatorCommunity)
|
||||
.join(creatorCommunity.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(communityPostCondition(creatorId, canViewAdultContent))
|
||||
.orderBy(
|
||||
CaseBuilder()
|
||||
@@ -104,11 +117,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
viewerId: Long,
|
||||
isPinned: Boolean,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityPostRecord> {
|
||||
val creatorName = CreatorNameJoin(member, lang, "channelHomePostCreatorName")
|
||||
val rows = queryFactory
|
||||
.selectCommunityPostRow()
|
||||
.selectCommunityPostRow(creatorName)
|
||||
.from(creatorCommunity)
|
||||
.join(creatorCommunity.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
homeCommunityPostCondition(creatorId, viewerId, canViewAdultContent),
|
||||
creatorCommunity.isFixed.eq(isPinned),
|
||||
@@ -124,11 +141,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
override fun findCommunityPost(
|
||||
postId: Long,
|
||||
viewerId: Long,
|
||||
canViewAdultContent: Boolean
|
||||
canViewAdultContent: Boolean,
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityPostRecord? {
|
||||
val creatorName = CreatorNameJoin(member, lang, "channelPostDetailCreatorName")
|
||||
val rows = queryFactory
|
||||
.selectCommunityPostRow()
|
||||
.selectCommunityPostRow(creatorName)
|
||||
.from(creatorCommunity)
|
||||
.join(creatorCommunity.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(communityPostByIdCondition(postId, canViewAdultContent))
|
||||
.fetch()
|
||||
|
||||
@@ -155,11 +176,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
viewerId: Long,
|
||||
creatorId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord> {
|
||||
val writerName = CreatorNameJoin(member, lang, "channelCommentWriterName")
|
||||
return queryFactory
|
||||
.selectCommunityCommentRow()
|
||||
.selectCommunityCommentRow(writerName)
|
||||
.from(creatorCommunityComment)
|
||||
.join(creatorCommunityComment.member, member)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(rootCommentCondition(postId, creatorId, viewerId))
|
||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||
.offset(offset)
|
||||
@@ -170,13 +195,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
|
||||
override fun findLatestRepliesByCommentIds(
|
||||
commentIds: List<Long>,
|
||||
viewerId: Long
|
||||
viewerId: Long,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord> {
|
||||
if (commentIds.isEmpty()) return emptyList()
|
||||
val writerName = CreatorNameJoin(member, lang, "channelLatestReplyWriterName")
|
||||
|
||||
return queryFactory
|
||||
.selectCommunityCommentRow()
|
||||
.selectCommunityCommentRow(writerName)
|
||||
.from(creatorCommunityComment)
|
||||
.join(creatorCommunityComment.member, member)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(replyCondition(commentIds, viewerId))
|
||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||
.fetch()
|
||||
@@ -224,11 +253,15 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
commentId: Long,
|
||||
viewerId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord> {
|
||||
val writerName = CreatorNameJoin(member, lang, "channelReplyWriterName")
|
||||
return queryFactory
|
||||
.selectCommunityCommentRow()
|
||||
.selectCommunityCommentRow(writerName)
|
||||
.from(creatorCommunityComment)
|
||||
.join(creatorCommunityComment.member, member)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(replyCondition(commentId, viewerId))
|
||||
.orderBy(creatorCommunityComment.createdAt.desc(), creatorCommunityComment.id.desc())
|
||||
.offset(offset)
|
||||
@@ -237,10 +270,10 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
.map { it.toCommunityCommentRecord() }
|
||||
}
|
||||
|
||||
private fun JPAQueryFactory.selectCommunityPostRow() = select(
|
||||
private fun JPAQueryFactory.selectCommunityPostRow(creatorName: CreatorNameJoin) = select(
|
||||
creatorCommunity.id,
|
||||
creatorCommunity.member.id,
|
||||
creatorCommunity.member.nickname,
|
||||
creatorName.displayName.`as`(postCreatorDisplayName),
|
||||
creatorCommunity.member.profileImage,
|
||||
creatorCommunity.imagePath,
|
||||
creatorCommunity.audioPath,
|
||||
@@ -252,16 +285,17 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
creatorCommunity.isCommentAvailable
|
||||
)
|
||||
|
||||
private fun JPAQueryFactory.selectCommunityCommentRow() = select(
|
||||
private fun JPAQueryFactory.selectCommunityCommentRow(writerName: CreatorNameJoin) = select(
|
||||
creatorCommunityComment.id,
|
||||
creatorCommunityComment.parent.id,
|
||||
creatorCommunityComment.creatorCommunity.id,
|
||||
creatorCommunityComment.member.id,
|
||||
creatorCommunityComment.member.profileImage,
|
||||
creatorCommunityComment.member.nickname,
|
||||
writerName.displayName.`as`(commentWriterDisplayName),
|
||||
creatorCommunityComment.comment,
|
||||
creatorCommunityComment.isSecret,
|
||||
creatorCommunityComment.createdAt
|
||||
creatorCommunityComment.createdAt,
|
||||
writerName.name.id.isNotNull.`as`(localizedWriterName)
|
||||
)
|
||||
|
||||
private fun communityPostCondition(creatorId: Long, canViewAdultContent: Boolean): BooleanExpression {
|
||||
@@ -519,7 +553,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
get() = get(creatorCommunity.member.id)!!
|
||||
|
||||
private val Tuple.creatorNickname: String
|
||||
get() = get(creatorCommunity.member.nickname)!!
|
||||
get() = get(postCreatorDisplayName)!!
|
||||
|
||||
private val Tuple.creatorProfilePath: String?
|
||||
get() = get(creatorCommunity.member.profileImage)
|
||||
@@ -555,10 +589,11 @@ class DefaultCreatorChannelCommunityQueryRepository(
|
||||
postId = get(creatorCommunityComment.creatorCommunity.id)!!,
|
||||
writerId = get(creatorCommunityComment.member.id)!!,
|
||||
writerProfilePath = get(creatorCommunityComment.member.profileImage),
|
||||
writerNickname = get(creatorCommunityComment.member.nickname)!!,
|
||||
writerNickname = get(commentWriterDisplayName)!!,
|
||||
content = get(creatorCommunityComment.comment)!!,
|
||||
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.common.SodaException
|
||||
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.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
@@ -46,17 +47,18 @@ class CreatorChannelCommunityQueryService(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityTab {
|
||||
val communityPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||
}
|
||||
@@ -69,7 +71,8 @@ class CreatorChannelCommunityQueryService(
|
||||
viewerId = viewerId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
offset = communityPage.offset,
|
||||
limit = communityPage.fetchLimit
|
||||
limit = communityPage.fetchLimit,
|
||||
lang = lang
|
||||
)
|
||||
val communityPosts = queryPolicy.limitItems(fetchedPosts, communityPage)
|
||||
val displayContents = creatorCommunityTranslationService
|
||||
@@ -94,7 +97,8 @@ class CreatorChannelCommunityQueryService(
|
||||
viewerId: Long,
|
||||
isPinned: Boolean,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityPost> {
|
||||
val posts = queryPortProvider.getObject()
|
||||
.findHomeCommunityPosts(
|
||||
@@ -102,7 +106,8 @@ class CreatorChannelCommunityQueryService(
|
||||
viewerId = viewerId,
|
||||
isPinned = isPinned,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
limit = limit
|
||||
limit = limit,
|
||||
lang = lang
|
||||
)
|
||||
val displayContents = creatorCommunityTranslationService
|
||||
.findDisplayContents(posts.map { it.postId }, langContext.lang.code)
|
||||
@@ -115,12 +120,13 @@ class CreatorChannelCommunityQueryService(
|
||||
fun getCommunityPostDetail(
|
||||
postId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityPostDetail {
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
val viewerId = viewer.id!!
|
||||
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)
|
||||
|
||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||
@@ -134,7 +140,7 @@ class CreatorChannelCommunityQueryService(
|
||||
|
||||
return CreatorChannelCommunityPostDetail(
|
||||
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,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityComments {
|
||||
val communityPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
val viewerId = viewer.id!!
|
||||
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)
|
||||
|
||||
validateNotBlocked(queryPort, viewerId, postRecord.creatorId)
|
||||
@@ -168,12 +175,14 @@ class CreatorChannelCommunityQueryService(
|
||||
viewerId = viewerId,
|
||||
creatorId = postRecord.creatorId,
|
||||
offset = communityPage.offset,
|
||||
limit = communityPage.fetchLimit
|
||||
limit = communityPage.fetchLimit,
|
||||
lang = lang
|
||||
)
|
||||
val limitedComments = queryPolicy.limitItems(fetchedComments, communityPage)
|
||||
val latestReplies = queryPort.findLatestRepliesByCommentIds(
|
||||
commentIds = limitedComments.map { it.commentId },
|
||||
viewerId = viewerId
|
||||
viewerId = viewerId,
|
||||
lang = lang
|
||||
).associateBy { it.parentCommentId }
|
||||
|
||||
return CreatorChannelCommunityComments(
|
||||
@@ -193,7 +202,8 @@ class CreatorChannelCommunityQueryService(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityReplies {
|
||||
val communityPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
@@ -217,7 +227,8 @@ class CreatorChannelCommunityQueryService(
|
||||
commentId = commentId,
|
||||
viewerId = viewerId,
|
||||
offset = communityPage.offset,
|
||||
limit = communityPage.fetchLimit
|
||||
limit = communityPage.fetchLimit,
|
||||
lang = lang
|
||||
)
|
||||
|
||||
return CreatorChannelCommunityReplies(
|
||||
@@ -274,7 +285,7 @@ class CreatorChannelCommunityQueryService(
|
||||
commentId = commentId,
|
||||
writerId = writerId,
|
||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||
content = content,
|
||||
isSecret = isSecret,
|
||||
createdAt = createdAt,
|
||||
@@ -287,7 +298,7 @@ class CreatorChannelCommunityQueryService(
|
||||
commentId = commentId,
|
||||
writerId = writerId,
|
||||
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||
content = content,
|
||||
createdAt = createdAt
|
||||
)
|
||||
|
||||
+16
-8
@@ -1,10 +1,11 @@
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
@@ -19,7 +20,8 @@ interface CreatorChannelCommunityQueryPort {
|
||||
viewerId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityPostRecord>
|
||||
|
||||
fun findHomeCommunityPosts(
|
||||
@@ -27,13 +29,15 @@ interface CreatorChannelCommunityQueryPort {
|
||||
viewerId: Long,
|
||||
isPinned: Boolean,
|
||||
canViewAdultContent: Boolean,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityPostRecord>
|
||||
|
||||
fun findCommunityPost(
|
||||
postId: Long,
|
||||
viewerId: Long,
|
||||
canViewAdultContent: Boolean
|
||||
canViewAdultContent: Boolean,
|
||||
lang: Lang
|
||||
): CreatorChannelCommunityPostRecord?
|
||||
|
||||
fun countCommunityComments(
|
||||
@@ -47,12 +51,14 @@ interface CreatorChannelCommunityQueryPort {
|
||||
viewerId: Long,
|
||||
creatorId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord>
|
||||
|
||||
fun findLatestRepliesByCommentIds(
|
||||
commentIds: List<Long>,
|
||||
viewerId: Long
|
||||
viewerId: Long,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord>
|
||||
|
||||
fun findRootCommentContext(
|
||||
@@ -70,7 +76,8 @@ interface CreatorChannelCommunityQueryPort {
|
||||
commentId: Long,
|
||||
viewerId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelCommunityCommentRecord>
|
||||
}
|
||||
|
||||
@@ -107,7 +114,8 @@ data class CreatorChannelCommunityCommentRecord(
|
||||
val writerNickname: String,
|
||||
val content: String,
|
||||
val isSecret: Boolean,
|
||||
val createdAt: LocalDateTime
|
||||
val createdAt: LocalDateTime,
|
||||
val isLocalizedName: Boolean = false
|
||||
)
|
||||
|
||||
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.jpa.impl.JPAQueryFactory
|
||||
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.block.QBlockMember
|
||||
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.CreatorChannelDonationRecord
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@@ -18,16 +20,18 @@ class DefaultCreatorChannelDonationQueryRepository(
|
||||
) : CreatorChannelDonationQueryRepository {
|
||||
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
|
||||
.select(
|
||||
member.id,
|
||||
member.role,
|
||||
member.nickname,
|
||||
creatorName.displayName,
|
||||
member.isVisibleDonationRank,
|
||||
member.donationRankingPeriod
|
||||
)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
@@ -37,7 +41,7 @@ class DefaultCreatorChannelDonationQueryRepository(
|
||||
return CreatorChannelDonationCreatorRecord(
|
||||
creatorId = creator.get(member.id)!!,
|
||||
role = creator.get(member.role)!!,
|
||||
nickname = creator.get(member.nickname)!!,
|
||||
nickname = creator.get(creatorName.displayName)!!,
|
||||
isVisibleDonationRank = creator.get(member.isVisibleDonationRank)!!,
|
||||
donationRankingPeriod = creator.get(member.donationRankingPeriod)
|
||||
)
|
||||
@@ -75,20 +79,26 @@ class DefaultCreatorChannelDonationQueryRepository(
|
||||
viewerId: Long,
|
||||
now: LocalDateTime,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelDonationRecord> {
|
||||
val donorName = CreatorNameJoin(member, lang, "channelDonationDonorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelDonationRecord::class.java,
|
||||
channelDonationMessage.member.nickname,
|
||||
channelDonationMessage.member.id,
|
||||
donorName.displayName,
|
||||
channelDonationMessage.member.profileImage,
|
||||
channelDonationMessage.can,
|
||||
channelDonationMessage.additionalMessage,
|
||||
channelDonationMessage.createdAt
|
||||
channelDonationMessage.createdAt,
|
||||
donorName.name.id.isNotNull
|
||||
)
|
||||
)
|
||||
.from(channelDonationMessage)
|
||||
.join(channelDonationMessage.member, member)
|
||||
.leftJoin(donorName.name).on(donorName.condition)
|
||||
.where(channelDonationCondition(creatorId, viewerId, now))
|
||||
.orderBy(channelDonationMessage.createdAt.desc(), channelDonationMessage.id.desc())
|
||||
.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.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.member.DonationRankingPeriod
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
@@ -30,7 +30,6 @@ class CreatorChannelDonationQueryService(
|
||||
private val rankingPort: CreatorChannelDonationRankingPort,
|
||||
private val queryPolicy: CreatorChannelDonationQueryPolicy,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
@@ -39,17 +38,18 @@ class CreatorChannelDonationQueryService(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime
|
||||
now: LocalDateTime,
|
||||
lang: Lang
|
||||
): CreatorChannelDonationTab {
|
||||
val donationPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||
}
|
||||
@@ -61,7 +61,8 @@ class CreatorChannelDonationQueryService(
|
||||
viewerId = viewerId,
|
||||
now = now,
|
||||
offset = donationPage.offset,
|
||||
limit = donationPage.fetchLimit
|
||||
limit = donationPage.fetchLimit,
|
||||
lang = lang
|
||||
)
|
||||
|
||||
return CreatorChannelDonationTab(
|
||||
@@ -95,7 +96,8 @@ class CreatorChannelDonationQueryService(
|
||||
}
|
||||
|
||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||
nickname = nickname.removeDeletedNicknamePrefix(),
|
||||
memberId = memberId,
|
||||
nickname = if (isLocalizedName) nickname else nickname.removeDeletedNicknamePrefix(),
|
||||
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
can = can,
|
||||
message = message.orEmpty(),
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ data class CreatorChannelDonationRanking(
|
||||
)
|
||||
|
||||
data class CreatorChannelDonation(
|
||||
val memberId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String,
|
||||
val can: Int,
|
||||
|
||||
+7
-3
@@ -1,11 +1,12 @@
|
||||
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.MemberRole
|
||||
import java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
@@ -20,7 +21,8 @@ interface CreatorChannelDonationQueryPort {
|
||||
viewerId: Long,
|
||||
now: LocalDateTime,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelDonationRecord>
|
||||
}
|
||||
|
||||
@@ -33,9 +35,11 @@ data class CreatorChannelDonationCreatorRecord(
|
||||
)
|
||||
|
||||
data class CreatorChannelDonationRecord(
|
||||
val memberId: Long,
|
||||
val nickname: String,
|
||||
val profileImagePath: String?,
|
||||
val can: Int,
|
||||
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.jpa.impl.JPAQueryFactory
|
||||
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.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.CreatorChannelFanTalkRecord
|
||||
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
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorChannelFanTalkQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.select(member.id, member.role, member.nickname)
|
||||
.select(member.id, member.role, creatorName.displayName)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
@@ -28,7 +32,7 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
||||
return CreatorChannelFanTalkCreatorRecord(
|
||||
creatorId = creator.get(member.id)!!,
|
||||
role = creator.get(member.role)!!,
|
||||
nickname = creator.get(member.nickname)!!
|
||||
nickname = creator.get(creatorName.displayName)!!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,21 +63,26 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
||||
creatorId: Long,
|
||||
viewerId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelFanTalkRecord> {
|
||||
val writerName = CreatorNameJoin(member, lang, "channelFanTalkWriterName")
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelFanTalkRecord::class.java,
|
||||
creatorCheers.id,
|
||||
creatorCheers.member.id,
|
||||
creatorCheers.member.nickname,
|
||||
writerName.displayName,
|
||||
creatorCheers.member.profileImage,
|
||||
creatorCheers.cheers,
|
||||
creatorCheers.createdAt
|
||||
creatorCheers.createdAt,
|
||||
writerName.name.id.isNotNull
|
||||
)
|
||||
)
|
||||
.from(creatorCheers)
|
||||
.join(creatorCheers.member, member)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(fanTalkCondition(creatorId, viewerId))
|
||||
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
||||
.offset(offset)
|
||||
@@ -83,9 +92,11 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
||||
|
||||
override fun findCreatorReplies(
|
||||
creatorId: Long,
|
||||
parentFanTalkIds: List<Long>
|
||||
parentFanTalkIds: List<Long>,
|
||||
lang: Lang
|
||||
): List<CreatorChannelFanTalkReplyRecord> {
|
||||
if (parentFanTalkIds.isEmpty()) return emptyList()
|
||||
val writerName = CreatorNameJoin(member, lang, "channelFanTalkReplyWriterName")
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
@@ -94,13 +105,16 @@ class DefaultCreatorChannelFanTalkQueryRepository(
|
||||
creatorCheers.id,
|
||||
creatorCheers.parent.id,
|
||||
creatorCheers.member.id,
|
||||
creatorCheers.member.nickname,
|
||||
writerName.displayName,
|
||||
creatorCheers.member.profileImage,
|
||||
creatorCheers.cheers,
|
||||
creatorCheers.createdAt
|
||||
creatorCheers.createdAt,
|
||||
writerName.name.id.isNotNull
|
||||
)
|
||||
)
|
||||
.from(creatorCheers)
|
||||
.join(creatorCheers.member, member)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(
|
||||
creatorCheers.creator.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.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.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
@@ -27,7 +27,6 @@ class CreatorChannelFanTalkQueryService(
|
||||
private val queryPortProvider: ObjectProvider<CreatorChannelFanTalkQueryPort>,
|
||||
private val queryPolicy: CreatorChannelFanTalkQueryPolicy,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
@@ -36,17 +35,18 @@ class CreatorChannelFanTalkQueryService(
|
||||
viewer: Member,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelFanTalkTab {
|
||||
val fanTalkPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||
}
|
||||
@@ -57,10 +57,11 @@ class CreatorChannelFanTalkQueryService(
|
||||
creatorId = creatorId,
|
||||
viewerId = viewerId,
|
||||
offset = fanTalkPage.offset,
|
||||
limit = fanTalkPage.fetchLimit
|
||||
limit = fanTalkPage.fetchLimit,
|
||||
lang = lang
|
||||
)
|
||||
val fanTalkRecords = queryPolicy.limitItems(fetchedFanTalks, fanTalkPage)
|
||||
val repliesByParentId = findRepliesByParentId(queryPort, creatorId, fanTalkRecords)
|
||||
val repliesByParentId = findRepliesByParentId(queryPort, creatorId, fanTalkRecords, lang)
|
||||
|
||||
return CreatorChannelFanTalkTab(
|
||||
fanTalkCount = queryPort.countFanTalks(creatorId, viewerId),
|
||||
@@ -80,11 +81,12 @@ class CreatorChannelFanTalkQueryService(
|
||||
private fun findRepliesByParentId(
|
||||
queryPort: CreatorChannelFanTalkQueryPort,
|
||||
creatorId: Long,
|
||||
fanTalkRecords: List<CreatorChannelFanTalkRecord>
|
||||
fanTalkRecords: List<CreatorChannelFanTalkRecord>,
|
||||
lang: Lang
|
||||
): Map<Long, List<CreatorChannelFanTalkReply>> {
|
||||
val parentFanTalkIds = fanTalkRecords.map { it.fanTalkId }
|
||||
if (parentFanTalkIds.isEmpty()) return emptyMap()
|
||||
return queryPort.findCreatorReplies(creatorId, parentFanTalkIds)
|
||||
return queryPort.findCreatorReplies(creatorId, parentFanTalkIds, lang)
|
||||
.groupBy(
|
||||
keySelector = { it.parentFanTalkId },
|
||||
valueTransform = { it.toDomain() }
|
||||
@@ -96,7 +98,7 @@ class CreatorChannelFanTalkQueryService(
|
||||
) = CreatorChannelFanTalk(
|
||||
fanTalkId = fanTalkId,
|
||||
writerId = writerId,
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
content = content,
|
||||
createdAt = createdAt,
|
||||
@@ -106,7 +108,7 @@ class CreatorChannelFanTalkQueryService(
|
||||
private fun CreatorChannelFanTalkReplyRecord.toDomain() = CreatorChannelFanTalkReply(
|
||||
fanTalkId = fanTalkId,
|
||||
writerId = writerId,
|
||||
writerNickname = writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerNickname = if (isLocalizedName) writerNickname else writerNickname.removeDeletedNicknamePrefix(),
|
||||
writerProfileImageUrl = writerProfileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
content = content,
|
||||
createdAt = createdAt
|
||||
|
||||
+10
-5
@@ -1,10 +1,11 @@
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
@@ -14,12 +15,14 @@ interface CreatorChannelFanTalkQueryPort {
|
||||
creatorId: Long,
|
||||
viewerId: Long,
|
||||
offset: Long,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelFanTalkRecord>
|
||||
|
||||
fun findCreatorReplies(
|
||||
creatorId: Long,
|
||||
parentFanTalkIds: List<Long>
|
||||
parentFanTalkIds: List<Long>,
|
||||
lang: Lang
|
||||
): List<CreatorChannelFanTalkReplyRecord>
|
||||
}
|
||||
|
||||
@@ -35,7 +38,8 @@ data class CreatorChannelFanTalkRecord(
|
||||
val writerNickname: String,
|
||||
val writerProfileImagePath: String?,
|
||||
val content: String,
|
||||
val createdAt: LocalDateTime
|
||||
val createdAt: LocalDateTime,
|
||||
val isLocalizedName: Boolean = false
|
||||
)
|
||||
|
||||
data class CreatorChannelFanTalkReplyRecord(
|
||||
@@ -45,5 +49,6 @@ data class CreatorChannelFanTalkReplyRecord(
|
||||
val writerNickname: String,
|
||||
val writerProfileImagePath: 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.channelDonation.QChannelDonationMessage.channelDonationMessage
|
||||
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.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
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.block.QBlockMember
|
||||
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.CreatorChannelSeriesRecord
|
||||
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 java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
@@ -46,10 +49,12 @@ import java.time.temporal.ChronoUnit
|
||||
class DefaultCreatorChannelHomeQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.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)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
@@ -81,7 +86,7 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
creatorId = creator.get(member.id)!!,
|
||||
role = creator.get(member.role)!!,
|
||||
characterId = characterId,
|
||||
nickname = creator.get(member.nickname)!!,
|
||||
nickname = creator.get(creatorName.displayName)!!,
|
||||
profileImagePath = creator.get(member.profileImage),
|
||||
introduce = creator.get(member.introduce)!!,
|
||||
followerCount = queryFactory
|
||||
@@ -172,8 +177,11 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<CreatorChannelDonationRecord> {
|
||||
val donor = QMember("channelHomeDonor")
|
||||
val donorName = CreatorNameJoin(donor, lang, "channelHomeDonorName")
|
||||
val kstZoneId = ZoneId.of("Asia/Seoul")
|
||||
val utcZoneId = ZoneId.of("UTC")
|
||||
val nowKst = now.atZone(utcZoneId).withZoneSameInstant(kstZoneId)
|
||||
@@ -187,14 +195,18 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelDonationRecord::class.java,
|
||||
channelDonationMessage.member.nickname,
|
||||
channelDonationMessage.member.id,
|
||||
donorName.displayName,
|
||||
channelDonationMessage.member.profileImage,
|
||||
channelDonationMessage.can,
|
||||
channelDonationMessage.additionalMessage.coalesce(""),
|
||||
channelDonationMessage.createdAt
|
||||
channelDonationMessage.createdAt,
|
||||
donorName.name.id.isNotNull
|
||||
)
|
||||
)
|
||||
.from(channelDonationMessage)
|
||||
.join(channelDonationMessage.member, donor)
|
||||
.leftJoin(donorName.name).on(donorName.condition)
|
||||
.where(
|
||||
channelDonationMessage.creator.id.eq(creatorId),
|
||||
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
|
||||
.select(creatorCheers.id.count())
|
||||
.from(creatorCheers)
|
||||
@@ -371,19 +385,22 @@ class DefaultCreatorChannelHomeQueryRepository(
|
||||
CreatorChannelFanTalkRecord::class.java,
|
||||
creatorCheers.id,
|
||||
creatorCheers.member.id,
|
||||
creatorCheers.member.nickname,
|
||||
writerName.displayName,
|
||||
creatorCheers.member.profileImage,
|
||||
creatorCheers.cheers,
|
||||
creatorCheers.languageCode,
|
||||
creatorCheers.createdAt
|
||||
creatorCheers.createdAt,
|
||||
writerName.name.id.isNotNull
|
||||
)
|
||||
)
|
||||
.from(creatorCheers)
|
||||
.join(creatorCheers.member, writer)
|
||||
.leftJoin(writerName.name).on(writerName.condition)
|
||||
.where(fanTalkSummaryCondition(creatorId, viewerId))
|
||||
.orderBy(creatorCheers.createdAt.desc(), creatorCheers.id.desc())
|
||||
.limit(1)
|
||||
.fetchFirst()
|
||||
?.let { it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) }
|
||||
?.let { if (it.isLocalizedName) it else it.copy(nickname = it.nickname.removeDeletedNicknamePrefix()) }
|
||||
|
||||
return CreatorChannelFanTalkSummaryRecord(
|
||||
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.extensions.removeDeletedNicknamePrefix
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
@@ -54,15 +55,16 @@ class CreatorChannelHomeQueryService(
|
||||
fun getHome(
|
||||
creatorId: Long,
|
||||
viewer: Member,
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||
now: LocalDateTime = LocalDateTime.now(ZoneOffset.UTC),
|
||||
lang: Lang
|
||||
): CreatorChannelHome {
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
throw SodaException(message = String.format(messageTemplate, creator.nickname))
|
||||
}
|
||||
@@ -100,13 +102,14 @@ class CreatorChannelHomeQueryService(
|
||||
effectiveViewerGender = effectiveViewerGender
|
||||
)?.toDomain(),
|
||||
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(
|
||||
creatorId = creatorId,
|
||||
viewerId = viewerId,
|
||||
isPinned = true,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
limit = 3
|
||||
limit = 3,
|
||||
lang = lang
|
||||
),
|
||||
schedules = queryPolicy.limitSchedules(
|
||||
queryPort.findSchedules(
|
||||
@@ -135,9 +138,10 @@ class CreatorChannelHomeQueryService(
|
||||
viewerId = viewerId,
|
||||
isPinned = false,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
limit = 3
|
||||
limit = 3,
|
||||
lang = lang
|
||||
),
|
||||
fanTalk = queryPort.findFanTalkSummary(creatorId, viewerId).toDomain(),
|
||||
fanTalk = queryPort.findFanTalkSummary(creatorId, viewerId, lang).toDomain(),
|
||||
introduce = creator.introduce,
|
||||
activity = queryPort.findActivity(creatorId, now).toDomain(),
|
||||
sns = queryPort.findSns(creatorId).toDomain()
|
||||
@@ -193,7 +197,8 @@ class CreatorChannelHomeQueryService(
|
||||
)
|
||||
|
||||
private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation(
|
||||
nickname = nickname.removeDeletedNicknamePrefix(),
|
||||
memberId = memberId,
|
||||
nickname = if (isLocalizedName) nickname else nickname.removeDeletedNicknamePrefix(),
|
||||
profileImageUrl = profileImagePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
|
||||
can = can,
|
||||
message = message,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ data class CreatorChannelLive(
|
||||
)
|
||||
|
||||
data class CreatorChannelDonation(
|
||||
val memberId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String,
|
||||
val can: Int,
|
||||
|
||||
+10
-5
@@ -1,13 +1,14 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.home.port.out
|
||||
|
||||
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.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
@@ -32,7 +33,8 @@ interface CreatorChannelHomeQueryPort {
|
||||
creatorId: Long,
|
||||
viewerId: Long?,
|
||||
now: LocalDateTime,
|
||||
limit: Int = 8
|
||||
limit: Int = 8,
|
||||
lang: Lang
|
||||
): List<CreatorChannelDonationRecord>
|
||||
|
||||
fun findSchedules(
|
||||
@@ -66,7 +68,7 @@ interface CreatorChannelHomeQueryPort {
|
||||
limit: Int = 8
|
||||
): 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
|
||||
|
||||
@@ -112,11 +114,13 @@ data class CreatorChannelAudioContentRecord(
|
||||
)
|
||||
|
||||
data class CreatorChannelDonationRecord(
|
||||
val memberId: Long,
|
||||
val nickname: String,
|
||||
val profileImagePath: String?,
|
||||
val can: Int,
|
||||
val message: String,
|
||||
val createdAt: LocalDateTime
|
||||
val createdAt: LocalDateTime,
|
||||
val isLocalizedName: Boolean = false
|
||||
)
|
||||
|
||||
data class CreatorChannelScheduleRecord(
|
||||
@@ -148,7 +152,8 @@ data class CreatorChannelFanTalkRecord(
|
||||
val profileImagePath: String?,
|
||||
val content: String,
|
||||
val languageCode: String?,
|
||||
val createdAt: LocalDateTime
|
||||
val createdAt: LocalDateTime,
|
||||
val isLocalizedName: Boolean = false
|
||||
)
|
||||
|
||||
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.creator.admin.content.series.QSeries.series
|
||||
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.QLiveRoom.liveRoom
|
||||
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.CreatorChannelCreatorRecord
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
@@ -27,17 +29,19 @@ import java.time.LocalDateTime
|
||||
class DefaultCreatorChannelLiveQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelCreatorRecord::class.java,
|
||||
member.id,
|
||||
member.role,
|
||||
member.nickname
|
||||
creatorName.displayName
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
member.isActive.isTrue
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.creator.channel.live.application
|
||||
|
||||
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.member.Gender
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
@@ -30,7 +30,6 @@ class CreatorChannelLiveQueryService(
|
||||
private val queryPolicy: CreatorChannelLiveReplayQueryPolicy,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
@@ -40,17 +39,18 @@ class CreatorChannelLiveQueryService(
|
||||
sort: ContentSort,
|
||||
page: Int,
|
||||
size: Int,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelLiveTab {
|
||||
val livePage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Gender
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
|
||||
+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.creator.admin.content.series.QSeries.series
|
||||
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.block.QBlockMember
|
||||
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.CreatorChannelSeriesRecord
|
||||
import kr.co.vividnext.sodalive.v2.creatorname.adapter.out.persistence.CreatorNameJoin
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@@ -23,17 +25,19 @@ import java.time.LocalDateTime
|
||||
class DefaultCreatorChannelSeriesQueryRepository(
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : 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
|
||||
.select(
|
||||
Projections.constructor(
|
||||
CreatorChannelSeriesCreatorRecord::class.java,
|
||||
member.id,
|
||||
member.role,
|
||||
member.nickname
|
||||
creatorName.displayName
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.id.eq(creatorId),
|
||||
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.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.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
@@ -37,18 +38,19 @@ class CreatorChannelSeriesQueryService(
|
||||
sort: String?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
now: LocalDateTime = LocalDateTime.now()
|
||||
now: LocalDateTime = LocalDateTime.now(),
|
||||
lang: Lang
|
||||
): CreatorChannelSeriesTab {
|
||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||
val seriesPage = queryPolicy.createPage(page, size)
|
||||
val queryPort = queryPortProvider.getObject()
|
||||
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")
|
||||
|
||||
if (queryPort.existsBlockedBetween(viewerId, creatorId)) {
|
||||
val messageTemplate = messageSource
|
||||
.getMessage("explorer.creator.blocked_access", langContext.lang)
|
||||
.getMessage("explorer.creator.blocked_access", lang)
|
||||
.orEmpty()
|
||||
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.SeriesState
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import java.time.LocalDateTime
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.v2.gift.application
|
||||
|
||||
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.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.GiftCategoryRepository
|
||||
import kr.co.vividnext.sodalive.v2.gift.adapter.out.persistence.GiftDelivery
|
||||
@@ -112,7 +114,8 @@ class GiftQueryService(
|
||||
private val memberRepository: MemberRepository,
|
||||
private val giftMailboxRepository: GiftMailboxRepository,
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
private val cloudFrontHost: String,
|
||||
private val creatorLocalizedNameService: CreatorLocalizedNameService
|
||||
) {
|
||||
@Transactional(readOnly = true)
|
||||
fun getFormOptions() = GiftFormOptionsResult(
|
||||
@@ -134,7 +137,7 @@ class GiftQueryService(
|
||||
)
|
||||
|
||||
@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 normalizedSize = if (size <= 0) DEFAULT_PAGE_SIZE else size
|
||||
val pageable = PageRequest.of(normalizedPage, normalizedSize, Sort.by(Sort.Direction.DESC, "createdAt", "id"))
|
||||
@@ -156,13 +159,14 @@ class GiftQueryService(
|
||||
val membersById = memberRepository.findAllById(
|
||||
gifts.content.flatMap { listOf(it.senderMemberId, it.recipientMemberId) }.toSet()
|
||||
).associateBy { it.id ?: throw invalidRequest() }
|
||||
val names = creatorLocalizedNameService.findLocalizedNames(membersById.keys, lang)
|
||||
val deliveriesByGiftId = deliveryRepository.findByGiftIdIn(
|
||||
gifts.content.mapNotNull { it.id }
|
||||
).associateBy { it.giftId }
|
||||
|
||||
return GiftListResult(
|
||||
totalCount = gifts.totalElements,
|
||||
items = gifts.content.map { it.toGiftListItem(memberId, membersById, deliveriesByGiftId) },
|
||||
items = gifts.content.map { it.toGiftListItem(memberId, membersById, deliveriesByGiftId, names) },
|
||||
page = normalizedPage,
|
||||
size = normalizedSize,
|
||||
hasNext = gifts.hasNext()
|
||||
@@ -170,12 +174,13 @@ class GiftQueryService(
|
||||
}
|
||||
|
||||
@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 delivery = deliveryRepository.findByGiftId(gift.id ?: throw invalidRequest()) ?: throw invalidRequest()
|
||||
val direction = gift.directionFor(memberId)
|
||||
val sender = memberRepository.findByIdOrNull(gift.senderMemberId) ?: 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
|
||||
|
||||
return GiftDetailResult(
|
||||
@@ -184,8 +189,10 @@ class GiftQueryService(
|
||||
status = gift.status,
|
||||
statusName = gift.status.displayName,
|
||||
giftInfo = GiftDetailInfoResult(
|
||||
recipientCreatorNickname = recipient.nickname.takeIf { direction == GiftDirection.SENT },
|
||||
senderNickname = sender.nickname.takeIf { direction == GiftDirection.RECEIVED },
|
||||
recipientCreatorNickname = (names[gift.recipientMemberId] ?: recipient.nickname)
|
||||
.takeIf { direction == GiftDirection.SENT },
|
||||
senderNickname = (names[gift.senderMemberId] ?: sender.nickname)
|
||||
.takeIf { direction == GiftDirection.RECEIVED },
|
||||
sizeName = gift.sizeCode.displayName,
|
||||
categoryName = gift.categoryNameSnapshot,
|
||||
applicationNo = gift.applicationNo.takeIf { direction == GiftDirection.SENT },
|
||||
@@ -212,7 +219,8 @@ class GiftQueryService(
|
||||
private fun Gift.toGiftListItem(
|
||||
memberId: Long,
|
||||
membersById: Map<Long, Member>,
|
||||
deliveriesByGiftId: Map<Long, GiftDelivery>
|
||||
deliveriesByGiftId: Map<Long, GiftDelivery>,
|
||||
names: Map<Long, String>
|
||||
): GiftListItemResult {
|
||||
val direction = if (senderMemberId == memberId) GiftDirection.SENT else GiftDirection.RECEIVED
|
||||
val counterpartId = if (direction == GiftDirection.SENT) recipientMemberId else senderMemberId
|
||||
@@ -229,7 +237,7 @@ class GiftQueryService(
|
||||
categoryName = categoryNameSnapshot,
|
||||
sizeName = sizeCode.displayName,
|
||||
counterpartMemberId = counterpartId,
|
||||
counterpartNickname = counterpart.nickname,
|
||||
counterpartNickname = names[counterpartId] ?: counterpart.nickname,
|
||||
counterpartProfileImageUrl = counterpart.profileImageUrl(),
|
||||
recipientAddressRegistered = 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.like.QCreatorCommunityLike.creatorCommunityLike
|
||||
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.QLiveRoom.liveRoom
|
||||
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.v2.common.domain.CreatorActivityType
|
||||
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.domain.FollowingNewsType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCommunityPostNews
|
||||
@@ -46,12 +48,14 @@ class DefaultHomeFollowingQueryRepository(
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) : 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 creatorName = CreatorNameJoin(creator, lang, "followingCreatorName")
|
||||
return queryFactory
|
||||
.select(creator.id, creator.nickname, creator.profileImage)
|
||||
.select(creator.id, creatorName.displayName, creator.profileImage)
|
||||
.from(creatorFollowing)
|
||||
.join(creatorFollowing.creator, creator)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
creatorFollowing.member.id.eq(memberId),
|
||||
creatorFollowing.isActive.isTrue,
|
||||
@@ -65,7 +69,7 @@ class DefaultHomeFollowingQueryRepository(
|
||||
.map { row ->
|
||||
HomeFollowingCreator(
|
||||
creatorId = row.get(creator.id)!!,
|
||||
creatorNickname = row.get(creator.nickname)!!,
|
||||
creatorNickname = row.get(creatorName.displayName)!!,
|
||||
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage))
|
||||
)
|
||||
}
|
||||
@@ -76,13 +80,23 @@ class DefaultHomeFollowingQueryRepository(
|
||||
canViewAdultContent: Boolean,
|
||||
effectiveViewerGender: Gender,
|
||||
isViewerCreator: Boolean,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingLive> {
|
||||
val creator = QMember("onAirCreator")
|
||||
val creatorName = CreatorNameJoin(creator, lang, "followingOnAirCreatorName")
|
||||
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)
|
||||
.join(liveRoom.member, creator)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||
.where(
|
||||
creatorFollowing.member.id.eq(memberId),
|
||||
@@ -104,9 +118,10 @@ class DefaultHomeFollowingQueryRepository(
|
||||
HomeFollowingLive(
|
||||
liveId = row.get(liveRoom.id)!!,
|
||||
creatorProfileImageUrl = profileImageUrl(row.get(creator.profileImage)),
|
||||
creatorNickname = row.get(creator.nickname)!!,
|
||||
creatorNickname = row.get(creatorName.displayName)!!,
|
||||
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,
|
||||
isViewerCreator: Boolean,
|
||||
now: LocalDateTime,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingSchedule> {
|
||||
val window = monthlyScheduleWindow(now)
|
||||
val liveSchedules = findLiveSchedules(
|
||||
@@ -125,9 +141,10 @@ class DefaultHomeFollowingQueryRepository(
|
||||
canViewAdultContent,
|
||||
effectiveViewerGender,
|
||||
isViewerCreator,
|
||||
window
|
||||
window,
|
||||
lang
|
||||
)
|
||||
val audioSchedules = findAudioSchedules(memberId, canViewAdultContent, window)
|
||||
val audioSchedules = findAudioSchedules(memberId, canViewAdultContent, window, lang)
|
||||
return (liveSchedules + audioSchedules)
|
||||
.sortedWith(
|
||||
compareBy<HomeFollowingSchedule> { it.scheduledAtUtc }
|
||||
@@ -141,17 +158,20 @@ class DefaultHomeFollowingQueryRepository(
|
||||
memberId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
nowUtc: LocalDateTime,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingNews> {
|
||||
val creator = QMember("newsCreator")
|
||||
val creatorName = CreatorNameJoin(creator, lang, "followingNewsCreatorName")
|
||||
val newsAudioContent = QAudioContent("recentNewsAudioContent")
|
||||
val newsCommunity = QCreatorCommunity("recentNewsCommunity")
|
||||
val rows = queryFactory
|
||||
.select(
|
||||
homeFollowingNewsInbox.id,
|
||||
homeFollowingNewsInbox.newsType,
|
||||
creator.id,
|
||||
creator.profileImage,
|
||||
creator.nickname,
|
||||
creatorName.displayName,
|
||||
homeFollowingNewsInbox.targetId,
|
||||
homeFollowingNewsInbox.visibleFromAtUtc,
|
||||
homeFollowingNewsInbox.rank,
|
||||
@@ -163,6 +183,7 @@ class DefaultHomeFollowingQueryRepository(
|
||||
)
|
||||
.from(homeFollowingNewsInbox)
|
||||
.join(creator).on(creator.id.eq(homeFollowingNewsInbox.creatorId))
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.leftJoin(newsAudioContent).on(
|
||||
homeFollowingNewsInbox.newsType.`in`(FollowingNewsType.AUDIO_CONTENT, FollowingNewsType.CONTENT_RANKING),
|
||||
newsAudioContent.id.eq(homeFollowingNewsInbox.targetId)
|
||||
@@ -190,11 +211,14 @@ class DefaultHomeFollowingQueryRepository(
|
||||
.map { it.get(homeFollowingNewsInbox.targetId)!! }
|
||||
val likeCounts = communityLikeCounts(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(
|
||||
newsCreator: QMember,
|
||||
creatorName: CreatorNameJoin,
|
||||
newsAudioContent: QAudioContent,
|
||||
newsCommunity: QCreatorCommunity,
|
||||
likeCounts: Map<Long, Int>,
|
||||
@@ -203,12 +227,13 @@ class DefaultHomeFollowingQueryRepository(
|
||||
val type = get(homeFollowingNewsInbox.newsType)!!
|
||||
val targetId = get(homeFollowingNewsInbox.targetId)!!
|
||||
val creatorProfileImageUrl = profileImageUrl(get(newsCreator.profileImage))
|
||||
val creatorNickname = get(newsCreator.nickname)!!
|
||||
val creatorNickname = get(creatorName.displayName)!!
|
||||
val visibleFromAtUtc = get(homeFollowingNewsInbox.visibleFromAtUtc)!!.toUtcIso()
|
||||
val rank = get(homeFollowingNewsInbox.rank)
|
||||
|
||||
return HomeFollowingNews(
|
||||
newsId = get(homeFollowingNewsInbox.id)!!.toString(),
|
||||
creatorMemberId = get(newsCreator.id)!!,
|
||||
type = type,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
creatorRanking = if (type == FollowingNewsType.CREATOR_RANKING && rank != null) {
|
||||
@@ -264,21 +289,24 @@ class DefaultHomeFollowingQueryRepository(
|
||||
canViewAdultContent: Boolean,
|
||||
effectiveViewerGender: Gender,
|
||||
isViewerCreator: Boolean,
|
||||
window: ScheduleWindow
|
||||
window: ScheduleWindow,
|
||||
lang: Lang
|
||||
): List<HomeFollowingSchedule> {
|
||||
val creator = QMember("scheduleLiveCreator")
|
||||
val creatorName = CreatorNameJoin(creator, lang, "followingLiveScheduleCreatorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
liveRoom.id,
|
||||
creator.id,
|
||||
creator.profileImage,
|
||||
creator.nickname,
|
||||
creatorName.displayName,
|
||||
liveRoom.title,
|
||||
liveRoom.beginDateTime,
|
||||
liveRoom.channelName
|
||||
)
|
||||
.from(liveRoom)
|
||||
.join(liveRoom.member, creator)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||
.where(
|
||||
creatorFollowing.member.id.eq(memberId),
|
||||
@@ -294,26 +322,29 @@ class DefaultHomeFollowingQueryRepository(
|
||||
notBlockedCreatorCondition(memberId, creator.id)
|
||||
)
|
||||
.fetch()
|
||||
.map { row -> row.toLiveSchedule(creator) }
|
||||
.map { row -> row.toLiveSchedule(creator, creatorName) }
|
||||
}
|
||||
|
||||
private fun findAudioSchedules(
|
||||
memberId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
window: ScheduleWindow
|
||||
window: ScheduleWindow,
|
||||
lang: Lang
|
||||
): List<HomeFollowingSchedule> {
|
||||
val creator = QMember("scheduleAudioCreator")
|
||||
val creatorName = CreatorNameJoin(creator, lang, "followingAudioScheduleCreatorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
audioContent.id,
|
||||
creator.id,
|
||||
creator.profileImage,
|
||||
creator.nickname,
|
||||
creatorName.displayName,
|
||||
audioContent.title,
|
||||
audioContent.releaseDate
|
||||
)
|
||||
.from(audioContent)
|
||||
.join(audioContent.member, creator)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.join(creatorFollowing).on(creatorFollowing.creator.id.eq(creator.id))
|
||||
.where(
|
||||
creatorFollowing.member.id.eq(memberId),
|
||||
@@ -328,17 +359,17 @@ class DefaultHomeFollowingQueryRepository(
|
||||
notBlockedCreatorCondition(memberId, creator.id)
|
||||
)
|
||||
.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 channelName = get(liveRoom.channelName)
|
||||
return HomeFollowingSchedule(
|
||||
scheduleId = "${CreatorActivityType.LIVE}:$liveId",
|
||||
creatorId = get(creator.id)!!,
|
||||
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
||||
creatorNickname = get(creator.nickname)!!,
|
||||
creatorNickname = get(creatorName.displayName)!!,
|
||||
title = get(liveRoom.title)!!,
|
||||
type = CreatorActivityType.LIVE,
|
||||
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)!!
|
||||
return HomeFollowingSchedule(
|
||||
scheduleId = "${CreatorActivityType.AUDIO}:$contentId",
|
||||
creatorId = get(creator.id)!!,
|
||||
creatorProfileImageUrl = profileImageUrl(get(creator.profileImage)),
|
||||
creatorNickname = get(creator.nickname)!!,
|
||||
creatorNickname = get(creatorName.displayName)!!,
|
||||
title = get(audioContent.title)!!,
|
||||
type = CreatorActivityType.AUDIO,
|
||||
targetId = contentId,
|
||||
|
||||
+9
-8
@@ -1,6 +1,6 @@
|
||||
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.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
@@ -19,34 +19,34 @@ import java.time.LocalDateTime
|
||||
class HomeFollowingQueryService(
|
||||
private val queryPort: HomeFollowingQueryPort,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val langContext: LangContext,
|
||||
private val creatorCommunityTranslationService: CreatorCommunityTranslationService,
|
||||
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 canViewAdultContent = memberContentPreferenceService.canViewAdultContent(member)
|
||||
val effectiveViewerGender = member.effectiveGender()
|
||||
val isViewerCreator = member.role == MemberRole.CREATOR
|
||||
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 ->
|
||||
if (news.type == FollowingNewsType.COMMUNITY_POST) news.communityPost?.postId else null
|
||||
}
|
||||
val displayContents = if (communityPostIds.isEmpty()) {
|
||||
emptyMap()
|
||||
} else {
|
||||
creatorCommunityTranslationService.findDisplayContents(communityPostIds, langContext.lang.code)
|
||||
creatorCommunityTranslationService.findDisplayContents(communityPostIds, lang.code)
|
||||
}
|
||||
|
||||
return HomeFollowing(
|
||||
followingCreators = queryPort.findFollowingCreators(memberId, FOLLOWING_CREATORS_LIMIT),
|
||||
followingCreators = queryPort.findFollowingCreators(memberId, FOLLOWING_CREATORS_LIMIT, lang),
|
||||
onAirLives = queryPort.findOnAirLives(
|
||||
memberId,
|
||||
canViewAdultContent,
|
||||
effectiveViewerGender,
|
||||
isViewerCreator,
|
||||
ON_AIR_LIVES_LIMIT
|
||||
ON_AIR_LIVES_LIMIT,
|
||||
lang
|
||||
),
|
||||
recentChats = emptyList(),
|
||||
monthlySchedules = queryPort.findMonthlySchedules(
|
||||
@@ -55,7 +55,8 @@ class HomeFollowingQueryService(
|
||||
effectiveViewerGender,
|
||||
isViewerCreator,
|
||||
now,
|
||||
MONTHLY_SCHEDULES_LIMIT
|
||||
MONTHLY_SCHEDULES_LIMIT,
|
||||
lang
|
||||
),
|
||||
recentNews = recentNews.map { news ->
|
||||
if (news.type != FollowingNewsType.COMMUNITY_POST) return@map news
|
||||
|
||||
@@ -22,7 +22,8 @@ data class HomeFollowingLive(
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val startedAtUtc: String
|
||||
val startedAtUtc: String,
|
||||
val creatorMemberId: Long
|
||||
)
|
||||
|
||||
data class HomeFollowingSchedule(
|
||||
@@ -45,7 +46,8 @@ data class HomeFollowingNews(
|
||||
val audioContent: HomeFollowingContentNews? = null,
|
||||
val photoContent: HomeFollowingContentNews? = null,
|
||||
val contentRanking: HomeFollowingContentRankingNews? = null,
|
||||
val communityPost: HomeFollowingCommunityPostNews? = null
|
||||
val communityPost: HomeFollowingCommunityPostNews? = null,
|
||||
val creatorMemberId: Long
|
||||
)
|
||||
|
||||
data class HomeFollowingCreatorRankingNews(
|
||||
|
||||
+8
-4
@@ -1,5 +1,6 @@
|
||||
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.v2.home.following.domain.HomeFollowingCreator
|
||||
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
|
||||
|
||||
interface HomeFollowingQueryPort {
|
||||
fun findFollowingCreators(memberId: Long, limit: Int): List<HomeFollowingCreator>
|
||||
fun findFollowingCreators(memberId: Long, limit: Int, lang: Lang): List<HomeFollowingCreator>
|
||||
|
||||
fun findOnAirLives(
|
||||
memberId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
effectiveViewerGender: Gender,
|
||||
isViewerCreator: Boolean,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingLive>
|
||||
|
||||
fun findMonthlySchedules(
|
||||
@@ -24,13 +26,15 @@ interface HomeFollowingQueryPort {
|
||||
effectiveViewerGender: Gender,
|
||||
isViewerCreator: Boolean,
|
||||
now: LocalDateTime,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingSchedule>
|
||||
|
||||
fun findRecentNews(
|
||||
memberId: Long,
|
||||
canViewAdultContent: Boolean,
|
||||
nowUtc: LocalDateTime,
|
||||
limit: Int
|
||||
limit: Int,
|
||||
lang: Lang
|
||||
): List<HomeFollowingNews>
|
||||
}
|
||||
|
||||
+31
-5
@@ -1,5 +1,11 @@
|
||||
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.port.out.CreatorRankingSnapshotPort
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
||||
@@ -9,7 +15,8 @@ import java.time.LocalDateTime
|
||||
|
||||
@Repository
|
||||
class DefaultCreatorRankingSnapshotRepository(
|
||||
private val repository: CreatorRankingSnapshotRepository
|
||||
private val repository: CreatorRankingSnapshotRepository,
|
||||
private val queryFactory: JPAQueryFactory
|
||||
) : CreatorRankingSnapshotPort {
|
||||
override fun findSnapshotsByAggregationPeriod(
|
||||
aggregationStartAtUtc: LocalDateTime,
|
||||
@@ -31,9 +38,28 @@ class DefaultCreatorRankingSnapshotRepository(
|
||||
|
||||
override fun findLatestVisibleSnapshots(
|
||||
rankingType: CreatorRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
nowUtc: LocalDateTime,
|
||||
lang: Lang
|
||||
): 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(
|
||||
@@ -64,14 +90,14 @@ class DefaultCreatorRankingSnapshotRepository(
|
||||
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
|
||||
}
|
||||
|
||||
private fun CreatorRankingSnapshot.toRecord(): CreatorRankingSnapshotRecord {
|
||||
private fun CreatorRankingSnapshot.toRecord(displayNickname: String = nickname): CreatorRankingSnapshotRecord {
|
||||
return CreatorRankingSnapshotRecord(
|
||||
rankingType = rankingType,
|
||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
creatorId = creatorId,
|
||||
nickname = nickname,
|
||||
nickname = displayNickname,
|
||||
profileImageUrl = profileImageUrl,
|
||||
rankNo = rankNo,
|
||||
finalScore = finalScore,
|
||||
|
||||
+6
-5
@@ -1,5 +1,6 @@
|
||||
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.ranking.domain.CreatorRankingItem
|
||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
||||
@@ -24,11 +25,11 @@ class CreatorRankingQueryService(
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
fun getCreatorRankings(viewerMemberId: Long?): CreatorRankingResult {
|
||||
fun getCreatorRankings(viewerMemberId: Long?, lang: Lang): CreatorRankingResult {
|
||||
val startedAt = System.currentTimeMillis()
|
||||
return runCatching {
|
||||
val nowUtc = nowUtc()
|
||||
val latestSnapshots = findLatestVisibleSnapshots(nowUtc)
|
||||
val latestSnapshots = findLatestVisibleSnapshots(nowUtc, lang)
|
||||
val latestItems = latestSnapshots.toRankedItems()
|
||||
if (latestItems.isEmpty()) {
|
||||
return@runCatching QueryLogResult(CreatorRankingResult(showRankChange = false, items = emptyList()), 0)
|
||||
@@ -69,15 +70,15 @@ class CreatorRankingQueryService(
|
||||
}.getOrThrow().result
|
||||
}
|
||||
|
||||
private fun findLatestVisibleSnapshots(nowUtc: LocalDateTime): List<CreatorRankingSnapshotRecord> {
|
||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc)
|
||||
private fun findLatestVisibleSnapshots(nowUtc: LocalDateTime, lang: Lang): List<CreatorRankingSnapshotRecord> {
|
||||
val latestSnapshots = snapshotPort.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, nowUtc, lang)
|
||||
if (latestSnapshots.isNotEmpty()) return latestSnapshots
|
||||
|
||||
runCatching { snapshotJobService.refreshLastCompletedWeekByFallback() }
|
||||
.onFailure { 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(
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
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 java.time.LocalDateTime
|
||||
|
||||
@@ -15,7 +16,8 @@ interface CreatorRankingSnapshotPort {
|
||||
|
||||
fun findLatestVisibleSnapshots(
|
||||
rankingType: CreatorRankingType,
|
||||
nowUtc: LocalDateTime
|
||||
nowUtc: LocalDateTime,
|
||||
lang: Lang
|
||||
): List<CreatorRankingSnapshotRecord>
|
||||
|
||||
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.following.QCreatorFollowing
|
||||
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.RecommendedSectionType
|
||||
import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeAiCharacterRecommendationRecord
|
||||
@@ -62,22 +63,26 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
memberId: Long?,
|
||||
includeAdultLives: Boolean,
|
||||
isViewerCreator: Boolean,
|
||||
effectiveViewerGender: Gender?
|
||||
effectiveViewerGender: Gender?,
|
||||
lang: Lang
|
||||
): List<HomeLiveRecommendationRecord> {
|
||||
val creatorName = CreatorNameJoin(member, lang, "homeLiveCreatorName")
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
HomeLiveRecommendationRecord::class.java,
|
||||
liveRoom.id,
|
||||
member.nickname,
|
||||
creatorName.displayName,
|
||||
member.profileImage,
|
||||
liveRoom.title,
|
||||
liveRoom.price,
|
||||
liveRoom.beginDateTime
|
||||
liveRoom.beginDateTime,
|
||||
member.id
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
.join(liveRoom.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
liveRoom.isActive.isTrue,
|
||||
liveRoom.channelName.isNotNull,
|
||||
@@ -138,11 +143,12 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
override fun findRecentlyActiveCreators(
|
||||
limit: Int,
|
||||
memberId: Long?,
|
||||
includeAdultActivities: Boolean
|
||||
includeAdultActivities: Boolean,
|
||||
lang: Lang
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
val sql = """
|
||||
select ranked.creator_id,
|
||||
ranked.creator_nickname,
|
||||
coalesce(creator_name.display_name, ranked.creator_nickname),
|
||||
ranked.creator_profile_image,
|
||||
ranked.activity_type,
|
||||
ranked.activity_at,
|
||||
@@ -200,6 +206,9 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and m.role = 'CREATOR'
|
||||
) activities
|
||||
) 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
|
||||
and ${notBlockedCreatorSql("ranked.creator_id")}
|
||||
order by ranked.activity_at desc, ranked.target_sort_id desc
|
||||
@@ -210,6 +219,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
.setParameter("liveReplayTheme", LIVE_REPLAY_THEME)
|
||||
.setParameter("memberId", memberId)
|
||||
.setParameter("includeAdultActivities", includeAdultActivities)
|
||||
.setParameter("lang", lang.name)
|
||||
.setParameter("limit", limit)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -232,7 +242,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
memberId: Long?,
|
||||
includeAdultContents: Boolean
|
||||
includeAdultContents: Boolean,
|
||||
lang: Lang
|
||||
): List<RecentDebutCreatorRecord> {
|
||||
val sql = """
|
||||
with creator_debut as (
|
||||
@@ -328,9 +339,11 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
group by communication.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
|
||||
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
|
||||
left join follow_stats fs on fs.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)
|
||||
.setRecommendationQueryParameters(now, limit)
|
||||
.setParameter("lang", lang.name)
|
||||
.setParameter("offset", offset)
|
||||
.setParameter("memberId", memberId)
|
||||
.setParameter("includeAdultContents", includeAdultContents)
|
||||
@@ -437,7 +451,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
select ec.content_id as content_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,
|
||||
ec.title as title,
|
||||
ec.price as price,
|
||||
@@ -447,6 +461,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
ec.is_original_series as is_original_series
|
||||
from eligible_contents ec
|
||||
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
|
||||
where ec.active_rank = 1
|
||||
and m.is_active = true
|
||||
@@ -468,6 +484,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
|
||||
val query = entityManager.createNativeQuery(sql)
|
||||
.setParameter("now", now)
|
||||
.setParameter("lang", Lang.fromCode(locale).name)
|
||||
.setParameter("limit", limit)
|
||||
.setParameter("offset", offset)
|
||||
.setParameter("memberId", memberId)
|
||||
@@ -832,20 +849,23 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
|
||||
override fun findCheerCreatorRecommendationDetails(
|
||||
creatorIds: List<Long>,
|
||||
memberId: Long?
|
||||
memberId: Long?,
|
||||
lang: Lang
|
||||
): List<HomeCheerCreatorRecommendationRecord> {
|
||||
if (creatorIds.isEmpty()) return emptyList()
|
||||
val creatorName = CreatorNameJoin(member, lang, "homeCheerCreatorName")
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
HomeCheerCreatorRecommendationRecord::class.java,
|
||||
member.id,
|
||||
member.nickname,
|
||||
creatorName.displayName,
|
||||
member.profileImage
|
||||
)
|
||||
)
|
||||
.from(member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.where(
|
||||
member.isActive.isTrue,
|
||||
member.id.`in`(creatorIds),
|
||||
@@ -859,9 +879,11 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
override fun findPopularCommunityRecommendationDetails(
|
||||
communityIds: List<Long>,
|
||||
memberId: Long?,
|
||||
includeAdultCommunities: Boolean
|
||||
includeAdultCommunities: Boolean,
|
||||
lang: Lang
|
||||
): List<HomePopularCommunityRecommendationRecord> {
|
||||
if (communityIds.isEmpty()) return emptyList()
|
||||
val creatorName = CreatorNameJoin(member, lang, "homeCommunityCreatorName")
|
||||
|
||||
return queryFactory
|
||||
.select(
|
||||
@@ -869,7 +891,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
HomePopularCommunityRecommendationRecord::class.java,
|
||||
creatorCommunity.id,
|
||||
member.id,
|
||||
member.nickname,
|
||||
creatorName.displayName,
|
||||
member.profileImage,
|
||||
creatorCommunity.imagePath,
|
||||
creatorCommunity.audioPath,
|
||||
@@ -884,6 +906,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
.from(creatorCommunity)
|
||||
.join(creatorCommunity.member, member)
|
||||
.leftJoin(creatorName.name).on(creatorName.condition)
|
||||
.leftJoin(creatorCommunityLike).on(
|
||||
creatorCommunityLike.creatorCommunity.id.eq(creatorCommunity.id),
|
||||
creatorCommunityLike.isActive.isTrue
|
||||
@@ -905,6 +928,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
creatorCommunity.id,
|
||||
member.id,
|
||||
member.nickname,
|
||||
creatorName.name.displayName,
|
||||
member.profileImage,
|
||||
creatorCommunity.imagePath,
|
||||
creatorCommunity.audioPath,
|
||||
@@ -919,7 +943,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
genreLimit: Int,
|
||||
creatorLimit: Int
|
||||
creatorLimit: Int,
|
||||
lang: Lang
|
||||
): List<HomeGenreCreatorRecommendationGroup> {
|
||||
val groups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||
val selectedGenreIds = mutableSetOf<Long>()
|
||||
@@ -931,7 +956,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
|
||||
viewedTargets.forEach { target ->
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||
selectedGenreIds.add(target.id)
|
||||
}
|
||||
|
||||
@@ -941,7 +966,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
memberId = memberId,
|
||||
includeAdultGenres = includeAdultGenres,
|
||||
genreLimit = genreLimit,
|
||||
creatorLimit = creatorLimit
|
||||
creatorLimit = creatorLimit,
|
||||
lang = lang
|
||||
)
|
||||
|
||||
return groups
|
||||
@@ -993,7 +1019,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
genreLimit: Int,
|
||||
creatorLimit: Int
|
||||
creatorLimit: Int,
|
||||
lang: Lang
|
||||
) {
|
||||
val fullTargets = findFallbackGenreRecommendationTargets(
|
||||
memberId = memberId,
|
||||
@@ -1004,7 +1031,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
|
||||
fullTargets.forEach { target ->
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||
selectedGenreIds.add(target.id)
|
||||
}
|
||||
val partialTargets = findFallbackGenreRecommendationTargets(
|
||||
@@ -1016,7 +1043,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
|
||||
partialTargets.forEach { target ->
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit))
|
||||
groups.add(toGenreCreatorRecommendationGroup(target, memberId, includeAdultGenres, creatorLimit, lang))
|
||||
selectedGenreIds.add(target.id)
|
||||
}
|
||||
}
|
||||
@@ -1099,7 +1126,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
target: GenreRecommendationTarget,
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
creatorLimit: Int
|
||||
creatorLimit: Int,
|
||||
lang: Lang
|
||||
): HomeGenreCreatorRecommendationGroup {
|
||||
return HomeGenreCreatorRecommendationGroup(
|
||||
genreId = target.id,
|
||||
@@ -1109,7 +1137,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
genreId = target.id,
|
||||
memberId = memberId,
|
||||
includeAdultGenres = includeAdultGenres,
|
||||
creatorLimit = creatorLimit
|
||||
creatorLimit = creatorLimit,
|
||||
lang = lang
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1152,11 +1181,12 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
genreId: Long,
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
creatorLimit: Int
|
||||
creatorLimit: Int,
|
||||
lang: Lang
|
||||
): List<HomeGenreCreatorRecommendationRecord> {
|
||||
val sql = """
|
||||
select candidates.creator_id,
|
||||
candidates.creator_nickname,
|
||||
coalesce(creator_name.display_name, candidates.creator_nickname),
|
||||
candidates.creator_profile_image
|
||||
from (
|
||||
select m.id as creator_id,
|
||||
@@ -1190,12 +1220,16 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
group by m.id, m.nickname, m.profile_image
|
||||
) 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
|
||||
limit :creatorLimit
|
||||
""".trimIndent()
|
||||
|
||||
val query = entityManager.createNativeQuery(sql)
|
||||
.setParameter("genreId", genreId)
|
||||
.setParameter("lang", lang.name)
|
||||
.setParameter("memberId", memberId)
|
||||
.setParameter("includeAdultGenres", includeAdultGenres)
|
||||
.setParameter("creatorLimit", creatorLimit)
|
||||
|
||||
+24
-17
@@ -1,7 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.v2.recommendation.application
|
||||
|
||||
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.v2.common.domain.CreatorActivityType
|
||||
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 popularCommunitySnapshotFallbackService: PopularCommunitySnapshotFallbackPort? = null,
|
||||
private val clock: Clock = Clock.systemUTC(),
|
||||
private val langContext: LangContext,
|
||||
private val creatorCommunityTranslationService: CreatorCommunityTranslationService
|
||||
) {
|
||||
private val snapshotWindowPolicy = RecommendationSnapshotWindowPolicy()
|
||||
@@ -44,7 +42,8 @@ class HomeRecommendationQueryService(
|
||||
memberId: Long? = null,
|
||||
includeAdultLives: Boolean = false,
|
||||
isViewerCreator: Boolean = false,
|
||||
effectiveViewerGender: Gender? = null
|
||||
effectiveViewerGender: Gender? = null,
|
||||
lang: Lang
|
||||
): List<HomeLiveRecommendationRecord> {
|
||||
return queryPort.findLiveRecommendations(
|
||||
offset,
|
||||
@@ -52,7 +51,8 @@ class HomeRecommendationQueryService(
|
||||
memberId,
|
||||
includeAdultLives,
|
||||
isViewerCreator,
|
||||
effectiveViewerGender
|
||||
effectiveViewerGender,
|
||||
lang
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,9 +68,10 @@ class HomeRecommendationQueryService(
|
||||
fun findRecentlyActiveCreators(
|
||||
limit: Int = DEFAULT_ACTIVE_CREATOR_LIMIT,
|
||||
memberId: Long? = null,
|
||||
includeAdultActivities: Boolean = false
|
||||
includeAdultActivities: Boolean = false,
|
||||
lang: Lang
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
return queryPort.findRecentlyActiveCreators(limit, memberId, includeAdultActivities)
|
||||
return queryPort.findRecentlyActiveCreators(limit, memberId, includeAdultActivities, lang)
|
||||
}
|
||||
|
||||
fun findRecentDebutCreators(
|
||||
@@ -78,9 +79,10 @@ class HomeRecommendationQueryService(
|
||||
offset: Long = 0,
|
||||
limit: Int = DEFAULT_RECENT_DEBUT_CREATOR_LIMIT,
|
||||
memberId: Long? = null,
|
||||
includeAdultContents: Boolean = false
|
||||
includeAdultContents: Boolean = false,
|
||||
lang: Lang
|
||||
): List<RecentDebutCreatorRecord> {
|
||||
return queryPort.findRecentDebutCreators(now, offset, limit, memberId, includeAdultContents)
|
||||
return queryPort.findRecentDebutCreators(now, offset, limit, memberId, includeAdultContents, lang)
|
||||
}
|
||||
|
||||
fun findFirstAudioContents(
|
||||
@@ -88,9 +90,10 @@ class HomeRecommendationQueryService(
|
||||
offset: Long = 0,
|
||||
limit: Int = DEFAULT_FIRST_AUDIO_CONTENT_LIMIT,
|
||||
memberId: Long? = null,
|
||||
includeAdultContents: Boolean = false
|
||||
includeAdultContents: Boolean = false,
|
||||
lang: Lang
|
||||
): 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(
|
||||
@@ -128,10 +131,11 @@ class HomeRecommendationQueryService(
|
||||
|
||||
fun findCheerCreatorRecommendations(
|
||||
limit: Int = DEFAULT_CHEER_CREATOR_LIMIT,
|
||||
memberId: Long? = null
|
||||
memberId: Long? = null,
|
||||
lang: Lang
|
||||
): List<HomeCheerCreatorRecommendationRecord> {
|
||||
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 }
|
||||
|
||||
return snapshots.mapNotNull { detailsById[it.targetId] }.take(limit)
|
||||
@@ -155,13 +159,15 @@ class HomeRecommendationQueryService(
|
||||
fun findPopularCommunityRecommendations(
|
||||
limit: Int = DEFAULT_POPULAR_COMMUNITY_LIMIT,
|
||||
memberId: Long? = null,
|
||||
includeAdultCommunities: Boolean = false
|
||||
includeAdultCommunities: Boolean = false,
|
||||
lang: Lang
|
||||
): List<HomePopularCommunityRecommendationRecord> {
|
||||
val snapshots = findPopularCommunitySnapshotsWithFallback()
|
||||
val detailsById = queryPort.findPopularCommunityRecommendationDetails(
|
||||
snapshots.map { it.targetId },
|
||||
memberId,
|
||||
includeAdultCommunities
|
||||
includeAdultCommunities,
|
||||
lang
|
||||
)
|
||||
.associateBy { it.communityId }
|
||||
val selectedCreatorIds = mutableSetOf<Long>()
|
||||
@@ -173,7 +179,7 @@ class HomeRecommendationQueryService(
|
||||
|
||||
val displayContents = creatorCommunityTranslationService.findDisplayContents(
|
||||
recommendations.map { it.communityId },
|
||||
langContext.lang.code
|
||||
lang.code
|
||||
)
|
||||
return recommendations.map { recommendation ->
|
||||
displayContents[recommendation.communityId]
|
||||
@@ -201,13 +207,14 @@ class HomeRecommendationQueryService(
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
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> {
|
||||
val selectedCreatorIds = mutableSetOf<Long>()
|
||||
val partialFallbackGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||
val selectedGroups = mutableListOf<HomeGenreCreatorRecommendationGroup>()
|
||||
|
||||
queryPort.findGenreCreatorRecommendations(memberId, includeAdultGenres, genreLimit, creatorLimit)
|
||||
queryPort.findGenreCreatorRecommendations(memberId, includeAdultGenres, genreLimit, creatorLimit, lang)
|
||||
.forEach { group ->
|
||||
if (selectedGroups.size >= genreLimit) return@forEach
|
||||
|
||||
|
||||
+14
-7
@@ -12,7 +12,8 @@ interface HomeRecommendationQueryPort {
|
||||
memberId: Long? = null,
|
||||
includeAdultLives: Boolean = false,
|
||||
isViewerCreator: Boolean = false,
|
||||
effectiveViewerGender: Gender? = null
|
||||
effectiveViewerGender: Gender? = null,
|
||||
lang: Lang
|
||||
): List<HomeLiveRecommendationRecord>
|
||||
|
||||
fun findHomeBanners(
|
||||
@@ -25,7 +26,8 @@ interface HomeRecommendationQueryPort {
|
||||
fun findRecentlyActiveCreators(
|
||||
limit: Int,
|
||||
memberId: Long? = null,
|
||||
includeAdultActivities: Boolean = false
|
||||
includeAdultActivities: Boolean = false,
|
||||
lang: Lang
|
||||
): List<RecentlyActiveCreatorRecord>
|
||||
|
||||
fun findRecentDebutCreators(
|
||||
@@ -33,7 +35,8 @@ interface HomeRecommendationQueryPort {
|
||||
offset: Long = 0,
|
||||
limit: Int,
|
||||
memberId: Long? = null,
|
||||
includeAdultContents: Boolean = false
|
||||
includeAdultContents: Boolean = false,
|
||||
lang: Lang
|
||||
): List<RecentDebutCreatorRecord>
|
||||
|
||||
fun findFirstAudioContents(
|
||||
@@ -72,20 +75,23 @@ interface HomeRecommendationQueryPort {
|
||||
|
||||
fun findCheerCreatorRecommendationDetails(
|
||||
creatorIds: List<Long>,
|
||||
memberId: Long? = null
|
||||
memberId: Long? = null,
|
||||
lang: Lang
|
||||
): List<HomeCheerCreatorRecommendationRecord>
|
||||
|
||||
fun findPopularCommunityRecommendationDetails(
|
||||
communityIds: List<Long>,
|
||||
memberId: Long? = null,
|
||||
includeAdultCommunities: Boolean
|
||||
includeAdultCommunities: Boolean,
|
||||
lang: Lang
|
||||
): List<HomePopularCommunityRecommendationRecord>
|
||||
|
||||
fun findGenreCreatorRecommendations(
|
||||
memberId: Long?,
|
||||
includeAdultGenres: Boolean,
|
||||
genreLimit: Int,
|
||||
creatorLimit: Int
|
||||
creatorLimit: Int,
|
||||
lang: Lang
|
||||
): List<HomeGenreCreatorRecommendationGroup>
|
||||
}
|
||||
|
||||
@@ -95,7 +101,8 @@ data class HomeLiveRecommendationRecord(
|
||||
val creatorProfileImage: String?,
|
||||
val title: String,
|
||||
val price: Int,
|
||||
val beginDateTime: LocalDateTime
|
||||
val beginDateTime: LocalDateTime,
|
||||
val creatorMemberId: Long
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatMessage
|
||||
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.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface UserCreatorChatMessageRepository : JpaRepository<UserCreatorChatMessage, Long> {
|
||||
fun findByChatRoomAndIsActiveTrueOrderByIdDesc(
|
||||
chatRoom: UserCreatorChatRoom,
|
||||
@Query(
|
||||
"""
|
||||
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
|
||||
): List<UserCreatorChatMessage>
|
||||
|
||||
fun findByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(
|
||||
chatRoom: UserCreatorChatRoom,
|
||||
id: Long,
|
||||
pageable: Pageable
|
||||
): List<UserCreatorChatMessage>
|
||||
): List<UserCreatorChatMessageRow>
|
||||
|
||||
fun existsByChatRoomAndIsActiveTrueAndIdLessThan(chatRoom: UserCreatorChatRoom, id: Long): Boolean
|
||||
}
|
||||
|
||||
+25
@@ -1,6 +1,8 @@
|
||||
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.dto.UserCreatorChatOpponentProfile
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
@@ -33,4 +35,27 @@ interface UserCreatorChatParticipantRepository : JpaRepository<UserCreatorChatPa
|
||||
@Param("roomId") roomId: Long,
|
||||
@Param("memberId") memberId: Long
|
||||
): 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
|
||||
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListQueryDto
|
||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.UserCreatorChatRoom
|
||||
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(
|
||||
r.id,
|
||||
'DM',
|
||||
opponent.member.nickname,
|
||||
coalesce(opponentName.displayName, opponentMember.nickname),
|
||||
opponent.member.profileImage,
|
||||
m.textMessage,
|
||||
str(m.messageType),
|
||||
@@ -51,6 +52,12 @@ interface UserCreatorChatRoomRepository : JpaRepository<UserCreatorChatRoom, Lon
|
||||
join r.participants mine
|
||||
join r.participants opponent
|
||||
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
|
||||
and mine.isActive = true
|
||||
and mine.member.id = :memberId
|
||||
@@ -90,6 +97,7 @@ interface UserCreatorChatRoomRepository : JpaRepository<UserCreatorChatRoom, Lon
|
||||
@Param("cursorChatType") cursorChatType: String?,
|
||||
@Param("cursorRoomId") cursorRoomId: Long?,
|
||||
@Param("followedCreatorsOnly") followedCreatorsOnly: Boolean,
|
||||
@Param("lang") lang: Lang,
|
||||
pageable: Pageable
|
||||
): 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.FcmEventType
|
||||
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.MemberKind
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
|
||||
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.UserCreatorChatMessageType
|
||||
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.SendUserCreatorVoiceMessageRequest
|
||||
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.UserCreatorChatRoomOpenResponse
|
||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.repository.UserCreatorChatMessageRepository
|
||||
@@ -52,6 +56,8 @@ class UserCreatorChatService(
|
||||
private val applicationEventPublisher: ApplicationEventPublisher,
|
||||
private val objectMapper: ObjectMapper,
|
||||
private val s3Uploader: S3Uploader,
|
||||
private val localizedNameService: CreatorLocalizedNameService,
|
||||
private val langContext: LangContext,
|
||||
|
||||
@Value("\${cloud.aws.s3.bucket}")
|
||||
private val bucket: String,
|
||||
@@ -80,7 +86,7 @@ class UserCreatorChatService(
|
||||
fun openRoom(member: Member, roomId: Long, limit: Int = 20): UserCreatorChatRoomOpenResponse {
|
||||
val room = findRoom(roomId)
|
||||
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")
|
||||
val opponentProfilePath = opponent.profileImage ?: "profile/default-profile.png"
|
||||
val page = getMessages(member, roomId, cursor = null, limit = limit)
|
||||
@@ -98,12 +104,8 @@ class UserCreatorChatService(
|
||||
val room = findRoom(roomId)
|
||||
requireParticipant(roomId, member.id!!)
|
||||
val pageable = PageRequest.of(0, limit.coerceIn(1, 100))
|
||||
val fetched = if (cursor != null) {
|
||||
messageRepository.findByChatRoomAndIdLessThanAndIsActiveTrueOrderByIdDesc(room, cursor, pageable)
|
||||
} else {
|
||||
messageRepository.findByChatRoomAndIsActiveTrueOrderByIdDesc(room, pageable)
|
||||
}
|
||||
val nextCursor = fetched.minByOrNull { it.id ?: Long.MAX_VALUE }?.id
|
||||
val fetched = messageRepository.findMessageHistory(room, cursor, langContext.lang, pageable)
|
||||
val nextCursor = fetched.minOfOrNull { it.messageId }
|
||||
val hasMore = nextCursor?.let { messageRepository.existsByChatRoomAndIsActiveTrueAndIdLessThan(room, it) } ?: false
|
||||
return UserCreatorChatMessagesPageResponse(
|
||||
messages = fetched.sortedBy { it.createdAt }.map { toMessageItemDto(it, member) },
|
||||
@@ -113,19 +115,25 @@ class UserCreatorChatService(
|
||||
}
|
||||
|
||||
@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")
|
||||
val senderParticipant = validateParticipant(roomId, memberId)
|
||||
val sender = senderParticipant.member
|
||||
val context = resolveSendContext(sender, roomId)
|
||||
val message = saveTextMessage(context, textMessage)
|
||||
val senderMessage = toMessageItemDto(message, sender)
|
||||
val names = localizedNameService.findLocalizedNames(listOf(sender.id!!), lang)
|
||||
val opponent = context.opponentParticipant.member
|
||||
val deliveredRealtime = deliverRealtime(message, opponent)
|
||||
if (!deliveredRealtime) {
|
||||
publishMessagePush(message, sender, opponent)
|
||||
}
|
||||
return senderMessage
|
||||
return senderMessage.copy(senderNickname = names[sender.id] ?: senderMessage.senderNickname)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -185,7 +193,9 @@ class UserCreatorChatService(
|
||||
opponentParticipant: UserCreatorChatParticipant
|
||||
): SendUserCreatorChatMessageResponse {
|
||||
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)
|
||||
if (deliveredRealtime) {
|
||||
return SendUserCreatorChatMessageResponse(message = item, deliveredRealtime = true, pushSent = false)
|
||||
@@ -239,6 +249,7 @@ class UserCreatorChatService(
|
||||
messageKey = messageKey,
|
||||
senderMemberId = sender.id,
|
||||
args = listOf(sender.nickname),
|
||||
nameArgumentMemberIds = mapOf(0 to sender.id!!),
|
||||
recipients = listOf(opponent.id!!),
|
||||
deepLinkValue = FcmDeepLinkValue.CHAT,
|
||||
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(
|
||||
val room: UserCreatorChatRoom,
|
||||
val senderParticipant: UserCreatorChatParticipant,
|
||||
|
||||
+23
-3
@@ -1,6 +1,9 @@
|
||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||
|
||||
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.MessageListener
|
||||
import org.springframework.data.redis.core.StringRedisTemplate
|
||||
@@ -16,7 +19,8 @@ class UserCreatorChatRoomMessageBroker(
|
||||
private val stringRedisTemplate: StringRedisTemplate,
|
||||
private val sessionRegistry: UserCreatorChatWebSocketSessionRegistry,
|
||||
private val objectMapper: ObjectMapper,
|
||||
listenerContainer: RedisMessageListenerContainer
|
||||
listenerContainer: RedisMessageListenerContainer,
|
||||
private val localizedNameService: CreatorLocalizedNameService
|
||||
) : MessageListener {
|
||||
init {
|
||||
listenerContainer.addMessageListener(this, ChannelTopic(ROOM_CHANNEL))
|
||||
@@ -36,9 +40,25 @@ class UserCreatorChatRoomMessageBroker(
|
||||
String(message.body, StandardCharsets.UTF_8),
|
||||
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 }
|
||||
.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) {
|
||||
|
||||
+4
@@ -1,8 +1,10 @@
|
||||
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.TokenProvider
|
||||
import kr.co.vividnext.sodalive.member.MemberAdapter
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.server.ServerHttpRequest
|
||||
import org.springframework.http.server.ServerHttpResponse
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -35,6 +37,7 @@ class UserCreatorChatWebSocketAuthInterceptor(
|
||||
|
||||
attributes[MEMBER_ID_ATTRIBUTE] = memberId
|
||||
attributes[AUTHENTICATION_ATTRIBUTE] = authentication
|
||||
attributes[LANG_ATTRIBUTE] = Lang.fromAcceptLanguage(request.headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE))
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -58,6 +61,7 @@ class UserCreatorChatWebSocketAuthInterceptor(
|
||||
companion object {
|
||||
const val MEMBER_ID_ATTRIBUTE = "memberId"
|
||||
const val AUTHENTICATION_ATTRIBUTE = "authentication"
|
||||
const val LANG_ATTRIBUTE = "lang"
|
||||
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 kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.socket.CloseStatus
|
||||
@@ -60,7 +61,8 @@ class UserCreatorChatWebSocketHandler(
|
||||
val message = service.sendTextMessageByWebSocket(
|
||||
memberId = memberId(session),
|
||||
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)
|
||||
} 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.configs.SecurityConfig
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
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.common.domain.ContentSort
|
||||
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.Test
|
||||
import org.mockito.Mockito
|
||||
@@ -41,6 +43,11 @@ class MainContentAllControllerTest @Autowired constructor(
|
||||
@MockBean
|
||||
private lateinit var langContext: LangContext
|
||||
|
||||
@BeforeEach
|
||||
fun setUpLanguage() {
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
}
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@@ -57,7 +64,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
||||
@DisplayName("전체 탭 조회는 비회원에게 200 OK와 기본 응답을 반환한다")
|
||||
fun shouldAllowAnonymousAndUseDefaultType() {
|
||||
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"))
|
||||
.andExpect(status().isOk)
|
||||
@@ -76,7 +83,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
||||
role = MemberRole.USER
|
||||
).apply { id = 10L }
|
||||
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(
|
||||
get("/api/v2/audio/contents")
|
||||
@@ -92,7 +99,7 @@ class MainContentAllControllerTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.data.type").value("SERIES"))
|
||||
.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 {
|
||||
|
||||
+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.MemberRole
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import org.hamcrest.Matchers.contains
|
||||
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.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
@@ -30,6 +34,7 @@ import javax.persistence.EntityManager
|
||||
properties = [
|
||||
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||
"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"
|
||||
]
|
||||
)
|
||||
@@ -39,8 +44,107 @@ import javax.persistence.EntityManager
|
||||
class MainContentAllEndToEndTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
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
|
||||
@DisplayName("전체 탭 AUDIO API는 controller-service-repository를 거쳐 오디오 응답과 빈 series를 반환한다")
|
||||
fun shouldReturnAudioContentsThroughControllerServiceAndRepository() {
|
||||
@@ -196,7 +300,7 @@ class MainContentAllEndToEndTest @Autowired constructor(
|
||||
entityManager.flush()
|
||||
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(
|
||||
val audioContentId: Long
|
||||
val audioContentId: Long,
|
||||
val creatorId: Long
|
||||
)
|
||||
|
||||
private data class SeriesFixture(
|
||||
|
||||
+5
-3
@@ -1,5 +1,6 @@
|
||||
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.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
@@ -36,7 +37,7 @@ class MainContentAllFacadeTest {
|
||||
page = MainContentPage(1, 30),
|
||||
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(
|
||||
type = "FREE",
|
||||
@@ -44,10 +45,11 @@ class MainContentAllFacadeTest {
|
||||
dayOfWeek = "MON",
|
||||
page = 1,
|
||||
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(ContentSort.PRICE_LOW, response.sort)
|
||||
}
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ class MainContentAllTabResponseTest {
|
||||
audios = emptyList(),
|
||||
series = listOf(
|
||||
MainContentAllSeries(
|
||||
creatorMemberId = 100L,
|
||||
seriesId = 10L,
|
||||
title = "시리즈",
|
||||
coverImageUrl = "https://cdn/series.jpg",
|
||||
@@ -58,6 +59,7 @@ class MainContentAllTabResponseTest {
|
||||
totalCount = 1,
|
||||
audios = listOf(
|
||||
MainContentAllAudio(
|
||||
creatorMemberId = 100L,
|
||||
audioContentId = 1L,
|
||||
title = "audio",
|
||||
imageUrl = "https://cdn/audio.jpg",
|
||||
@@ -69,6 +71,7 @@ class MainContentAllTabResponseTest {
|
||||
creatorNickname = "creator"
|
||||
),
|
||||
MainContentAllAudio(
|
||||
creatorMemberId = 100L,
|
||||
audioContentId = 2L,
|
||||
title = "free audio",
|
||||
imageUrl = "https://cdn/free-audio.jpg",
|
||||
@@ -80,6 +83,7 @@ class MainContentAllTabResponseTest {
|
||||
creatorNickname = "creator"
|
||||
),
|
||||
MainContentAllAudio(
|
||||
creatorMemberId = 100L,
|
||||
audioContentId = 3L,
|
||||
title = "point unavailable audio",
|
||||
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.configs.SecurityConfig
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
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.dto.ContentOverviewPageResponse
|
||||
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.Test
|
||||
import org.mockito.Mockito
|
||||
@@ -41,6 +43,11 @@ class ContentOverviewControllerTest @Autowired constructor(
|
||||
@MockBean
|
||||
private lateinit var langContext: LangContext
|
||||
|
||||
@BeforeEach
|
||||
fun setUpLanguage() {
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
}
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@@ -62,7 +69,7 @@ class ContentOverviewControllerTest @Autowired constructor(
|
||||
fun shouldPassAuthenticatedMemberAndQueryParameters() {
|
||||
val member = member(id = 10L)
|
||||
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(
|
||||
get("/api/v2/contents")
|
||||
@@ -75,7 +82,8 @@ class ContentOverviewControllerTest @Autowired constructor(
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
.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 {
|
||||
|
||||
+7
-4
@@ -1,5 +1,6 @@
|
||||
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.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
@@ -31,9 +32,9 @@ class ContentOverviewFacadeTest {
|
||||
fun shouldReturnNewAndHotPage() {
|
||||
val member = member(id = 10L)
|
||||
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((1L..20L).toList(), response.items.map { it.contentId })
|
||||
@@ -54,10 +55,11 @@ class ContentOverviewFacadeTest {
|
||||
eqValue(20L),
|
||||
eqValue(21),
|
||||
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(listOf(1L, 2L), response.items.map { it.contentId })
|
||||
@@ -79,6 +81,7 @@ class ContentOverviewFacadeTest {
|
||||
|
||||
private fun audioCard(id: Long): AudioCard {
|
||||
return AudioCard(
|
||||
creatorMemberId = id + 100L,
|
||||
audioContentId = id,
|
||||
title = "audio$id",
|
||||
duration = "00:01",
|
||||
|
||||
+1
@@ -120,6 +120,7 @@ class ContentOverviewPageResponseTest {
|
||||
|
||||
private fun audioCard(price: Int, isPointAvailable: Boolean): AudioCard {
|
||||
return AudioCard(
|
||||
creatorMemberId = 10L,
|
||||
audioContentId = price.toLong() + 1,
|
||||
title = "audio",
|
||||
duration = "00:01",
|
||||
|
||||
+5
-3
@@ -1,5 +1,6 @@
|
||||
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.v2.content.ranking.application.AudioRankingQueryService
|
||||
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRanking
|
||||
@@ -23,6 +24,7 @@ class AudioRankingFacadeTest {
|
||||
type = AudioRankingType.RISING,
|
||||
items = listOf(
|
||||
AudioRankingItem(
|
||||
creatorMemberId = 10L,
|
||||
contentId = 1L,
|
||||
title = "audio",
|
||||
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(AudioRankingType.RISING, response.type)
|
||||
assertEquals(1, response.items.size)
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ class AudioRankingResponseTest {
|
||||
type = AudioRankingType.RISING,
|
||||
items = listOf(
|
||||
AudioRankingItem(
|
||||
creatorMemberId = 10L,
|
||||
contentId = 1001L,
|
||||
title = "rising audio",
|
||||
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.configs.SecurityConfig
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
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.v2.api.content.recommendation.application.AudioRecommendationFacade
|
||||
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.Test
|
||||
import org.mockito.Mockito
|
||||
@@ -39,6 +41,11 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
||||
@MockBean
|
||||
private lateinit var langContext: LangContext
|
||||
|
||||
@BeforeEach
|
||||
fun setUpLanguage() {
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
}
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@@ -54,7 +61,7 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
||||
@Test
|
||||
@DisplayName("오디오 추천 조회는 비회원에게 200 OK와 ApiResponse.ok wrapper를 반환한다")
|
||||
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"))
|
||||
.andExpect(status().isOk)
|
||||
@@ -74,13 +81,13 @@ class AudioRecommendationControllerTest @Autowired constructor(
|
||||
nickname = "viewer",
|
||||
role = MemberRole.USER
|
||||
).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))))
|
||||
.andExpect(status().isOk)
|
||||
.andExpect(jsonPath("$.success").value(true))
|
||||
|
||||
Mockito.verify(facade).getRecommendations(eqValue(member))
|
||||
Mockito.verify(facade).getRecommendations(eqValue(member), eqValue(Lang.KO))
|
||||
}
|
||||
|
||||
private fun emptyResponse(): AudioRecommendationsResponse {
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.content.recommendation.application
|
||||
|
||||
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.content.recommendation.application.AudioRecommendationQueryService
|
||||
import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard
|
||||
@@ -20,9 +21,9 @@ class AudioRecommendationFacadeTest {
|
||||
@Test
|
||||
@DisplayName("facade는 도메인 추천 결과를 모든 공개 응답 필드로 변환한다")
|
||||
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.originalSeries.size)
|
||||
@@ -49,6 +50,7 @@ class AudioRecommendationFacadeTest {
|
||||
|
||||
private fun domain(): AudioRecommendations {
|
||||
val card = AudioCard(
|
||||
creatorMemberId = 10L,
|
||||
audioContentId = 1L,
|
||||
title = "audio",
|
||||
duration = "00:01",
|
||||
|
||||
+15
-4
@@ -1,6 +1,7 @@
|
||||
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.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
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.common.dto.CreatorChannelAudioContentResponse
|
||||
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.Test
|
||||
import org.mockito.Mockito
|
||||
@@ -47,6 +49,11 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
||||
@MockBean
|
||||
private lateinit var langContext: LangContext
|
||||
|
||||
@BeforeEach
|
||||
fun setUpLanguage() {
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
}
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@@ -88,7 +95,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
||||
eqValue(null),
|
||||
eqValue(null),
|
||||
eqValue(null),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -118,7 +126,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
||||
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(-1),
|
||||
eqValue(100),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -158,7 +168,8 @@ class CreatorChannelAudioControllerTest @Autowired constructor(
|
||||
eqValue(999L),
|
||||
eqValue(-1),
|
||||
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.module.kotlin.KotlinModule
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
@@ -33,7 +34,8 @@ class CreatorChannelAudioFacadeTest {
|
||||
themeId = 10L,
|
||||
page = 0,
|
||||
size = 20,
|
||||
now = now
|
||||
now = now,
|
||||
lang = Lang.KO
|
||||
)
|
||||
|
||||
val response = facade.getAudioTab(
|
||||
@@ -43,7 +45,8 @@ class CreatorChannelAudioFacadeTest {
|
||||
themeId = 10L,
|
||||
page = 0,
|
||||
size = 20,
|
||||
now = now
|
||||
now = now,
|
||||
lang = Lang.KO
|
||||
)
|
||||
|
||||
assertEquals(3, response.audioContentCount)
|
||||
|
||||
+29
-11
@@ -1,6 +1,7 @@
|
||||
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.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
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.CreatorChannelCommunityReplyResponse
|
||||
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.Test
|
||||
import org.mockito.Mockito
|
||||
@@ -50,6 +52,11 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
@MockBean
|
||||
private lateinit var langContext: LangContext
|
||||
|
||||
@BeforeEach
|
||||
fun setUpLanguage() {
|
||||
Mockito.`when`(langContext.lang).thenReturn(Lang.KO)
|
||||
}
|
||||
|
||||
@MockBean
|
||||
private lateinit var sodaMessageSource: SodaMessageSource
|
||||
|
||||
@@ -89,7 +96,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(1),
|
||||
eqValue(20),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -117,7 +125,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(1),
|
||||
eqValue(20),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,7 +139,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
eqValue(100),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -148,7 +158,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
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(
|
||||
eqValue(101L),
|
||||
eqValue(viewer),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -195,7 +207,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(0),
|
||||
eqValue(20),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -222,7 +235,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
eqValue(100),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -240,7 +254,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
eqValue(100),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -253,7 +268,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(0),
|
||||
eqValue(20),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -278,7 +294,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
eqValue(100),
|
||||
anyValue(LocalDateTime.now())
|
||||
anyValue(LocalDateTime.now()),
|
||||
eqValue(Lang.KO)
|
||||
)
|
||||
|
||||
mockMvc.perform(
|
||||
@@ -296,7 +313,8 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
|
||||
eqValue(viewer),
|
||||
eqValue(-1),
|
||||
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.context.SpringBootTest
|
||||
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.test.context.ContextConfiguration
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
@@ -44,7 +45,8 @@ import javax.persistence.EntityManager
|
||||
class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
||||
private val mockMvc: MockMvc,
|
||||
private val entityManager: EntityManager,
|
||||
private val transactionTemplate: TransactionTemplate
|
||||
private val transactionTemplate: TransactionTemplate,
|
||||
private val jdbcTemplate: JdbcTemplate
|
||||
) {
|
||||
@MockBean
|
||||
private lateinit var audioContentCloudFront: AudioContentCloudFront
|
||||
@@ -52,6 +54,67 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
|
||||
@MockBean
|
||||
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
|
||||
@DisplayName("커뮤니티 탭 API는 E2E로 정렬, fallback, 성인 필터, 유료 미디어 접근 정책을 반환한다")
|
||||
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.module.kotlin.KotlinModule
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityTabResponse
|
||||
@@ -71,7 +72,8 @@ class CreatorChannelCommunityFacadeTest {
|
||||
viewer = viewer,
|
||||
page = -1,
|
||||
size = 100,
|
||||
now = now
|
||||
now = now,
|
||||
lang = Lang.KO
|
||||
)
|
||||
|
||||
val response = facade.getCommunityTab(
|
||||
@@ -79,7 +81,8 @@ class CreatorChannelCommunityFacadeTest {
|
||||
viewer = viewer,
|
||||
page = -1,
|
||||
size = 100,
|
||||
now = now
|
||||
now = now,
|
||||
lang = Lang.KO
|
||||
)
|
||||
|
||||
assertEquals(2, response.communityPostCount)
|
||||
@@ -99,9 +102,9 @@ class CreatorChannelCommunityFacadeTest {
|
||||
val facade = CreatorChannelCommunityFacade(service)
|
||||
val viewer = createMember(id = 10L)
|
||||
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)
|
||||
assertTrue(response.isLiked)
|
||||
@@ -119,9 +122,9 @@ class CreatorChannelCommunityFacadeTest {
|
||||
val facade = CreatorChannelCommunityFacade(service)
|
||||
val viewer = createMember(id = 10L)
|
||||
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(0, response.page)
|
||||
@@ -139,9 +142,9 @@ class CreatorChannelCommunityFacadeTest {
|
||||
val facade = CreatorChannelCommunityFacade(service)
|
||||
val viewer = createMember(id = 10L)
|
||||
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(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