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

This commit is contained in:
2026-10-31 20:33:22 +09:00
parent 4a6b2ee9e5
commit 1f24a4b703
3 changed files with 338 additions and 6 deletions
@@ -30,6 +30,7 @@ import kr.co.vividnext.sodalive.member.MemberKind
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.member.following.QCreatorFollowing
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
@@ -58,6 +59,7 @@ class DefaultHomeRecommendationQueryRepository(
private val queryFactory: JPAQueryFactory,
private val entityManager: EntityManager,
private val contentTranslationRepository: ContentTranslationRepository,
private val eventNowProvider: () -> LocalDateTime = { LocalDateTime.now() },
private val nowProvider: () -> LocalDateTime = { LocalDateTime.now(ZoneOffset.UTC) }
) : HomeRecommendationQueryRepository {
override fun findLiveRecommendations(
@@ -137,7 +139,7 @@ class DefaultHomeRecommendationQueryRepository(
audioContentBanner.lang.eq(lang),
audioContentBanner.isVisibleAt(nowProvider()),
includeAdultBannerCondition(includeAdultBanners),
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner)
activeBannerTargetCondition(memberId, bannerCreator, seriesOwner, lang)
)
.orderBy(audioContentBanner.orders.asc(), randomTieBreaker.asc())
.limit(limit.toLong())
@@ -1298,8 +1300,16 @@ class DefaultHomeRecommendationQueryRepository(
private fun activeBannerTargetCondition(
memberId: Long?,
bannerCreator: QMember,
seriesOwner: 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))
@@ -1309,11 +1319,24 @@ class DefaultHomeRecommendationQueryRepository(
.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 BooleanExpression.withOptionalAnd(condition: BooleanExpression?): BooleanExpression {
return if (condition == null) this else and(condition)
}