feat(recommend): 홈 추천 응답 필드를 정리한다
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.home.application
|
||||
|
||||
import kr.co.vividnext.sodalive.event.EventItem
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.isAdultVisibleByPolicy
|
||||
@@ -14,6 +15,7 @@ import kr.co.vividnext.sodalive.v2.api.home.dto.HomePopularCommunityPostItem
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeRecommendationPageResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.HomeRecommendationResponse
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.imageUrl
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.profileImageUrl
|
||||
import kr.co.vividnext.sodalive.v2.api.home.dto.toUtcIso
|
||||
import kr.co.vividnext.sodalive.v2.recommend.application.HomeRecommendationQueryService
|
||||
import kr.co.vividnext.sodalive.v2.recommend.port.out.HomeAiCharacterRecommendationRecord
|
||||
@@ -228,30 +230,38 @@ class HomeRecommendationFacade(
|
||||
}
|
||||
|
||||
private fun HomeLiveRecommendationRecord.toItem() = HomeLiveItem(
|
||||
liveRoomId = liveRoomId,
|
||||
creatorId = creatorId,
|
||||
roomId = liveRoomId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage),
|
||||
title = title,
|
||||
coverImage = imageUrl(cloudFrontHost, coverImage),
|
||||
beginDateTime = beginDateTime.toUtcIso(),
|
||||
channelName = channelName
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage)
|
||||
)
|
||||
|
||||
private fun HomeBannerRecommendationRecord.toItem() = HomeBannerItem(
|
||||
bannerId = bannerId,
|
||||
type = type,
|
||||
thumbnailImage = imageUrl(cloudFrontHost, thumbnailImage),
|
||||
eventId = eventId,
|
||||
imageUrl = imageUrl(cloudFrontHost, thumbnailImage) ?: "",
|
||||
eventItem = eventItem(),
|
||||
creatorId = creatorId,
|
||||
seriesId = seriesId,
|
||||
link = link
|
||||
)
|
||||
|
||||
private fun HomeBannerRecommendationRecord.eventItem(): EventItem? {
|
||||
if (eventId == null || eventThumbnailImage == null) return null
|
||||
return EventItem(
|
||||
id = eventId,
|
||||
thumbnailImageUrl = eventImageUrl(eventThumbnailImage) ?: eventThumbnailImage,
|
||||
detailImageUrl = eventImageUrl(eventDetailImage),
|
||||
popupImageUrl = null,
|
||||
link = eventLink
|
||||
)
|
||||
}
|
||||
|
||||
private fun eventImageUrl(path: String?): String? {
|
||||
if (path.isNullOrBlank()) return null
|
||||
return if (path.startsWith("https://")) path else imageUrl(cloudFrontHost, path)
|
||||
}
|
||||
|
||||
private fun RecentlyActiveCreatorRecord.toItem() = HomeActiveCreatorItem(
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage),
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage),
|
||||
activityType = activityType.name,
|
||||
activityAt = activityAt.toUtcIso(),
|
||||
targetId = targetId
|
||||
@@ -260,18 +270,17 @@ class HomeRecommendationFacade(
|
||||
private fun RecentDebutCreatorRecord.toItem() = HomeCreatorItem(
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage)
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage)
|
||||
)
|
||||
|
||||
private fun HomeFirstAudioContentRecord.toItem() = HomeFirstAudioContentItem(
|
||||
contentId = contentId,
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage),
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage),
|
||||
title = title,
|
||||
price = price,
|
||||
coverImage = imageUrl(cloudFrontHost, coverImage),
|
||||
releaseDate = releaseDate.toUtcIso(),
|
||||
isPointAvailable = isPointAvailable
|
||||
)
|
||||
|
||||
@@ -285,13 +294,12 @@ class HomeRecommendationFacade(
|
||||
)
|
||||
|
||||
private fun HomeGenreCreatorRecommendationGroup.toItem() = HomeGenreCreatorGroupItem(
|
||||
genreId = genreId,
|
||||
genreName = genreName,
|
||||
creators = creators.map {
|
||||
HomeCreatorItem(
|
||||
creatorId = it.creatorId,
|
||||
creatorNickname = it.creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, it.creatorProfileImage)
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, it.creatorProfileImage)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -299,7 +307,7 @@ class HomeRecommendationFacade(
|
||||
private fun HomeCheerCreatorRecommendationRecord.toCreatorItem() = HomeCreatorItem(
|
||||
creatorId = creatorId,
|
||||
creatorNickname = creatorNickname,
|
||||
creatorProfileImage = imageUrl(cloudFrontHost, creatorProfileImage)
|
||||
creatorProfileImage = profileImageUrl(cloudFrontHost, creatorProfileImage)
|
||||
)
|
||||
|
||||
private fun HomePopularCommunityRecommendationRecord.toItem() = HomePopularCommunityPostItem(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.home.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import kr.co.vividnext.sodalive.event.EventItem
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@@ -12,6 +13,10 @@ internal fun imageUrl(cloudFrontHost: String, path: String?): String? {
|
||||
return if (path.isNullOrBlank()) null else "$cloudFrontHost/$path"
|
||||
}
|
||||
|
||||
internal fun profileImageUrl(cloudFrontHost: String, path: String?): String {
|
||||
return imageUrl(cloudFrontHost, path) ?: "$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
|
||||
data class HomeRecommendationResponse(
|
||||
val lives: List<HomeLiveItem>,
|
||||
val banners: List<HomeBannerItem>,
|
||||
@@ -25,30 +30,22 @@ data class HomeRecommendationResponse(
|
||||
)
|
||||
|
||||
data class HomeLiveItem(
|
||||
val liveRoomId: Long,
|
||||
val creatorId: Long,
|
||||
val roomId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val title: String,
|
||||
val coverImage: String?,
|
||||
val beginDateTime: String,
|
||||
val channelName: String
|
||||
val creatorProfileImage: String
|
||||
)
|
||||
|
||||
data class HomeBannerItem(
|
||||
val bannerId: Long,
|
||||
val type: String,
|
||||
val thumbnailImage: String?,
|
||||
val eventId: Long?,
|
||||
val imageUrl: String,
|
||||
val eventItem: EventItem?,
|
||||
val creatorId: Long?,
|
||||
val seriesId: Long?,
|
||||
val link: String?
|
||||
)
|
||||
|
||||
data class HomeActiveCreatorItem(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val creatorProfileImage: String,
|
||||
val activityType: String,
|
||||
val activityAt: String,
|
||||
val targetId: Long?
|
||||
@@ -57,18 +54,17 @@ data class HomeActiveCreatorItem(
|
||||
data class HomeCreatorItem(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?
|
||||
val creatorProfileImage: String
|
||||
)
|
||||
|
||||
data class HomeFirstAudioContentItem(
|
||||
val contentId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val creatorProfileImage: String,
|
||||
val title: String,
|
||||
val price: Int,
|
||||
val coverImage: String?,
|
||||
val releaseDate: String,
|
||||
@JsonProperty("isPointAvailable")
|
||||
val isPointAvailable: Boolean
|
||||
)
|
||||
@@ -83,7 +79,6 @@ data class HomeAiCharacterItem(
|
||||
)
|
||||
|
||||
data class HomeGenreCreatorGroupItem(
|
||||
val genreId: Long,
|
||||
val genreName: String,
|
||||
val creators: List<HomeCreatorItem>
|
||||
)
|
||||
|
||||
@@ -59,13 +59,8 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
Projections.constructor(
|
||||
HomeLiveRecommendationRecord::class.java,
|
||||
liveRoom.id,
|
||||
member.id,
|
||||
member.nickname,
|
||||
member.profileImage,
|
||||
liveRoom.title,
|
||||
liveRoom.coverImage,
|
||||
liveRoom.beginDateTime,
|
||||
liveRoom.channelName
|
||||
member.profileImage
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
@@ -96,15 +91,14 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
.select(
|
||||
Projections.constructor(
|
||||
HomeBannerRecommendationRecord::class.java,
|
||||
audioContentBanner.id,
|
||||
audioContentBanner.type.stringValue(),
|
||||
audioContentBanner.thumbnailImage,
|
||||
event.id,
|
||||
event.thumbnailImage,
|
||||
event.detailImage,
|
||||
event.link,
|
||||
bannerCreator.id,
|
||||
series.id,
|
||||
audioContentBanner.link,
|
||||
audioContentBanner.orders,
|
||||
randomTieBreaker
|
||||
audioContentBanner.link
|
||||
)
|
||||
)
|
||||
.from(audioContentBanner)
|
||||
@@ -128,8 +122,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
includeAdultActivities: Boolean
|
||||
): List<RecentlyActiveCreatorRecord> {
|
||||
val sql = """
|
||||
select ranked.creator_id,
|
||||
ranked.creator_nickname,
|
||||
select ranked.creator_nickname,
|
||||
ranked.creator_profile_image,
|
||||
ranked.activity_type,
|
||||
ranked.activity_at,
|
||||
@@ -202,12 +195,11 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
|
||||
return rows.map { row ->
|
||||
RecentlyActiveCreatorRecord(
|
||||
creatorId = (row[0] as Number).toLong(),
|
||||
creatorNickname = row[1] as String,
|
||||
creatorProfileImage = row[2] as String?,
|
||||
activityType = RecommendedActivityType.valueOf(row[3] as String),
|
||||
activityAt = toLocalDateTime(row[4]),
|
||||
targetId = (row[5] as Number?)?.toLong()
|
||||
creatorNickname = row[0] as String,
|
||||
creatorProfileImage = row[1] as String?,
|
||||
activityType = RecommendedActivityType.valueOf(row[2] as String),
|
||||
activityAt = toLocalDateTime(row[3]),
|
||||
targetId = (row[4] as Number?)?.toLong()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -315,18 +307,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
)
|
||||
select m.id as creator_id,
|
||||
m.nickname as creator_nickname,
|
||||
m.profile_image as creator_profile_image,
|
||||
cd.debut_at as debut_at,
|
||||
((coalesce(fs.follow_increase, 0) * ${RecommendationScoreSpec.DEBUT_FOLLOW_INCREASE_WEIGHT} +
|
||||
coalesce(cs.content_activity_score, 0) * ${RecommendationScoreSpec.DEBUT_CONTENT_ACTIVITY_WEIGHT} +
|
||||
coalesce(cms.communication_score, 0) * ${RecommendationScoreSpec.DEBUT_COMMUNICATION_WEIGHT}) *
|
||||
case
|
||||
when cd.debut_at >= :boost10Start then ${RecommendationScoreSpec.NEW_BOOST_10_DAYS}
|
||||
when cd.debut_at >= :boost20Start then ${RecommendationScoreSpec.NEW_BOOST_20_DAYS}
|
||||
when cd.debut_at >= :boost30Start then ${RecommendationScoreSpec.NEW_BOOST_30_DAYS}
|
||||
else ${RecommendationScoreSpec.DEFAULT_NEW_BOOST}
|
||||
end) as score,
|
||||
m.id as random_tie_breaker
|
||||
m.profile_image as creator_profile_image
|
||||
from member m
|
||||
join creator_debut cd on cd.creator_id = m.id
|
||||
left join follow_stats fs on fs.creator_id = m.id
|
||||
@@ -336,7 +317,15 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and cd.debut_at >= :boost30Start
|
||||
and cd.debut_at <= :now
|
||||
and ${notBlockedCreatorSql("m.id")}
|
||||
order by score desc, random_tie_breaker asc
|
||||
order by ((coalesce(fs.follow_increase, 0) * ${RecommendationScoreSpec.DEBUT_FOLLOW_INCREASE_WEIGHT} +
|
||||
coalesce(cs.content_activity_score, 0) * ${RecommendationScoreSpec.DEBUT_CONTENT_ACTIVITY_WEIGHT} +
|
||||
coalesce(cms.communication_score, 0) * ${RecommendationScoreSpec.DEBUT_COMMUNICATION_WEIGHT}) *
|
||||
case
|
||||
when cd.debut_at >= :boost10Start then ${RecommendationScoreSpec.NEW_BOOST_10_DAYS}
|
||||
when cd.debut_at >= :boost20Start then ${RecommendationScoreSpec.NEW_BOOST_20_DAYS}
|
||||
when cd.debut_at >= :boost30Start then ${RecommendationScoreSpec.NEW_BOOST_30_DAYS}
|
||||
else ${RecommendationScoreSpec.DEFAULT_NEW_BOOST}
|
||||
end) desc, rand(m.id) asc
|
||||
limit :limit
|
||||
offset :offset
|
||||
""".trimIndent()
|
||||
@@ -354,10 +343,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
RecentDebutCreatorRecord(
|
||||
creatorId = (row[0] as Number).toLong(),
|
||||
creatorNickname = row[1] as String,
|
||||
creatorProfileImage = row[2] as String?,
|
||||
debutAt = toLocalDateTime(row[3]),
|
||||
score = (row[4] as Number).toDouble(),
|
||||
randomTieBreaker = (row[5] as Number).toDouble()
|
||||
creatorProfileImage = row[2] as String?
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -425,17 +411,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
ec.title as title,
|
||||
ec.price as price,
|
||||
ec.cover_image as cover_image,
|
||||
ec.release_date as release_date,
|
||||
ec.is_point_available as is_point_available,
|
||||
case
|
||||
when ec.release_date >= :recency3Start then 100
|
||||
when ec.release_date >= :recency7Start then 80
|
||||
when ec.release_date >= :recency14Start then 60
|
||||
when ec.release_date >= :recency21Start then 40
|
||||
when ec.release_date >= :boost30Start then 20
|
||||
else 0
|
||||
end as recency_score,
|
||||
ec.content_id as random_tie_breaker
|
||||
ec.is_point_available as is_point_available
|
||||
from eligible_contents ec
|
||||
join member m on m.id = ec.creator_id
|
||||
join creator_debut cd on cd.creator_id = ec.creator_id
|
||||
@@ -445,7 +421,14 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
and cd.debut_at <= :now
|
||||
and ec.release_date >= :boost30Start
|
||||
and ${notBlockedCreatorSql("m.id")}
|
||||
order by recency_score desc, random_tie_breaker asc
|
||||
order by case
|
||||
when ec.release_date >= :recency3Start then 100
|
||||
when ec.release_date >= :recency7Start then 80
|
||||
when ec.release_date >= :recency14Start then 60
|
||||
when ec.release_date >= :recency21Start then 40
|
||||
when ec.release_date >= :boost30Start then 20
|
||||
else 0
|
||||
end desc, rand(ec.content_id) asc
|
||||
limit :limit
|
||||
offset :offset
|
||||
""".trimIndent()
|
||||
@@ -477,10 +460,7 @@ class DefaultHomeRecommendationQueryRepository(
|
||||
title = row[4] as String,
|
||||
price = (row[5] as Number).toInt(),
|
||||
coverImage = row[6] as String?,
|
||||
releaseDate = toLocalDateTime(row[7]),
|
||||
isPointAvailable = row[8] as Boolean,
|
||||
recencyScore = (row[9] as Number).toInt(),
|
||||
randomTieBreaker = (row[10] as Number).toDouble()
|
||||
isPointAvailable = row[7] as Boolean
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,29 +79,22 @@ interface HomeRecommendationQueryPort {
|
||||
|
||||
data class HomeLiveRecommendationRecord(
|
||||
val liveRoomId: Long,
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val title: String,
|
||||
val coverImage: String?,
|
||||
val beginDateTime: LocalDateTime,
|
||||
val channelName: String
|
||||
val creatorProfileImage: String?
|
||||
)
|
||||
|
||||
data class HomeBannerRecommendationRecord(
|
||||
val bannerId: Long,
|
||||
val type: String,
|
||||
val thumbnailImage: String,
|
||||
val eventId: Long?,
|
||||
val eventThumbnailImage: String?,
|
||||
val eventDetailImage: String?,
|
||||
val eventLink: String?,
|
||||
val creatorId: Long?,
|
||||
val seriesId: Long?,
|
||||
val link: String?,
|
||||
val orders: Int,
|
||||
val randomTieBreaker: Double
|
||||
val link: String?
|
||||
)
|
||||
|
||||
data class RecentlyActiveCreatorRecord(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val activityType: RecommendedActivityType,
|
||||
@@ -112,10 +105,7 @@ data class RecentlyActiveCreatorRecord(
|
||||
data class RecentDebutCreatorRecord(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImage: String?,
|
||||
val debutAt: LocalDateTime,
|
||||
val score: Double,
|
||||
val randomTieBreaker: Double
|
||||
val creatorProfileImage: String?
|
||||
)
|
||||
|
||||
data class HomeFirstAudioContentRecord(
|
||||
@@ -126,10 +116,7 @@ data class HomeFirstAudioContentRecord(
|
||||
val title: String,
|
||||
val price: Int,
|
||||
val coverImage: String?,
|
||||
val releaseDate: LocalDateTime,
|
||||
val isPointAvailable: Boolean,
|
||||
val recencyScore: Int,
|
||||
val randomTieBreaker: Double
|
||||
val isPointAvailable: Boolean
|
||||
)
|
||||
|
||||
data class HomeAiCharacterRecommendationRecord(
|
||||
|
||||
Reference in New Issue
Block a user