Compare commits

...

4 Commits

6 changed files with 79 additions and 5 deletions

View File

@@ -270,13 +270,15 @@ class HomeRecommendationFacade(
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage), creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage),
title = title, title = title,
coverImage = imageUrl(cloudFrontHost, coverImage), coverImage = imageUrl(cloudFrontHost, coverImage),
releaseDate = releaseDate.toUtcIso() releaseDate = releaseDate.toUtcIso(),
isPointAvailable = isPointAvailable
) )
private fun HomeAiCharacterRecommendationRecord.toItem() = HomeAiCharacterItem( private fun HomeAiCharacterRecommendationRecord.toItem() = HomeAiCharacterItem(
characterId = characterId, characterId = characterId,
name = name, name = name,
description = description, description = description,
profileImage = imageUrl(cloudFrontHost, profileImage),
totalChatCount = totalChatCount, totalChatCount = totalChatCount,
originalWorkTitle = originalWorkTitle originalWorkTitle = originalWorkTitle
) )

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.v2.api.home.dto package kr.co.vividnext.sodalive.v2.api.home.dto
import com.fasterxml.jackson.annotation.JsonProperty
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -66,13 +67,16 @@ data class HomeFirstAudioContentItem(
val creatorProfileImage: String?, val creatorProfileImage: String?,
val title: String, val title: String,
val coverImage: String?, val coverImage: String?,
val releaseDate: String val releaseDate: String,
@JsonProperty("isPointAvailable")
val isPointAvailable: Boolean
) )
data class HomeAiCharacterItem( data class HomeAiCharacterItem(
val characterId: Long, val characterId: Long,
val name: String, val name: String,
val description: String, val description: String,
val profileImage: String?,
val totalChatCount: Long, val totalChatCount: Long,
val originalWorkTitle: String? val originalWorkTitle: String?
) )

View File

@@ -395,6 +395,7 @@ class DefaultHomeRecommendationQueryRepository(
ac.cover_image as cover_image, ac.cover_image as cover_image,
ac.release_date as release_date, ac.release_date as release_date,
ac.is_active as is_active, ac.is_active as is_active,
ac.is_point_available as is_point_available,
row_number() over ( row_number() over (
partition by ac.member_id partition by ac.member_id
order by ac.created_at asc, ac.release_date asc, ac.id asc order by ac.created_at asc, ac.release_date asc, ac.id asc
@@ -421,6 +422,7 @@ class DefaultHomeRecommendationQueryRepository(
ec.title as title, ec.title as title,
ec.cover_image as cover_image, ec.cover_image as cover_image,
ec.release_date as release_date, ec.release_date as release_date,
ec.is_point_available as is_point_available,
case case
when ec.release_date >= :recency3Start then 100 when ec.release_date >= :recency3Start then 100
when ec.release_date >= :recency7Start then 80 when ec.release_date >= :recency7Start then 80
@@ -471,8 +473,9 @@ class DefaultHomeRecommendationQueryRepository(
title = row[4] as String, title = row[4] as String,
coverImage = row[5] as String?, coverImage = row[5] as String?,
releaseDate = toLocalDateTime(row[6]), releaseDate = toLocalDateTime(row[6]),
recencyScore = (row[7] as Number).toInt(), isPointAvailable = row[7] as Boolean,
randomTieBreaker = (row[8] as Number).toDouble() recencyScore = (row[8] as Number).toInt(),
randomTieBreaker = (row[9] as Number).toDouble()
) )
} }
} }
@@ -708,6 +711,7 @@ class DefaultHomeRecommendationQueryRepository(
chatCharacter.id, chatCharacter.id,
chatCharacter.name, chatCharacter.name,
chatCharacter.description, chatCharacter.description,
chatCharacter.imagePath,
chatMessage.id.count(), chatMessage.id.count(),
linkedOriginalWork.title linkedOriginalWork.title
) )
@@ -724,7 +728,13 @@ class DefaultHomeRecommendationQueryRepository(
chatMessage.isActive.isTrue chatMessage.isActive.isTrue
) )
.where(chatCharacter.isActive.isTrue, chatCharacter.id.`in`(characterIds)) .where(chatCharacter.isActive.isTrue, chatCharacter.id.`in`(characterIds))
.groupBy(chatCharacter.id, chatCharacter.name, chatCharacter.description, linkedOriginalWork.title) .groupBy(
chatCharacter.id,
chatCharacter.name,
chatCharacter.description,
chatCharacter.imagePath,
linkedOriginalWork.title
)
.fetch() .fetch()
} }

