fix(subbanner): 언어 설정과 국가별 공개 필터를 추가한다

This commit is contained in:
2026-11-03 00:30:02 +09:00
parent 255da2b6aa
commit c4c61e3615
17 changed files with 318 additions and 32 deletions
@@ -18,7 +18,7 @@ class AudioRecommendationFacade(
) {
fun getRecommendations(member: Member?, lang: Lang): AudioRecommendationsResponse {
val result = queryService.getRecommendations(member, lang)
val subBanners = subBannerService.findVisibleByPlacement(SubBannerPlacement.CONTENT_RECOMMENDATION)
val subBanners = subBannerService.findVisibleByPlacement(SubBannerPlacement.CONTENT_RECOMMENDATION, member)
.map { SubBannerResponse.from(it, cloudFrontHost) }
return AudioRecommendationsResponse.from(result, subBanners)
}
@@ -72,7 +72,7 @@ class HomeRecommendationFacade(
lang = bannerLang
)
.map { RecommendationBannerResponse.from(it.toBanner()) },
subBanners = subBannerService.findVisibleByPlacement(SubBannerPlacement.HOME_RECOMMENDATION)
subBanners = subBannerService.findVisibleByPlacement(SubBannerPlacement.HOME_RECOMMENDATION, member)
.map { SubBannerResponse.from(it, cloudFrontHost) },
recentlyActiveCreators = queryService.findRecentlyActiveCreators(
HOME_ACTIVE_CREATOR_LIMIT,
@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.v2.subbanner.adapter.out.persistence
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.subbanner.adapter.out.persistence.QSubBannerEntity.subBannerEntity
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBanner
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerPlacement
@@ -25,12 +26,13 @@ class DefaultSubBannerRepository(
.fetch()
.map { it.toDomain() }
override fun findVisibleByPlacement(placement: SubBannerPlacement, now: LocalDateTime): List<SubBanner> {
override fun findVisibleByPlacement(placement: SubBannerPlacement, lang: Lang, now: LocalDateTime): List<SubBanner> {
val visibleAt = now.truncatedTo(ChronoUnit.SECONDS)
return queryFactory
.selectFrom(subBannerEntity)
.where(
subBannerEntity.placement.eq(placement),
subBannerEntity.lang.eq(lang),
subBannerEntity.isActive.isTrue,
subBannerEntity.startDate.isNull.or(subBannerEntity.startDate.loe(visibleAt)),
subBannerEntity.endDate.isNull.or(subBannerEntity.endDate.goe(visibleAt))
@@ -51,7 +53,8 @@ class DefaultSubBannerRepository(
subBanner.androidDeepLink,
subBanner.iosDeepLink,
startDate,
endDate
endDate,
lang = subBanner.lang
).also { entityManager.persist(it) }
} else {
entityManager.find(SubBannerEntity::class.java, subBanner.id).apply {
@@ -62,6 +65,7 @@ class DefaultSubBannerRepository(
iosDeepLink = subBanner.iosDeepLink
this.startDate = startDate
this.endDate = endDate
lang = subBanner.lang
}
}
return entity.toDomain()
@@ -1,6 +1,7 @@
package kr.co.vividnext.sodalive.v2.subbanner.adapter.out.persistence
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBanner
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerPlacement
import org.hibernate.annotations.DynamicUpdate
@@ -39,7 +40,12 @@ class SubBannerEntity(
var endDate: LocalDateTime?,
@Column(name = "is_active", nullable = false)
var isActive: Boolean = true
var isActive: Boolean = true,
@Enumerated(EnumType.STRING)
@Column(name = "lang", nullable = false, length = 10)
var lang: Lang = Lang.KO
) : BaseEntity() {
fun toDomain(): SubBanner = SubBanner(id, placement, imagePath, webUrl, androidDeepLink, iosDeepLink, startDate, endDate)
fun toDomain(): SubBanner =
SubBanner(id, placement, imagePath, webUrl, androidDeepLink, iosDeepLink, startDate, endDate, lang)
}
@@ -6,6 +6,9 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
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.contentpreference.MemberContentPreferenceService
import kr.co.vividnext.sodalive.utils.generateFileName
import kr.co.vividnext.sodalive.utils.validateImage
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBanner
@@ -33,6 +36,7 @@ class SubBannerService(
private val repository: SubBannerRepository,
private val s3Uploader: S3Uploader,
private val objectMapper: ObjectMapper,
private val memberContentPreferenceService: MemberContentPreferenceService,
@Value("\${cloud.aws.s3.bucket}") private val bucket: String,
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(ZoneOffset.UTC) }
) {
@@ -40,9 +44,10 @@ class SubBannerService(
fun findAllByPlacement(placement: SubBannerPlacement): List<SubBanner> = repository.findAllByPlacement(placement)
@Transactional(readOnly = true)
fun findVisibleByPlacement(placement: SubBannerPlacement): List<SubBanner> {
fun findVisibleByPlacement(placement: SubBannerPlacement, member: Member?): List<SubBanner> {
val now = nowProvider().truncatedTo(ChronoUnit.SECONDS)
return repository.findVisibleByPlacement(placement, now)
val lang = if (memberContentPreferenceService.resolveCountryCode(member) == "JP") Lang.JA else Lang.KO
return repository.findVisibleByPlacement(placement, lang, now)
}
@Transactional
@@ -61,7 +66,8 @@ class SubBannerService(
androidDeepLink = input.androidDeepLink,
iosDeepLink = input.iosDeepLink,
startDate = startDate,
endDate = endDate
endDate = endDate,
lang = input.lang
)
)
}
@@ -83,7 +89,8 @@ class SubBannerService(
androidDeepLink = input.androidDeepLink,
iosDeepLink = input.iosDeepLink,
startDate = startDate,
endDate = endDate
endDate = endDate,
lang = if (fields.has("lang")) input.lang else existing.lang
)
)
}
@@ -118,6 +125,14 @@ class SubBannerService(
val value = fields[field]
if (value != null && !value.isNull && !value.isTextual) invalidRequest()
}
fields["lang"]?.let { value ->
if (!value.isTextual) invalidRequest()
try {
Lang.fromCode(value.textValue())
} catch (_: IllegalArgumentException) {
invalidRequest()
}
}
return fields
}
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.subbanner.domain
import kr.co.vividnext.sodalive.i18n.Lang
import java.time.LocalDateTime
data class SubBanner(
@@ -10,7 +11,8 @@ data class SubBanner(
val androidDeepLink: String?,
val iosDeepLink: String?,
val startDate: LocalDateTime?,
val endDate: LocalDateTime?
val endDate: LocalDateTime?,
val lang: Lang = Lang.KO
) {
init {
require(!webUrl.isNullOrBlank() || !androidDeepLink.isNullOrBlank() || !iosDeepLink.isNullOrBlank()) {
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.subbanner.dto
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBanner
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerPlacement
import java.time.LocalDateTime
@@ -15,7 +16,8 @@ data class AdminSubBannerResponse(
val androidDeepLink: String?,
val iosDeepLink: String?,
val startDate: String?,
val endDate: String?
val endDate: String?,
val lang: Lang
) {
companion object {
fun from(banner: SubBanner, cloudFrontHost: String): AdminSubBannerResponse = AdminSubBannerResponse(
@@ -26,7 +28,8 @@ data class AdminSubBannerResponse(
androidDeepLink = banner.androidDeepLink,
iosDeepLink = banner.iosDeepLink,
startDate = formatDate(banner.startDate),
endDate = formatDate(banner.endDate)
endDate = formatDate(banner.endDate),
lang = banner.lang
)
private fun formatDate(value: LocalDateTime?): String? = value?.atZone(ZoneOffset.UTC)
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.subbanner.dto
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerPlacement
data class SubBannerRequest(
@@ -8,5 +9,6 @@ data class SubBannerRequest(
val androidDeepLink: String?,
val iosDeepLink: String?,
val startDate: String? = null,
val endDate: String? = null
val endDate: String? = null,
val lang: Lang = Lang.KO
)
@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.subbanner.port.out
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBanner
import kr.co.vividnext.sodalive.v2.subbanner.domain.SubBannerPlacement
import java.time.LocalDateTime
@@ -7,7 +8,7 @@ import java.time.LocalDateTime
interface SubBannerRepository {
fun findById(id: Long): SubBanner?
fun findAllByPlacement(placement: SubBannerPlacement): List<SubBanner>
fun findVisibleByPlacement(placement: SubBannerPlacement, now: LocalDateTime): List<SubBanner>
fun findVisibleByPlacement(placement: SubBannerPlacement, lang: Lang, now: LocalDateTime): List<SubBanner>
fun save(subBanner: SubBanner): SubBanner
fun deleteById(id: Long)
}