diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt index ff177312..c42dde50 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt @@ -155,7 +155,8 @@ class DefaultCreatorChannelHomeQueryRepository( override fun findLatestAudioContent( creatorId: Long, now: LocalDateTime, - canViewAdultContent: Boolean + canViewAdultContent: Boolean, + viewerId: Long? ): CreatorChannelAudioContentRecord? { val row = findAudioContentRows(creatorId, now, null, canViewAdultContent, 1).firstOrNull() ?: return null return row.toAudioRecord( @@ -351,6 +352,7 @@ class DefaultCreatorChannelHomeQueryRepository( now: LocalDateTime, latestAudioContentId: Long?, canViewAdultContent: Boolean, + viewerId: Long?, limit: Int ): List { val rows = findAudioContentRows(creatorId, now, latestAudioContentId, canViewAdultContent, limit) @@ -564,7 +566,9 @@ class DefaultCreatorChannelHomeQueryRepository( isFirstContent = firstContentId == audioContentId, publishedAt = get(audioContent.releaseDate)!!, seriesName = seriesSummary?.title, - isOriginalSeries = seriesSummary?.isOriginal + isOriginalSeries = seriesSummary?.isOriginal, + isOwned = false, + isRented = false ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt index 758c3875..62d0f1bc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt @@ -72,14 +72,15 @@ class CreatorChannelHomeQueryService( val isViewerCreator = viewerId == creatorId val effectiveViewerGender = viewer.effectiveGender() val latestAudioContent = queryPort - .findLatestAudioContent(creatorId, now, canViewAdultContent) + .findLatestAudioContent(creatorId, now, canViewAdultContent, viewerId) ?.toDomain() val audioContents = queryPolicy.excludeLatestAudioContent( queryPort.findAudioContents( creatorId = creatorId, now = now, latestAudioContentId = latestAudioContent?.audioContentId, - canViewAdultContent = canViewAdultContent + canViewAdultContent = canViewAdultContent, + viewerId = viewerId ).map { it.toDomain() }, latestAudioContent?.audioContentId ) @@ -179,7 +180,9 @@ class CreatorChannelHomeQueryService( isFirstContent = isFirstContent, publishedAt = publishedAt, seriesName = seriesName, - isOriginalSeries = isOriginalSeries + isOriginalSeries = isOriginalSeries, + isOwned = isOwned, + isRented = isRented ) private fun CreatorChannelDonationRecord.toDomain() = CreatorChannelDonation( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt index 710c0e51..b9c7350d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt @@ -51,7 +51,9 @@ data class CreatorChannelAudioContent( val isFirstContent: Boolean, val publishedAt: LocalDateTime, val seriesName: String?, - val isOriginalSeries: Boolean? + val isOriginalSeries: Boolean?, + val isOwned: Boolean, + val isRented: Boolean ) data class CreatorChannelDonation( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt index 1004f7cf..42e9169c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt @@ -122,7 +122,11 @@ data class CreatorChannelAudioContentResponse( val isFirstContent: Boolean, val seriesName: String?, @JsonProperty("isOriginalSeries") - val isOriginalSeries: Boolean? + val isOriginalSeries: Boolean?, + @JsonProperty("isOwned") + val isOwned: Boolean, + @JsonProperty("isRented") + val isRented: Boolean ) { companion object { fun from(audioContent: CreatorChannelAudioContent): CreatorChannelAudioContentResponse { @@ -136,7 +140,9 @@ data class CreatorChannelAudioContentResponse( isPointAvailable = audioContent.isPointAvailable, isFirstContent = audioContent.isFirstContent, seriesName = audioContent.seriesName, - isOriginalSeries = audioContent.isOriginalSeries + isOriginalSeries = audioContent.isOriginalSeries, + isOwned = audioContent.isOwned, + isRented = audioContent.isRented ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt index 00ba456d..fffaf653 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt @@ -23,7 +23,8 @@ interface CreatorChannelHomeQueryPort { fun findLatestAudioContent( creatorId: Long, now: LocalDateTime, - canViewAdultContent: Boolean + canViewAdultContent: Boolean, + viewerId: Long? = null ): CreatorChannelAudioContentRecord? fun findChannelDonations( @@ -56,6 +57,7 @@ interface CreatorChannelHomeQueryPort { now: LocalDateTime, latestAudioContentId: Long?, canViewAdultContent: Boolean, + viewerId: Long? = null, limit: Int = 9 ): List @@ -109,7 +111,9 @@ data class CreatorChannelAudioContentRecord( val isFirstContent: Boolean, val publishedAt: LocalDateTime, val seriesName: String?, - val isOriginalSeries: Boolean? + val isOriginalSeries: Boolean?, + val isOwned: Boolean, + val isRented: Boolean ) data class CreatorChannelDonationRecord( diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt index 22d58f5e..284b3c36 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt @@ -122,6 +122,10 @@ class CreatorChannelHomeControllerTest @Autowired constructor( .andExpect(jsonPath("$.data.latestAudioContent.isPointAvailable").value(true)) .andExpect(jsonPath("$.data.latestAudioContent.isFirstContent").value(true)) .andExpect(jsonPath("$.data.latestAudioContent.isOriginalSeries").value(true)) + .andExpect(jsonPath("$.data.latestAudioContent.isOwned").value(true)) + .andExpect(jsonPath("$.data.latestAudioContent.isRented").value(false)) + .andExpect(jsonPath("$.data.audioContents[0].isOwned").value(false)) + .andExpect(jsonPath("$.data.audioContents[0].isRented").value(true)) .andExpect(jsonPath("$.data.currentLive.isAdult").value(true)) .andExpect(jsonPath("$.data.schedules[0].isAdult").doesNotExist()) .andExpect(jsonPath("$.data.channelDonations[0].donationId").doesNotExist()) @@ -195,7 +199,9 @@ class CreatorChannelHomeControllerTest @Autowired constructor( isFirstContent = true, publishedAt = LocalDateTime.of(2026, 6, 11, 1, 0), seriesName = "series", - isOriginalSeries = true + isOriginalSeries = true, + isOwned = true, + isRented = false ), channelDonations = listOf( CreatorChannelDonation( @@ -228,7 +234,9 @@ class CreatorChannelHomeControllerTest @Autowired constructor( isFirstContent = false, publishedAt = LocalDateTime.of(2026, 6, 10, 1, 0), seriesName = null, - isOriginalSeries = null + isOriginalSeries = null, + isOwned = false, + isRented = true ) ), series = listOf( diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt index 2a6e0cdb..16c78a23 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt @@ -68,7 +68,11 @@ class CreatorChannelHomeQueryServiceTest { assertEquals("https://cdn.test/profile/creator.png", home.creator.profileImageUrl) assertEquals("https://cdn.test/live.png", home.currentLive?.coverImageUrl) assertEquals("https://cdn.test/audio/latest.png", home.latestAudioContent?.imageUrl) + assertTrue(home.latestAudioContent?.isOwned == true) + assertFalse(home.latestAudioContent?.isRented == true) assertEquals(listOf(203L, 202L), home.audioContents.map { it.audioContentId }) + assertEquals(listOf(false, true), home.audioContents.map { it.isOwned }) + assertEquals(listOf(true, false), home.audioContents.map { it.isRented }) assertEquals(listOf(402L, 401L, 404L), home.schedules.map { it.targetId }) assertFalse(home.schedules.any { it.isAdult }) assertEquals("https://cdn.test/profile/fan.png", home.channelDonations.first().profileImageUrl) @@ -179,6 +183,8 @@ class CreatorChannelHomeQueryServiceTest { assertEquals(home.creator.characterId, response.creator.characterId) assertEquals(home.currentLive?.liveId, response.currentLive?.liveId) assertEquals(home.latestAudioContent?.audioContentId, response.latestAudioContent?.audioContentId) + assertEquals(home.latestAudioContent?.isOwned, response.latestAudioContent?.isOwned) + assertEquals(home.latestAudioContent?.isRented, response.latestAudioContent?.isRented) assertEquals(home.channelDonations.first().message, response.channelDonations.first().message) assertEquals(home.notices.first().postId, response.notices.first().postId) assertEquals(home.schedules.first().targetId, response.schedules.first().targetId) @@ -217,6 +223,8 @@ class CreatorChannelHomeQueryServiceTest { assertTrue(response.latestAudioContent?.isPointAvailable == true) assertTrue(response.latestAudioContent?.isFirstContent == true) assertTrue(response.latestAudioContent?.isAdult == true) + assertTrue(response.latestAudioContent?.isOwned == true) + assertFalse(response.latestAudioContent?.isRented == true) assertTrue(response.series.first().isOriginal) assertNotNull(response.latestAudioContent?.isOriginalSeries) } @@ -239,6 +247,10 @@ class CreatorChannelHomeQueryServiceTest { assertFalse(json["latestAudioContent"].has("firstContent")) assertTrue(json["latestAudioContent"]["isAdult"].asBoolean()) assertFalse(json["latestAudioContent"].has("adult")) + assertTrue(json["latestAudioContent"]["isOwned"].asBoolean()) + assertFalse(json["latestAudioContent"].has("owned")) + assertFalse(json["latestAudioContent"]["isRented"].asBoolean()) + assertFalse(json["latestAudioContent"].has("rented")) assertTrue(json["series"][0]["isOriginal"].asBoolean()) assertFalse(json["series"][0].has("original")) assertFalse(json["series"][0].has("published" + "DaysOfWeek")) @@ -297,7 +309,9 @@ class CreatorChannelHomeQueryServiceTest { isFirstContent = true, publishedAt = LocalDateTime.of(2026, 6, 11, 1, 0), seriesName = "series", - isOriginalSeries = true + isOriginalSeries = true, + isOwned = true, + isRented = false ), channelDonations = listOf( CreatorChannelDonation( @@ -330,7 +344,9 @@ class CreatorChannelHomeQueryServiceTest { isFirstContent = false, publishedAt = LocalDateTime.of(2026, 6, 10, 1, 0), seriesName = null, - isOriginalSeries = null + isOriginalSeries = null, + isOwned = false, + isRented = true ) ), series = listOf( @@ -484,7 +500,8 @@ private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort { override fun findLatestAudioContent( creatorId: Long, now: LocalDateTime, - canViewAdultContent: Boolean + canViewAdultContent: Boolean, + viewerId: Long? ): CreatorChannelAudioContentRecord? = audioContentRecord(201L, "audio/latest.png") override fun findChannelDonations( @@ -553,6 +570,7 @@ private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort { now: LocalDateTime, latestAudioContentId: Long?, canViewAdultContent: Boolean, + viewerId: Long?, limit: Int ): List { audioContentsLatestAudioContentId = latestAudioContentId @@ -630,7 +648,9 @@ private class FakeCreatorChannelHomeQueryPort : CreatorChannelHomeQueryPort { isFirstContent = false, publishedAt = LocalDateTime.of(2026, 6, 10, 0, audioContentId.toInt() % 60), seriesName = null, - isOriginalSeries = null + isOriginalSeries = null, + isOwned = audioContentId == 201L || audioContentId == 202L, + isRented = audioContentId == 203L ) } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt index 5bbdced7..ff558e30 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt @@ -127,7 +127,9 @@ class CreatorChannelHomeQueryPolicyTest { isFirstContent = false, publishedAt = publishedAt, seriesName = null, - isOriginalSeries = null + isOriginalSeries = null, + isOwned = false, + isRented = false ) } }