diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt new file mode 100644 index 00000000..b6b1a831 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacade.kt @@ -0,0 +1,28 @@ +package kr.co.vividnext.sodalive.v2.api.creator.channel.home.application + +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.v2.api.creator.channel.home.dto.CreatorChannelHomeResponse +import kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryService +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.time.LocalDateTime + +@Service +@Transactional(readOnly = true) +class CreatorChannelHomeFacade( + private val creatorChannelHomeQueryService: CreatorChannelHomeQueryService +) { + fun getHome( + creatorId: Long, + viewer: Member, + now: LocalDateTime = LocalDateTime.now() + ): CreatorChannelHomeResponse { + return CreatorChannelHomeResponse.from( + creatorChannelHomeQueryService.getHome( + creatorId = creatorId, + viewer = viewer, + now = now + ) + ) + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt new file mode 100644 index 00000000..6df59138 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/application/CreatorChannelHomeFacadeTest.kt @@ -0,0 +1,215 @@ +package kr.co.vividnext.sodalive.v2.api.creator.channel.home.application + +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberRole +import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType +import kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryService +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelActivity +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelAudioContent +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelCommunityPost +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelCreator +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelDonation +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelFanTalk +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelFanTalkSummary +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHome +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelLive +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSchedule +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSeries +import kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelSns +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 +import org.mockito.Mockito +import java.time.LocalDateTime + +class CreatorChannelHomeFacadeTest { + @Test + @DisplayName("크리에이터 채널 홈 facade는 query service 결과를 공개 응답 DTO로 변환한다") + fun shouldMapHomeQueryResultToPublicResponse() { + val service = Mockito.mock(CreatorChannelHomeQueryService::class.java) + val facade = CreatorChannelHomeFacade(service) + val viewer = createMember(id = 10L) + val now = LocalDateTime.of(2026, 6, 17, 10, 0) + Mockito.doReturn(createHome()).`when`(service).getHome( + creatorId = 1L, + viewer = viewer, + now = now + ) + + val response = facade.getHome( + creatorId = 1L, + viewer = viewer, + now = now + ) + + assertEquals(1L, response.creator.creatorId) + assertEquals(11L, response.creator.characterId) + assertTrue(response.creator.isAiChatAvailable) + assertFalse(response.creator.isDmAvailable) + assertEquals(101L, response.currentLive?.liveId) + assertEquals("2026-06-12T01:00:00Z", response.currentLive?.beginDateTimeUtc) + assertEquals(201L, response.latestAudioContent?.audioContentId) + assertTrue(response.latestAudioContent?.isPointAvailable == true) + assertTrue(response.latestAudioContent?.isFirstContent == true) + assertTrue(response.latestAudioContent?.isOriginalSeries == true) + assertTrue(response.latestAudioContent?.isOwned == true) + assertFalse(response.latestAudioContent?.isRented == true) + assertEquals("thanks", response.channelDonations.first().message) + assertEquals(301L, response.notices.first().postId) + assertEquals(501L, response.schedules.first().targetId) + assertEquals(202L, response.audioContents.first().audioContentId) + assertFalse(response.audioContents.first().isOwned) + assertTrue(response.audioContents.first().isRented) + assertEquals(601L, response.series.first().seriesId) + assertTrue(response.series.first().isNew) + assertTrue(response.series.first().isOriginal) + assertEquals(302L, response.communities.first().postId) + assertEquals(701L, response.fanTalk.latestFanTalk?.fanTalkId) + assertEquals("introduce", response.introduce) + assertEquals(10, response.activity.liveCount) + assertEquals("instagram", response.sns.instagramUrl) + } + + private fun createMember(id: Long): Member { + return Member( + email = "viewer$id@test.com", + password = "password", + nickname = "viewer$id", + role = MemberRole.USER + ).apply { + this.id = id + } + } + + private fun createHome(): CreatorChannelHome { + val post = CreatorChannelCommunityPost( + postId = 301L, + creatorId = 1L, + creatorNickname = "creator", + creatorProfileUrl = "profile.png", + imageUrl = "image.png", + audioUrl = "audio.mp3", + content = "notice", + price = 10, + date = LocalDateTime.of(2026, 6, 12, 4, 0), + existOrdered = true, + likeCount = 2, + commentCount = 3 + ) + + return CreatorChannelHome( + creator = CreatorChannelCreator( + creatorId = 1L, + characterId = 11L, + nickname = "creator", + profileImageUrl = "profile.png", + followerCount = 100, + isAiChatAvailable = true, + isDmAvailable = false, + isFollow = true, + isNotify = false + ), + currentLive = CreatorChannelLive( + liveId = 101L, + title = "live", + coverImageUrl = "live.png", + beginDateTime = LocalDateTime.of(2026, 6, 12, 1, 0), + price = 20, + isAdult = true + ), + latestAudioContent = CreatorChannelAudioContent( + audioContentId = 201L, + title = "audio", + duration = "00:10:00", + imageUrl = "audio.png", + price = 30, + isAdult = true, + isPointAvailable = true, + isFirstContent = true, + publishedAt = LocalDateTime.of(2026, 6, 11, 1, 0), + seriesName = "series", + isOriginalSeries = true, + isOwned = true, + isRented = false + ), + channelDonations = listOf( + CreatorChannelDonation( + nickname = "fan", + profileImageUrl = "fan.png", + can = 50, + message = "thanks", + createdAt = LocalDateTime.of(2026, 6, 12, 2, 0) + ) + ), + notices = listOf(post), + schedules = listOf( + CreatorChannelSchedule( + scheduledAt = LocalDateTime.of(2026, 6, 12, 3, 0), + title = "schedule", + type = CreatorActivityType.LIVE, + targetId = 501L, + isAdult = false + ) + ), + audioContents = listOf( + CreatorChannelAudioContent( + audioContentId = 202L, + title = "audio2", + duration = null, + imageUrl = null, + price = 0, + isAdult = false, + isPointAvailable = false, + isFirstContent = false, + publishedAt = LocalDateTime.of(2026, 6, 10, 1, 0), + seriesName = null, + isOriginalSeries = null, + isOwned = false, + isRented = true + ) + ), + series = listOf( + CreatorChannelSeries( + seriesId = 601L, + title = "series", + coverImageUrl = "series.png", + numberOfContent = 3, + isNew = true, + isOriginal = true + ) + ), + communities = listOf(post.copy(postId = 302L, content = "community")), + fanTalk = CreatorChannelFanTalkSummary( + totalCount = 1, + latestFanTalk = CreatorChannelFanTalk( + fanTalkId = 701L, + memberId = 2L, + nickname = "fan", + profileImageUrl = "fan.png", + content = "hello", + languageCode = "ko", + createdAt = LocalDateTime.of(2026, 6, 12, 5, 0) + ) + ), + introduce = "introduce", + activity = CreatorChannelActivity( + debutDate = LocalDateTime.of(2026, 6, 12, 6, 0), + dDay = "D+1", + liveCount = 10, + liveDurationHours = 20, + liveContributorCount = 30, + audioContentCount = 40, + seriesCount = 50 + ), + sns = CreatorChannelSns( + instagramUrl = "instagram", + fancimmUrl = "fancimm", + xUrl = "x", + youtubeUrl = "youtube", + kakaoOpenChatUrl = "kakao" + ) + ) + } +}