feat(home): 최근 활동 이동 식별자를 보강한다

This commit is contained in:
2026-07-30 14:42:46 +09:00
parent b30447f004
commit e6f56f24fe
10 changed files with 456 additions and 20 deletions

View File

@@ -245,6 +245,7 @@ class HomeRecommendationFacade(
}
private fun RecentlyActiveCreatorRecord.toItem() = HomeActiveCreatorItem(
creatorId = creatorId,
creatorNickname = creatorNickname,
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage),
activityType = activityType.name,

View File

@@ -36,6 +36,7 @@ data class HomeLiveItem(
)
data class HomeActiveCreatorItem(
val creatorId: Long,
val creatorNickname: String,
val creatorProfileImage: String,
val activityType: String,

View File

@@ -127,7 +127,8 @@ class DefaultHomeRecommendationQueryRepository(
includeAdultActivities: Boolean
): List<RecentlyActiveCreatorRecord> {
val sql = """
select ranked.creator_nickname,
select ranked.creator_id,
ranked.creator_nickname,
ranked.creator_profile_image,
ranked.activity_type,
ranked.activity_at,
@@ -144,7 +145,7 @@ class DefaultHomeRecommendationQueryRepository(
m.profile_image as creator_profile_image,
'LIVE' as activity_type,
lr.begin_date_time as activity_at,
null as target_id,
case when lr.is_active = true then lr.id else null end as target_id,
lr.id as target_sort_id
from live_room lr
join member m on m.id = lr.member_id
@@ -199,11 +200,12 @@ class DefaultHomeRecommendationQueryRepository(
return rows.map { row ->
RecentlyActiveCreatorRecord(
creatorNickname = row[0] as String,
creatorProfileImage = row[1] as String?,
activityType = CreatorActivityType.valueOf(row[2] as String),
activityAt = toLocalDateTime(row[3]),
targetId = (row[4] as Number?)?.toLong()
creatorId = (row[0] as Number).toLong(),
creatorNickname = row[1] as String,
creatorProfileImage = row[2] as String?,
activityType = CreatorActivityType.valueOf(row[3] as String),
activityAt = toLocalDateTime(row[4]),
targetId = (row[5] as Number?)?.toLong()
)
}
}

View File

@@ -103,6 +103,7 @@ data class HomeBannerRecommendationRecord(
)
data class RecentlyActiveCreatorRecord(
val creatorId: Long,
val creatorNickname: String,
val creatorProfileImage: String?,
val activityType: CreatorActivityType,

View File

@@ -524,6 +524,28 @@ class HomeRecommendationControllerTest @Autowired constructor(
.andExpect(jsonPath("$.data.items[0].creatorId").value(character.creatorMember!!.id))
}
@Test
@DisplayName("최근 활동 라이브 크리에이터는 creatorId와 라이브 상태별 targetId를 노출한다")
fun shouldExposeNavigationIdsForRecentlyActiveLiveCreators() {
val activeCreator = saveMember("active-live-activity", MemberRole.CREATOR)
val inactiveCreator = saveMember("inactive-live-activity", MemberRole.CREATOR)
val baseAt = LocalDateTime.of(2026, 5, 31, 10, 0)
val activeLive = saveLiveRoom(activeCreator, baseAt.plusMinutes(2), "active-live-channel", isAdult = false)
saveLiveRoom(inactiveCreator, baseAt.plusMinutes(1), "inactive-live-channel", isAdult = false, isActive = false)
entityManager.flush()
entityManager.clear()
mockMvc.perform(get("/api/v2/home/recommendations"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.data.recentlyActiveCreators[0].creatorId").value(activeCreator.id))
.andExpect(jsonPath("$.data.recentlyActiveCreators[0].activityType").value("LIVE"))
.andExpect(jsonPath("$.data.recentlyActiveCreators[0].targetId").value(activeLive.id))
.andExpect(jsonPath("$.data.recentlyActiveCreators[1].creatorId").value(inactiveCreator.id))
.andExpect(jsonPath("$.data.recentlyActiveCreators[1].activityType").value("LIVE"))
.andExpect(jsonPath("$.data.recentlyActiveCreators[1].targetId").hasJsonPath())
.andExpect(jsonPath("$.data.recentlyActiveCreators[1].targetId").doesNotExist())
}
private fun saveMember(seed: String, role: MemberRole): Member {
return memberRepository.saveAndFlush(
Member(
@@ -548,7 +570,8 @@ class HomeRecommendationControllerTest @Autowired constructor(
creator: Member,
beginDateTime: LocalDateTime,
channelName: String,
isAdult: Boolean
isAdult: Boolean,
isActive: Boolean = true
): LiveRoom {
val room = LiveRoom(
title = "live-${creator.nickname}-$channelName",
@@ -559,6 +582,7 @@ class HomeRecommendationControllerTest @Autowired constructor(
)
room.member = creator
room.channelName = channelName
room.isActive = isActive
entityManager.persist(room)
return room
}

View File

@@ -411,7 +411,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
val replayCreator = saveMember("activity-replay", MemberRole.CREATOR)
val communityCreator = saveMember("activity-community", MemberRole.CREATOR)
saveAudioContent(liveCreator, baseAt.minusDays(2), isActive = true)
saveLiveRoom(liveCreator, baseAt, channelName = "activity-live-channel")
val live = saveLiveRoom(liveCreator, baseAt, channelName = "activity-live-channel")
val audio = saveAudioContent(audioCreator, baseAt.minusHours(1), isActive = true)
val replay = saveAudioContent(replayCreator, baseAt.minusHours(2), isActive = true, themeName = "다시듣기")
val community = saveCommunity(communityCreator, isCommentAvailable = true)
@@ -426,8 +426,12 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
listOf(liveCreator.nickname, audioCreator.nickname, replayCreator.nickname, communityCreator.nickname),
creators.map { it.creatorNickname }
)
assertEquals(liveCreator.id, byCreatorNickname[liveCreator.nickname]!!.creatorId)
assertEquals(audioCreator.id, byCreatorNickname[audioCreator.nickname]!!.creatorId)
assertEquals(replayCreator.id, byCreatorNickname[replayCreator.nickname]!!.creatorId)
assertEquals(communityCreator.id, byCreatorNickname[communityCreator.nickname]!!.creatorId)
assertEquals(CreatorActivityType.LIVE, byCreatorNickname[liveCreator.nickname]!!.activityType)
assertEquals(null, byCreatorNickname[liveCreator.nickname]!!.targetId)
assertEquals(live.id, byCreatorNickname[liveCreator.nickname]!!.targetId)
assertEquals(baseAt, byCreatorNickname[liveCreator.nickname]!!.activityAt)
assertEquals(CreatorActivityType.AUDIO, byCreatorNickname[audioCreator.nickname]!!.activityType)
assertEquals(audio.id, byCreatorNickname[audioCreator.nickname]!!.targetId)
@@ -457,6 +461,7 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
val creators = repository.findRecentlyActiveCreators(limit = 10)
assertEquals(listOf(inactiveLiveCreator.nickname), creators.map { it.creatorNickname })
assertEquals(inactiveLiveCreator.id, creators.single().creatorId)
assertEquals(CreatorActivityType.LIVE, creators.single().activityType)
assertEquals(baseAt.plusMinutes(2), creators.single().activityAt)
assertEquals(null, creators.single().targetId)
@@ -470,8 +475,8 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
val adultLiveCreator = saveMember("activity-adult-live", MemberRole.CREATOR)
val adultAudioCreator = saveMember("activity-adult-audio", MemberRole.CREATOR)
val adultCommunityCreator = saveMember("activity-adult-community", MemberRole.CREATOR)
saveLiveRoom(normalLiveCreator, baseAt.plusMinutes(3), channelName = "normal-live", isAdult = false)
saveLiveRoom(adultLiveCreator, baseAt.plusMinutes(2), channelName = "adult-live", isAdult = true)
val normalLive = saveLiveRoom(normalLiveCreator, baseAt.plusMinutes(3), channelName = "normal-live", isAdult = false)
val adultLive = saveLiveRoom(adultLiveCreator, baseAt.plusMinutes(2), channelName = "adult-live", isAdult = true)
val adultAudio = saveAudioContent(adultAudioCreator, baseAt.plusMinutes(1), isActive = true, isAdult = true)
val adultCommunity = saveCommunity(adultCommunityCreator, isCommentAvailable = true, isAdult = true)
updateCreatedAt("CreatorCommunity", adultCommunity.id!!, baseAt)
@@ -490,8 +495,8 @@ class DefaultHomeRecommendationQueryRepositoryTest @Autowired constructor(
),
visibleCreators.map { it.creatorNickname }
)
assertEquals(null, visibleCreators[0].targetId)
assertEquals(null, visibleCreators[1].targetId)
assertEquals(normalLive.id, visibleCreators[0].targetId)
assertEquals(adultLive.id, visibleCreators[1].targetId)
assertEquals(adultAudio.id, visibleCreators[2].targetId)
assertEquals(adultCommunity.id, visibleCreators[3].targetId)
assertEquals(CreatorActivityType.LIVE, visibleCreators[0].activityType)

View File

@@ -1012,6 +1012,7 @@ class HomeRecommendationQueryServiceTest {
)
val activeCreators = listOf(
RecentlyActiveCreatorRecord(
creatorId = 301L,
creatorNickname = "creator",
creatorProfileImage = "profile.png",
activityType = CreatorActivityType.LIVE,