fix(audio-recommendation): 이벤트 상세 노출 조건으로 배너를 필터링한다

This commit is contained in:
2026-10-31 20:59:52 +09:00
parent 1f24a4b703
commit 969f999ae9
3 changed files with 401 additions and 5 deletions
@@ -18,8 +18,10 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seri
import kr.co.vividnext.sodalive.event.EventItem
import kr.co.vividnext.sodalive.event.QEvent.event
import kr.co.vividnext.sodalive.i18n.Lang
import kr.co.vividnext.sodalive.member.MemberRole
import kr.co.vividnext.sodalive.member.QMember
import kr.co.vividnext.sodalive.member.QMember.member
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
import kr.co.vividnext.sodalive.member.block.QBlockMember
import kr.co.vividnext.sodalive.v2.common.domain.RecommendationBanner
import kr.co.vividnext.sodalive.v2.common.domain.toCdnUrl
@@ -45,6 +47,7 @@ class DefaultAudioRecommendationQueryRepository(
private val contentTranslationRepository: ContentTranslationRepository,
@Value("\${cloud.aws.cloud-front.host}")
private val cloudFrontHost: String,
private val eventNowProvider: () -> LocalDateTime = { LocalDateTime.now() },
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(ZoneOffset.UTC) }
) : AudioRecommendationQueryRepository {
override fun findBanners(
@@ -79,7 +82,7 @@ class DefaultAudioRecommendationQueryRepository(
audioContentBanner.lang.eq(lang),
audioContentBanner.isVisibleAt(nowProvider()),
adultBannerCondition(canViewAdultContent),
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner, lang)
)
.orderBy(audioContentBanner.orders.asc(), randomTieBreaker.asc())
.limit(limit.toLong())
@@ -560,7 +563,19 @@ class DefaultAudioRecommendationQueryRepository(
.withOptionalAnd(notBlockedCreatorCondition(memberId, audioContent.member.id))
}
private fun activeBannerTargetCondition(memberId: Long?, bannerCreator: QMember, seriesOwner: QMember): BooleanExpression {
private fun activeBannerTargetCondition(
memberId: Long?,
bannerCreator: QMember,
seriesOwner: QMember,
lang: Lang
): BooleanExpression {
val eventNow = eventNowProvider()
val eventCondition = audioContentBanner.type.eq(AudioContentBannerType.EVENT)
.and(event.isActive.isTrue)
.and(event.startDate.loe(eventNow))
.and(event.endDate.goe(eventNow))
.and(event.lang.eq(lang))
.and(eventAudienceCondition(memberId))
val creatorCondition = audioContentBanner.type.eq(AudioContentBannerType.CREATOR)
.and(bannerCreator.isActive.isTrue)
.withOptionalAnd(notBlockedCreatorCondition(memberId, bannerCreator.id))
@@ -570,11 +585,24 @@ class DefaultAudioRecommendationQueryRepository(
.withOptionalAnd(notBlockedCreatorCondition(memberId, seriesOwner.id))
return audioContentBanner.type.eq(AudioContentBannerType.LINK)
.or(audioContentBanner.type.eq(AudioContentBannerType.EVENT).and(event.isActive.isTrue))
.or(eventCondition)
.or(creatorCondition)
.or(seriesCondition)
}
private fun eventAudienceCondition(memberId: Long?): BooleanExpression {
if (memberId == null) return event.isAdult.isNull.or(event.isAdult.isFalse)
val eventViewer = QMember("eventViewer")
val isAdmin = JPAExpressions.selectOne().from(eventViewer)
.where(eventViewer.id.eq(memberId), eventViewer.role.eq(MemberRole.ADMIN))
.exists()
val viewerAuth = JPAExpressions.selectOne().from(auth).where(auth.member.id.eq(memberId))
return event.isAdult.isNull
.or(isAdmin)
.or(event.isAdult.isTrue.and(viewerAuth.exists()))
.or(event.isAdult.isFalse.and(viewerAuth.notExists()))
}
private fun Tuple.toEventItem(): EventItem? {
val eventId = get(event.id) ?: return null
val thumbnailImage = get(event.thumbnailImage) ?: return null