From 6a3ca5f44f3cf774f95be7ff180415ea1d7d00a4 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 17 Jun 2026 18:20:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator):=20=EC=B1=84=EB=84=90=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=20=ED=83=AD=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=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 --- .../CreatorChannelLiveReplayQueryPolicy.kt | 28 +++++++ .../live/domain/CreatorChannelLiveTab.kt | 38 ++++++++++ .../channel/live/domain/CreatorChannelPage.kt | 9 +++ ...CreatorChannelLiveReplayQueryPolicyTest.kt | 76 +++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicy.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveTab.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelPage.kt create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicyTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicy.kt new file mode 100644 index 00000000..4c25f42a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicy.kt @@ -0,0 +1,28 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.live.domain + +import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.stereotype.Component + +@Component +class CreatorChannelLiveReplayQueryPolicy { + fun createPage(page: Int, size: Int): CreatorChannelPage { + if (page < MIN_PAGE || size < MIN_PAGE_SIZE || size > MAX_PAGE_SIZE) { + throw SodaException(messageKey = "common.error.invalid_request") + } + return CreatorChannelPage(page = page, size = size) + } + + fun limitItems(fetched: List, page: CreatorChannelPage): List { + return fetched.take(page.size) + } + + fun hasNext(fetched: List<*>, page: CreatorChannelPage): Boolean { + return fetched.size > page.size + } + + companion object { + private const val MIN_PAGE = 0 + private const val MIN_PAGE_SIZE = 20 + private const val MAX_PAGE_SIZE = 50 + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveTab.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveTab.kt new file mode 100644 index 00000000..bedfa2e0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveTab.kt @@ -0,0 +1,38 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.live.domain + +import kr.co.vividnext.sodalive.v2.common.domain.ContentSort +import java.time.LocalDateTime + +data class CreatorChannelLiveTab( + val liveReplayContentCount: Int, + val currentLive: CreatorChannelLive?, + val liveReplayContents: List, + val sort: ContentSort, + val page: CreatorChannelPage, + val hasNext: Boolean +) + +data class CreatorChannelLive( + val liveId: Long, + val title: String, + val coverImageUrl: String?, + val beginDateTime: LocalDateTime, + val price: Int, + val isAdult: Boolean +) + +data class CreatorChannelAudioContent( + val audioContentId: Long, + val title: String, + val duration: String?, + val imageUrl: String?, + val price: Int, + val isAdult: Boolean, + val isPointAvailable: Boolean, + val isFirstContent: Boolean, + val publishedAt: LocalDateTime, + val seriesName: String?, + val isOriginalSeries: Boolean?, + val isOwned: Boolean, + val isRented: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelPage.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelPage.kt new file mode 100644 index 00000000..284a92db --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelPage.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.live.domain + +data class CreatorChannelPage( + val page: Int, + val size: Int +) { + val offset: Long = page.toLong() * size + val fetchLimit: Int = size + 1 +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicyTest.kt new file mode 100644 index 00000000..f26581c9 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelLiveReplayQueryPolicyTest.kt @@ -0,0 +1,76 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.live.domain + +import kr.co.vividnext.sodalive.common.SodaException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +class CreatorChannelLiveReplayQueryPolicyTest { + private val policy = CreatorChannelLiveReplayQueryPolicy() + + @Test + @DisplayName("라이브 다시듣기 page 정책은 offset, fetch limit, items limit, hasNext를 계산한다") + fun shouldCalculatePagePolicyForLiveReplayContents() { + val page = policy.createPage(page = 0, size = 20) + val fetched = (1..21).toList() + + val items = policy.limitItems(fetched, page) + + assertEquals(0L, page.offset) + assertEquals(21, page.fetchLimit) + assertEquals(20, items.size) + assertEquals((1..20).toList(), items) + assertTrue(policy.hasNext(fetched, page)) + } + + @Test + @DisplayName("size가 50이면 fetch limit을 51로 계산한다") + fun shouldCalculateFetchLimitWhenSizeIsMaximum() { + val page = policy.createPage(page = 1, size = 50) + + assertEquals(50L, page.offset) + assertEquals(51, page.fetchLimit) + } + + @Test + @DisplayName("page가 0보다 작으면 invalid request 예외를 던진다") + fun shouldThrowInvalidRequestWhenPageIsNegative() { + val exception = assertThrows(SodaException::class.java) { + policy.createPage(page = -1, size = 20) + } + + assertEquals("common.error.invalid_request", exception.messageKey) + } + + @Test + @DisplayName("size가 20보다 작으면 invalid request 예외를 던진다") + fun shouldThrowInvalidRequestWhenSizeIsLessThanMinimum() { + val exception = assertThrows(SodaException::class.java) { + policy.createPage(page = 0, size = 19) + } + + assertEquals("common.error.invalid_request", exception.messageKey) + } + + @Test + @DisplayName("size가 50보다 크면 invalid request 예외를 던진다") + fun shouldThrowInvalidRequestWhenSizeIsGreaterThanMaximum() { + val exception = assertThrows(SodaException::class.java) { + policy.createPage(page = 0, size = 51) + } + + assertEquals("common.error.invalid_request", exception.messageKey) + } + + @Test + @DisplayName("size가 Int 최대값이면 fetch limit overflow 전에 invalid request 예외를 던진다") + fun shouldThrowInvalidRequestWhenSizeWouldOverflowFetchLimit() { + val exception = assertThrows(SodaException::class.java) { + policy.createPage(page = 0, size = Int.MAX_VALUE) + } + + assertEquals("common.error.invalid_request", exception.messageKey) + } +}