feat(home-following): 팔로잉 탭 응답 모델을 추가한다
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.home.following.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.HomeFollowingCreator
|
||||
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
|
||||
|
||||
data class HomeFollowingTabResponse(
|
||||
@JsonProperty("isLoginRequired")
|
||||
val isLoginRequired: Boolean,
|
||||
val followingCreators: List<FollowingCreatorResponse>,
|
||||
val onAirLives: List<FollowingLiveResponse>,
|
||||
val recentChats: List<ChatRoomListItemResponse>,
|
||||
val monthlySchedules: List<FollowingScheduleResponse>,
|
||||
val recentNews: List<FollowingNewsResponse>
|
||||
) {
|
||||
companion object {
|
||||
fun loginRequired(): HomeFollowingTabResponse {
|
||||
return HomeFollowingTabResponse(
|
||||
isLoginRequired = true,
|
||||
followingCreators = emptyList(),
|
||||
onAirLives = emptyList(),
|
||||
recentChats = emptyList(),
|
||||
monthlySchedules = emptyList(),
|
||||
recentNews = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun from(home: HomeFollowing): HomeFollowingTabResponse {
|
||||
return HomeFollowingTabResponse(
|
||||
isLoginRequired = false,
|
||||
followingCreators = home.followingCreators.map(FollowingCreatorResponse::from),
|
||||
onAirLives = home.onAirLives.map(FollowingLiveResponse::from),
|
||||
recentChats = home.recentChats,
|
||||
monthlySchedules = home.monthlySchedules.map(FollowingScheduleResponse::from),
|
||||
recentNews = home.recentNews.map(FollowingNewsResponse::from)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FollowingCreatorResponse(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImageUrl: String
|
||||
) {
|
||||
companion object {
|
||||
fun from(creator: HomeFollowingCreator): FollowingCreatorResponse {
|
||||
return FollowingCreatorResponse(
|
||||
creatorId = creator.creatorId,
|
||||
creatorNickname = creator.creatorNickname,
|
||||
creatorProfileImageUrl = creator.creatorProfileImageUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FollowingLiveResponse(
|
||||
val liveId: Long,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val startedAtUtc: String
|
||||
) {
|
||||
companion object {
|
||||
fun from(live: HomeFollowingLive): FollowingLiveResponse {
|
||||
return FollowingLiveResponse(
|
||||
liveId = live.liveId,
|
||||
creatorProfileImageUrl = live.creatorProfileImageUrl,
|
||||
creatorNickname = live.creatorNickname,
|
||||
title = live.title,
|
||||
startedAtUtc = live.startedAtUtc
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FollowingScheduleResponse(
|
||||
val scheduleId: String,
|
||||
val creatorId: Long,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val type: CreatorActivityType,
|
||||
val targetId: Long,
|
||||
val scheduledAtUtc: String,
|
||||
@JsonProperty("isOnAir")
|
||||
val isOnAir: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun from(schedule: HomeFollowingSchedule): FollowingScheduleResponse {
|
||||
return FollowingScheduleResponse(
|
||||
scheduleId = schedule.scheduleId,
|
||||
creatorId = schedule.creatorId,
|
||||
creatorProfileImageUrl = schedule.creatorProfileImageUrl,
|
||||
creatorNickname = schedule.creatorNickname,
|
||||
title = schedule.title,
|
||||
type = schedule.type,
|
||||
targetId = schedule.targetId,
|
||||
scheduledAtUtc = schedule.scheduledAtUtc,
|
||||
isOnAir = schedule.isOnAir
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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?
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package kr.co.vividnext.sodalive.v2.home.following.domain
|
||||
|
||||
enum class FollowingNewsType {
|
||||
CREATOR_RANKING,
|
||||
CONTENT_RANKING,
|
||||
COMMUNITY_POST,
|
||||
AUDIO_CONTENT,
|
||||
PHOTO_CONTENT
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package kr.co.vividnext.sodalive.v2.home.following.domain
|
||||
|
||||
import kr.co.vividnext.sodalive.v2.chat.dto.ChatRoomListItemResponse
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
|
||||
data class HomeFollowing(
|
||||
val followingCreators: List<HomeFollowingCreator>,
|
||||
val onAirLives: List<HomeFollowingLive>,
|
||||
val recentChats: List<ChatRoomListItemResponse>,
|
||||
val monthlySchedules: List<HomeFollowingSchedule>,
|
||||
val recentNews: List<HomeFollowingNews>
|
||||
)
|
||||
|
||||
data class HomeFollowingCreator(
|
||||
val creatorId: Long,
|
||||
val creatorNickname: String,
|
||||
val creatorProfileImageUrl: String
|
||||
)
|
||||
|
||||
data class HomeFollowingLive(
|
||||
val liveId: Long,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val startedAtUtc: String
|
||||
)
|
||||
|
||||
data class HomeFollowingSchedule(
|
||||
val scheduleId: String,
|
||||
val creatorId: Long,
|
||||
val creatorProfileImageUrl: String,
|
||||
val creatorNickname: String,
|
||||
val title: String,
|
||||
val type: CreatorActivityType,
|
||||
val targetId: Long,
|
||||
val scheduledAtUtc: String,
|
||||
val isOnAir: Boolean
|
||||
)
|
||||
|
||||
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?
|
||||
)
|
||||
@@ -0,0 +1,114 @@
|
||||
package kr.co.vividnext.sodalive.v2.api.home.following.dto
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
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.HomeFollowingCreator
|
||||
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
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class HomeFollowingTabResponseTest {
|
||||
private val objectMapper = jacksonObjectMapper()
|
||||
|
||||
@Test
|
||||
@DisplayName("비로그인 응답은 로그인이 필요하며 모든 섹션을 빈 배열로 반환한다")
|
||||
fun shouldReturnLoginRequiredResponseWithEmptySections() {
|
||||
val response = HomeFollowingTabResponse.loginRequired()
|
||||
val json = objectMapper.readTree(objectMapper.writeValueAsString(response))
|
||||
|
||||
assertTrue(response.isLoginRequired)
|
||||
assertTrue(response.followingCreators.isEmpty())
|
||||
assertTrue(response.onAirLives.isEmpty())
|
||||
assertTrue(response.recentChats.isEmpty())
|
||||
assertTrue(response.monthlySchedules.isEmpty())
|
||||
assertTrue(response.recentNews.isEmpty())
|
||||
assertEquals(true, json["isLoginRequired"].asBoolean())
|
||||
assertTrue(json["followingCreators"].isArray)
|
||||
assertTrue(json["onAirLives"].isArray)
|
||||
assertTrue(json["recentChats"].isArray)
|
||||
assertTrue(json["monthlySchedules"].isArray)
|
||||
assertTrue(json["recentNews"].isArray)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팔로잉 탭 도메인은 creatorId 없는 최근 소식과 nullable rank 응답으로 변환한다")
|
||||
fun shouldMapDomainToResponseWithoutCreatorIdInRecentNews() {
|
||||
val response = HomeFollowingTabResponse.from(createHomeFollowing())
|
||||
val json = objectMapper.readTree(objectMapper.writeValueAsString(response))
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
private fun createHomeFollowing(): HomeFollowing {
|
||||
return HomeFollowing(
|
||||
followingCreators = listOf(HomeFollowingCreator(1L, "creator", "https://cdn/profile.jpg")),
|
||||
onAirLives = listOf(
|
||||
HomeFollowingLive(
|
||||
liveId = 10L,
|
||||
creatorProfileImageUrl = "https://cdn/live-profile.jpg",
|
||||
creatorNickname = "live-creator",
|
||||
title = "live title",
|
||||
startedAtUtc = "2026-06-25T00:00:00Z"
|
||||
)
|
||||
),
|
||||
recentChats = listOf(
|
||||
ChatRoomListItemResponse(
|
||||
roomId = 100L,
|
||||
chatType = "DM",
|
||||
targetName = "creator",
|
||||
targetImageUrl = "https://cdn/chat.jpg",
|
||||
lastMessage = "hello",
|
||||
lastMessageAt = "2026-06-25T00:01:00Z"
|
||||
)
|
||||
),
|
||||
monthlySchedules = listOf(
|
||||
HomeFollowingSchedule(
|
||||
scheduleId = "LIVE:20",
|
||||
creatorId = 1L,
|
||||
creatorProfileImageUrl = "https://cdn/schedule.jpg",
|
||||
creatorNickname = "schedule-creator",
|
||||
title = "schedule title",
|
||||
type = CreatorActivityType.LIVE,
|
||||
targetId = 20L,
|
||||
scheduledAtUtc = "2026-06-26T00:00:00Z",
|
||||
isOnAir = true
|
||||
)
|
||||
),
|
||||
recentNews = listOf(
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user