feat(home): 팔로잉 최근 소식 응답을 중첩 구조로 바꾼다
This commit is contained in:
@@ -5,7 +5,11 @@ import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowing
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCommunityPostNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingContentNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingContentRankingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreator
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreatorRankingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingLive
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingSchedule
|
||||
@@ -112,31 +116,91 @@ data class FollowingScheduleResponse(
|
||||
data class FollowingNewsResponse(
|
||||
val newsId: String,
|
||||
val type: FollowingNewsType,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val body: String,
|
||||
val thumbnailImageUrl: String?,
|
||||
val targetId: Long,
|
||||
val occurredAtUtc: String,
|
||||
val visibleFromAtUtc: String,
|
||||
val rank: Int?
|
||||
val creatorRanking: FollowingCreatorRankingNewsResponse?,
|
||||
val audioContent: FollowingContentNewsResponse?,
|
||||
val photoContent: FollowingContentNewsResponse?,
|
||||
val contentRanking: FollowingContentRankingNewsResponse?,
|
||||
val communityPost: FollowingCommunityPostNewsResponse?
|
||||
) {
|
||||
companion object {
|
||||
fun from(news: HomeFollowingNews): FollowingNewsResponse {
|
||||
return FollowingNewsResponse(
|
||||
newsId = news.newsId,
|
||||
type = news.type,
|
||||
creatorProfileImageUrl = news.creatorProfileImageUrl,
|
||||
creatorNickname = news.creatorNickname,
|
||||
title = news.title,
|
||||
body = news.body,
|
||||
thumbnailImageUrl = news.thumbnailImageUrl,
|
||||
targetId = news.targetId,
|
||||
occurredAtUtc = news.occurredAtUtc,
|
||||
visibleFromAtUtc = news.visibleFromAtUtc,
|
||||
rank = news.rank
|
||||
creatorRanking = news.creatorRanking?.toResponse(),
|
||||
audioContent = news.audioContent?.toContentResponse(),
|
||||
photoContent = news.photoContent?.toContentResponse(),
|
||||
contentRanking = news.contentRanking?.toResponse(),
|
||||
communityPost = news.communityPost?.toResponse()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FollowingCreatorRankingNewsResponse(
|
||||
val rank: Int,
|
||||
val creatorId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String
|
||||
)
|
||||
|
||||
data class FollowingContentNewsResponse(
|
||||
val contentId: Long,
|
||||
val contentImageUrl: String?,
|
||||
val title: String,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String
|
||||
)
|
||||
|
||||
data class FollowingContentRankingNewsResponse(
|
||||
val rank: Int,
|
||||
val contentId: Long,
|
||||
val contentImageUrl: String?,
|
||||
val title: String
|
||||
)
|
||||
|
||||
data class FollowingCommunityPostNewsResponse(
|
||||
val postId: Long,
|
||||
val creatorProfileImage: String,
|
||||
val creatorNickname: String,
|
||||
val imageUrl: String?,
|
||||
val content: String,
|
||||
val createdAt: String,
|
||||
val likeCount: Int,
|
||||
val commentCount: Int
|
||||
)
|
||||
|
||||
private fun HomeFollowingCreatorRankingNews.toResponse() = FollowingCreatorRankingNewsResponse(
|
||||
rank = rank,
|
||||
creatorId = creatorId,
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImageUrl
|
||||
)
|
||||
|
||||
private fun HomeFollowingContentNews.toContentResponse() = FollowingContentNewsResponse(
|
||||
contentId = contentId,
|
||||
contentImageUrl = contentImageUrl,
|
||||
title = title,
|
||||
creatorProfileImageUrl = creatorProfileImageUrl,
|
||||
creatorNickname = creatorNickname
|
||||
)
|
||||
|
||||
private fun HomeFollowingContentRankingNews.toResponse() = FollowingContentRankingNewsResponse(
|
||||
rank = rank,
|
||||
contentId = contentId,
|
||||
contentImageUrl = contentImageUrl,
|
||||
title = title
|
||||
)
|
||||
|
||||
private fun HomeFollowingCommunityPostNews.toResponse() = FollowingCommunityPostNewsResponse(
|
||||
postId = postId,
|
||||
creatorProfileImage = creatorProfileImage,
|
||||
creatorNickname = creatorNickname,
|
||||
imageUrl = imageUrl,
|
||||
content = content,
|
||||
createdAt = createdAt,
|
||||
likeCount = likeCount,
|
||||
commentCount = commentCount
|
||||
)
|
||||
|
||||
@@ -40,13 +40,43 @@ data class HomeFollowingSchedule(
|
||||
data class HomeFollowingNews(
|
||||
val newsId: String,
|
||||
val type: FollowingNewsType,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val body: String,
|
||||
val thumbnailImageUrl: String?,
|
||||
val targetId: Long,
|
||||
val occurredAtUtc: String,
|
||||
val visibleFromAtUtc: String,
|
||||
val rank: Int?
|
||||
val creatorRanking: HomeFollowingCreatorRankingNews? = null,
|
||||
val audioContent: HomeFollowingContentNews? = null,
|
||||
val photoContent: HomeFollowingContentNews? = null,
|
||||
val contentRanking: HomeFollowingContentRankingNews? = null,
|
||||
val communityPost: HomeFollowingCommunityPostNews? = null
|
||||
)
|
||||
|
||||
data class HomeFollowingCreatorRankingNews(
|
||||
val rank: Int,
|
||||
val creatorId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String
|
||||
)
|
||||
|
||||
data class HomeFollowingContentNews(
|
||||
val contentId: Long,
|
||||
val contentImageUrl: String?,
|
||||
val title: String,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String
|
||||
)
|
||||
|
||||
data class HomeFollowingContentRankingNews(
|
||||
val rank: Int,
|
||||
val contentId: Long,
|
||||
val contentImageUrl: String?,
|
||||
val title: String
|
||||
)
|
||||
|
||||
data class HomeFollowingCommunityPostNews(
|
||||
val postId: Long,
|
||||
val creatorProfileImage: String,
|
||||
val creatorNickname: String,
|
||||
val imageUrl: String?,
|
||||
val content: String,
|
||||
val createdAt: String,
|
||||
val likeCount: Int,
|
||||
val commentCount: Int
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.v2.home.following.application.HomeFollowingQuery
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowing
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreator
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreatorRankingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingLive
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingSchedule
|
||||
@@ -109,15 +110,13 @@ class HomeFollowingFacadeTest {
|
||||
HomeFollowingNews(
|
||||
newsId = "news-5",
|
||||
type = FollowingNewsType.CREATOR_RANKING,
|
||||
creatorProfileImageUrl = "https://cdn.test/news.png",
|
||||
creatorNickname = "creator",
|
||||
title = "news",
|
||||
body = "body",
|
||||
thumbnailImageUrl = null,
|
||||
targetId = 1L,
|
||||
occurredAtUtc = "2026-06-25T03:00:00Z",
|
||||
visibleFromAtUtc = "2026-06-25T04:00:00Z",
|
||||
rank = 7
|
||||
creatorRanking = HomeFollowingCreatorRankingNews(
|
||||
rank = 7,
|
||||
creatorId = 1L,
|
||||
nickname = "creator",
|
||||
profileImageUrl = "https://cdn.test/news.png"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,7 +5,11 @@ import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowing
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCommunityPostNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingContentNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingContentRankingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreator
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingCreatorRankingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingLive
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingNews
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.HomeFollowingSchedule
|
||||
@@ -39,24 +43,71 @@ class HomeFollowingTabResponseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팔로잉 탭 도메인은 creatorId 없는 최근 소식과 nullable rank 응답으로 변환한다")
|
||||
fun shouldMapDomainToResponseWithoutCreatorIdInRecentNews() {
|
||||
@DisplayName("팔로잉 탭 도메인은 타입별 nested 최근 소식 응답으로 변환한다")
|
||||
fun shouldMapDomainToNestedRecentNewsResponseByType() {
|
||||
val response = HomeFollowingTabResponse.from(createHomeFollowing())
|
||||
val json = objectMapper.readTree(objectMapper.writeValueAsString(response))
|
||||
val recentNews = json["recentNews"]
|
||||
|
||||
assertFalse(response.isLoginRequired)
|
||||
assertEquals(1L, response.followingCreators.first().creatorId)
|
||||
assertEquals(10L, response.onAirLives.first().liveId)
|
||||
assertEquals(100L, response.recentChats.first().roomId)
|
||||
assertEquals("LIVE:20", response.monthlySchedules.first().scheduleId)
|
||||
assertEquals(3, response.recentNews.first().rank)
|
||||
assertEquals(false, json["isLoginRequired"].asBoolean())
|
||||
assertFalse(json["recentNews"][0].has("creatorId"))
|
||||
assertFalse(json["recentNews"][0].has("ranking"))
|
||||
assertFalse(json["recentNews"][0].has("rankChange"))
|
||||
assertFalse(json["recentNews"][0].has("isNew"))
|
||||
assertEquals(3, json["recentNews"][0]["rank"].asInt())
|
||||
assertEquals(true, json["monthlySchedules"][0]["isOnAir"].asBoolean())
|
||||
|
||||
val removedTopLevelFields = listOf(
|
||||
"creatorProfileImageUrl",
|
||||
"creatorNickname",
|
||||
"title",
|
||||
"body",
|
||||
"thumbnailImageUrl",
|
||||
"targetId",
|
||||
"occurredAtUtc",
|
||||
"rank"
|
||||
)
|
||||
removedTopLevelFields.forEach { field ->
|
||||
assertFalse(recentNews[0].has(field), "recentNews top-level must not expose $field")
|
||||
}
|
||||
|
||||
assertEquals("30", recentNews[0]["newsId"].asText())
|
||||
assertEquals("CREATOR_RANKING", recentNews[0]["type"].asText())
|
||||
assertEquals("2026-06-25T09:00:00Z", recentNews[0]["visibleFromAtUtc"].asText())
|
||||
assertEquals(3, recentNews[0]["creatorRanking"]["rank"].asInt())
|
||||
assertEquals(1L, recentNews[0]["creatorRanking"]["creatorId"].asLong())
|
||||
assertEquals("news-creator", recentNews[0]["creatorRanking"]["nickname"].asText())
|
||||
assertTrue(recentNews[0]["audioContent"].isNull)
|
||||
assertTrue(recentNews[0]["photoContent"].isNull)
|
||||
assertTrue(recentNews[0]["contentRanking"].isNull)
|
||||
assertTrue(recentNews[0]["communityPost"].isNull)
|
||||
|
||||
assertEquals("AUDIO_CONTENT", recentNews[1]["type"].asText())
|
||||
assertEquals(200L, recentNews[1]["audioContent"]["contentId"].asLong())
|
||||
assertEquals("audio title", recentNews[1]["audioContent"]["title"].asText())
|
||||
assertFalse(recentNews[1]["audioContent"].has("releaseDate"))
|
||||
assertTrue(recentNews[1]["creatorRanking"].isNull)
|
||||
assertTrue(recentNews[1]["communityPost"].isNull)
|
||||
|
||||
assertEquals("PHOTO_CONTENT", recentNews[2]["type"].asText())
|
||||
assertEquals(300L, recentNews[2]["photoContent"]["contentId"].asLong())
|
||||
assertEquals("photo title", recentNews[2]["photoContent"]["title"].asText())
|
||||
assertTrue(recentNews[2]["audioContent"].isNull)
|
||||
|
||||
assertEquals("CONTENT_RANKING", recentNews[3]["type"].asText())
|
||||
assertEquals(5, recentNews[3]["contentRanking"]["rank"].asInt())
|
||||
assertEquals(400L, recentNews[3]["contentRanking"]["contentId"].asLong())
|
||||
assertTrue(recentNews[3]["creatorRanking"].isNull)
|
||||
|
||||
assertEquals("COMMUNITY_POST", recentNews[4]["type"].asText())
|
||||
assertEquals(500L, recentNews[4]["communityPost"]["postId"].asLong())
|
||||
assertEquals("https://cdn/community-profile.jpg", recentNews[4]["communityPost"]["creatorProfileImage"].asText())
|
||||
assertEquals("community creator", recentNews[4]["communityPost"]["creatorNickname"].asText())
|
||||
assertTrue(recentNews[4]["communityPost"]["imageUrl"].isNull)
|
||||
assertEquals("community body", recentNews[4]["communityPost"]["content"].asText())
|
||||
assertEquals("2026-06-25T03:00:00Z", recentNews[4]["communityPost"]["createdAt"].asText())
|
||||
assertEquals(11, recentNews[4]["communityPost"]["likeCount"].asInt())
|
||||
assertEquals(2, recentNews[4]["communityPost"]["commentCount"].asInt())
|
||||
}
|
||||
|
||||
private fun createHomeFollowing(): HomeFollowing {
|
||||
@@ -98,15 +149,63 @@ class HomeFollowingTabResponseTest {
|
||||
HomeFollowingNews(
|
||||
newsId = "30",
|
||||
type = FollowingNewsType.CREATOR_RANKING,
|
||||
creatorProfileImageUrl = "https://cdn/news-profile.jpg",
|
||||
creatorNickname = "news-creator",
|
||||
title = "ranking",
|
||||
body = "ranked",
|
||||
thumbnailImageUrl = null,
|
||||
targetId = 1L,
|
||||
occurredAtUtc = "2026-06-25T00:00:00Z",
|
||||
visibleFromAtUtc = "2026-06-25T09:00:00Z",
|
||||
rank = 3
|
||||
creatorRanking = HomeFollowingCreatorRankingNews(
|
||||
rank = 3,
|
||||
creatorId = 1L,
|
||||
nickname = "news-creator",
|
||||
profileImageUrl = "https://cdn/news-profile.jpg"
|
||||
)
|
||||
),
|
||||
HomeFollowingNews(
|
||||
newsId = "31",
|
||||
type = FollowingNewsType.AUDIO_CONTENT,
|
||||
visibleFromAtUtc = "2026-06-26T00:00:00Z",
|
||||
audioContent = HomeFollowingContentNews(
|
||||
contentId = 200L,
|
||||
contentImageUrl = "https://cdn/audio.jpg",
|
||||
title = "audio title",
|
||||
creatorProfileImageUrl = "https://cdn/audio-profile.jpg",
|
||||
creatorNickname = "audio creator"
|
||||
)
|
||||
),
|
||||
HomeFollowingNews(
|
||||
newsId = "32",
|
||||
type = FollowingNewsType.PHOTO_CONTENT,
|
||||
visibleFromAtUtc = "2026-06-27T00:00:00Z",
|
||||
photoContent = HomeFollowingContentNews(
|
||||
contentId = 300L,
|
||||
contentImageUrl = "https://cdn/photo.jpg",
|
||||
title = "photo title",
|
||||
creatorProfileImageUrl = "https://cdn/photo-profile.jpg",
|
||||
creatorNickname = "photo creator"
|
||||
)
|
||||
),
|
||||
HomeFollowingNews(
|
||||
newsId = "33",
|
||||
type = FollowingNewsType.CONTENT_RANKING,
|
||||
visibleFromAtUtc = "2026-06-28T00:00:00Z",
|
||||
contentRanking = HomeFollowingContentRankingNews(
|
||||
rank = 5,
|
||||
contentId = 400L,
|
||||
contentImageUrl = "https://cdn/ranking.jpg",
|
||||
title = "content ranking"
|
||||
)
|
||||
),
|
||||
HomeFollowingNews(
|
||||
newsId = "34",
|
||||
type = FollowingNewsType.COMMUNITY_POST,
|
||||
visibleFromAtUtc = "2026-06-25T03:00:00Z",
|
||||
communityPost = HomeFollowingCommunityPostNews(
|
||||
postId = 500L,
|
||||
creatorProfileImage = "https://cdn/community-profile.jpg",
|
||||
creatorNickname = "community creator",
|
||||
imageUrl = null,
|
||||
content = "community body",
|
||||
createdAt = "2026-06-25T03:00:00Z",
|
||||
likeCount = 11,
|
||||
commentCount = 2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user