View File

@@ -126,6 +126,7 @@ data class HomeFirstAudioContentRecord(
val title: String, val title: String,
val coverImage: String?, val coverImage: String?,
val releaseDate: LocalDateTime, val releaseDate: LocalDateTime,
val isPointAvailable: Boolean,
val recencyScore: Int, val recencyScore: Int,
val randomTieBreaker: Double val randomTieBreaker: Double
) )
@@ -134,6 +135,7 @@ data class HomeAiCharacterRecommendationRecord(
val characterId: Long, val characterId: Long,
val name: String, val name: String,
val description: String, val description: String,
val profileImage: String?,
val totalChatCount: Long, val totalChatCount: Long,
val originalWorkTitle: String? val originalWorkTitle: String?
) )

View File

@@ -0,0 +1,51 @@
package kr.co.vividnext.sodalive.v2.api.home.dto
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test
class HomeRecommendationResponseTest {
private val objectMapper = jacksonObjectMapper()
@Test
fun shouldSerializeNewHomeRecommendationFields() {
val response = HomeRecommendationResponse(
lives = emptyList(),
banners = emptyList(),
recentlyActiveCreators = emptyList(),
recentDebutCreators = emptyList(),
firstAudioContents = listOf(
HomeFirstAudioContentItem(
contentId = 1L,
creatorId = 2L,
creatorNickname = "creator",
creatorProfileImage = "https://cdn.test/profile/creator.png",
title = "first audio",
coverImage = "https://cdn.test/cover/audio.png",
releaseDate = "2026-06-01T00:00:00Z",
isPointAvailable = true
)
),
aiCharacters = listOf(
HomeAiCharacterItem(
characterId = 3L,
name = "character",
description = "description",
profileImage = "https://cdn.test/profile/character.png",
totalChatCount = 4L,
originalWorkTitle = "original"
)
),
genreCreators = emptyList(),
cheerCreators = emptyList(),
popularCommunities = emptyList()
)
val json = objectMapper.readTree(objectMapper.writeValueAsString(response))
assertEquals(true, json["firstAudioContents"][0]["isPointAvailable"].asBoolean())
assertFalse(json["firstAudioContents"][0].has("pointAvailable"))
assertEquals("https://cdn.test/profile/character.png", json["aiCharacters"][0]["profileImage"].asText())
}
}

View File

@@ -154,6 +154,7 @@ class HomeRecommendationQueryServiceTest {
characterId = 1L, characterId = 1L,
name = "character-1", name = "character-1",
description = "description-1", description = "description-1",
profileImage = "profile/character-1.png",
totalChatCount = 3L, totalChatCount = 3L,
originalWorkTitle = "original-work" originalWorkTitle = "original-work"
), ),
@@ -161,6 +162,7 @@ class HomeRecommendationQueryServiceTest {
characterId = 2L, characterId = 2L,
name = "character-2", name = "character-2",
description = "description-2", description = "description-2",
profileImage = null,
totalChatCount = 0L, totalChatCount = 0L,
originalWorkTitle = null originalWorkTitle = null
) )
@@ -170,6 +172,8 @@ class HomeRecommendationQueryServiceTest {
assertEquals((1L..10L).toList(), port.aiCharacterDetailIds) assertEquals((1L..10L).toList(), port.aiCharacterDetailIds)
assertEquals(listOf(1L, 2L), characters.map { it.characterId }) assertEquals(listOf(1L, 2L), characters.map { it.characterId })
assertEquals("profile/character-1.png", characters.first().profileImage)
assertEquals(null, characters.last().profileImage)
assertEquals("original-work", characters.first().originalWorkTitle) assertEquals("original-work", characters.first().originalWorkTitle)
assertEquals(null, characters.last().originalWorkTitle) assertEquals(null, characters.last().originalWorkTitle)
} }
@@ -488,6 +492,7 @@ class HomeRecommendationQueryServiceTest {
title = "first-audio", title = "first-audio",
coverImage = "first-audio.png", coverImage = "first-audio.png",
releaseDate = LocalDateTime.of(2026, 5, 30, 10, 0), releaseDate = LocalDateTime.of(2026, 5, 30, 10, 0),
isPointAvailable = true,
recencyScore = 100, recencyScore = 100,
randomTieBreaker = 0.3 randomTieBreaker = 0.3
) )