From 3c4f852ddbae77818a845804ac3a622d5b939d15 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 27 Jun 2026 05:10:37 +0900 Subject: [PATCH] =?UTF-8?q?feat(content):=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/ContentOverviewPageResponse.kt | 76 ++++++++++++++++++ .../dto/ContentOverviewPageResponseTest.kt | 78 +++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt new file mode 100644 index 00000000..c7a69ac9 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponse.kt @@ -0,0 +1,76 @@ +package kr.co.vividnext.sodalive.v2.api.content.overview.dto + +import com.fasterxml.jackson.annotation.JsonProperty +import kr.co.vividnext.sodalive.v2.content.recommendation.domain.AudioCard +import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeFirstAudioContentRecord + +data class ContentOverviewPageResponse( + val type: ContentOverviewType, + val items: List, + val page: Int, + val size: Int, + @JsonProperty("hasNext") + val hasNext: Boolean +) + +enum class ContentOverviewType { + NEW_AND_HOT_AUDIO, + FIRST_AUDIO_CONTENT; + + companion object { + fun from(value: String?): ContentOverviewType { + return values().firstOrNull { it.name == value } ?: NEW_AND_HOT_AUDIO + } + } +} + +data class ContentOverviewItemResponse( + val contentId: Long, + val title: String, + val coverImage: String?, + val price: Int, + @JsonProperty("isPointAvailable") + val isPointAvailable: Boolean, + val creatorNickname: String, + @JsonProperty("isAdult") + val isAdult: Boolean, + @JsonProperty("isFirstContent") + val isFirstContent: Boolean, + @JsonProperty("isOriginalSeries") + val isOriginalSeries: Boolean +) { + companion object { + fun fromNewAndHot(audio: AudioCard): ContentOverviewItemResponse { + return ContentOverviewItemResponse( + contentId = audio.audioContentId, + title = audio.title, + coverImage = audio.imageUrl, + price = audio.price, + isPointAvailable = audio.isPointAvailable, + creatorNickname = audio.creatorNickname, + isAdult = audio.isAdult, + isFirstContent = audio.isFirstContent, + isOriginalSeries = audio.isOriginalSeries + ) + } + + fun fromFirstAudioContent( + audio: HomeFirstAudioContentRecord, + coverImage: String?, + isAdult: Boolean, + isOriginalSeries: Boolean + ): ContentOverviewItemResponse { + return ContentOverviewItemResponse( + contentId = audio.contentId, + title = audio.title, + coverImage = coverImage, + price = audio.price, + isPointAvailable = audio.isPointAvailable, + creatorNickname = audio.creatorNickname, + isAdult = isAdult, + isFirstContent = true, + isOriginalSeries = isOriginalSeries + ) + } + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt new file mode 100644 index 00000000..4ee45cd2 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/overview/dto/ContentOverviewPageResponseTest.kt @@ -0,0 +1,78 @@ +package kr.co.vividnext.sodalive.v2.api.content.overview.dto + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import kr.co.vividnext.sodalive.v2.recommendation.port.out.HomeFirstAudioContentRecord +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +class ContentOverviewPageResponseTest { + private val objectMapper = jacksonObjectMapper() + + @Test + @DisplayName("콘텐츠 전체보기 응답은 공개 JSON 필드명만 직렬화한다") + fun shouldSerializeContentOverviewPageResponse() { + val response = ContentOverviewPageResponse( + type = ContentOverviewType.NEW_AND_HOT_AUDIO, + items = listOf( + ContentOverviewItemResponse( + contentId = 1L, + title = "audio", + coverImage = "https://cdn.test/audio.png", + price = 10, + isPointAvailable = true, + creatorNickname = "creator", + isAdult = false, + isFirstContent = true, + isOriginalSeries = false + ) + ), + page = 0, + size = 20, + hasNext = true + ) + + val json = objectMapper.readTree(objectMapper.writeValueAsString(response)) + + assertEquals("NEW_AND_HOT_AUDIO", json["type"].asText()) + assertEquals(true, json["hasNext"].asBoolean()) + assertEquals(1L, json["items"][0]["contentId"].asLong()) + assertEquals("https://cdn.test/audio.png", json["items"][0]["coverImage"].asText()) + assertEquals(true, json["items"][0]["isPointAvailable"].asBoolean()) + assertEquals(false, json["items"][0]["isAdult"].asBoolean()) + assertEquals(true, json["items"][0]["isFirstContent"].asBoolean()) + assertEquals(false, json["items"][0]["isOriginalSeries"].asBoolean()) + assertEquals(false, json["items"][0].has("audioContentId")) + assertEquals(false, json["items"][0].has("imageUrl")) + assertEquals(false, json["items"][0].has("duration")) + assertEquals(false, json["items"][0].has("creatorId")) + assertEquals(false, json["items"][0].has("creatorProfileImage")) + assertEquals(false, json["items"][0].has("pointAvailable")) + assertEquals(false, json["items"][0].has("adult")) + assertEquals(false, json["items"][0].has("firstContent")) + assertEquals(false, json["items"][0].has("originalSeries")) + } + + @Test + @DisplayName("첫 번째 오디오 콘텐츠 변환은 성인/오리지널 플래그를 전달한다") + fun shouldMapFirstAudioContentFlags() { + val response = ContentOverviewItemResponse.fromFirstAudioContent( + audio = HomeFirstAudioContentRecord( + contentId = 1L, + creatorId = 10L, + creatorNickname = "creator", + creatorProfileImage = null, + title = "first audio", + price = 100, + coverImage = "cover/audio.png", + isPointAvailable = true + ), + coverImage = "https://cdn.test/cover/audio.png", + isAdult = true, + isOriginalSeries = true + ) + + assertEquals(true, response.isAdult) + assertEquals(true, response.isOriginalSeries) + } +}