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 new file mode 100644 index 00000000..2dfeb8c3 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt @@ -0,0 +1,132 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.domain + +import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType +import java.time.LocalDateTime + +data class CreatorChannelHome( + val creator: CreatorChannelCreator, + val currentLive: CreatorChannelLive?, + val latestAudioContent: CreatorChannelAudioContent?, + val channelDonations: List, + val notices: List, + val schedules: List, + val audioContents: List, + val series: List, + val communities: List, + val fanTalk: CreatorChannelFanTalkSummary, + val introduce: String, + val activity: CreatorChannelActivity, + val sns: CreatorChannelSns +) + +data class CreatorChannelCreator( + val creatorId: Long, + val nickname: String, + val profileImageUrl: String, + val followerCount: Int, + val isAiChatAvailable: Boolean, + val isDmAvailable: Boolean, + val isFollow: Boolean, + val isNotify: 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? +) + +data class CreatorChannelDonation( + val donationId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val can: Int, + val isSecret: Boolean, + val message: String, + val createdAt: LocalDateTime +) + +data class CreatorChannelSchedule( + val scheduledAt: LocalDateTime, + val title: String, + val type: CreatorActivityType, + val targetId: Long +) + +data class CreatorChannelSeries( + val seriesId: Long, + val title: String, + val coverImageUrl: String, + val publishedDaysOfWeek: String, + val isComplete: Boolean, + val numberOfContent: Int, + val isNew: Boolean, + val isPopular: Boolean, + val isOriginal: Boolean +) + +data class CreatorChannelCommunityPost( + val postId: Long, + val creatorId: Long, + val creatorNickname: String, + val creatorProfileUrl: String, + val imageUrl: String?, + val audioUrl: String?, + val content: String, + val price: Int, + val date: LocalDateTime, + val existOrdered: Boolean, + val likeCount: Int, + val commentCount: Int +) + +data class CreatorChannelFanTalkSummary( + val totalCount: Int, + val latestFanTalk: CreatorChannelFanTalk? +) + +data class CreatorChannelFanTalk( + val fanTalkId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val content: String, + val languageCode: String?, + val createdAt: LocalDateTime +) + +data class CreatorChannelActivity( + val debutDate: LocalDateTime?, + val dDay: String, + val liveCount: Long, + val liveDurationHours: Long, + val liveContributorCount: Long, + val audioContentCount: Long, + val seriesCount: Long +) + +data class CreatorChannelSns( + val instagramUrl: String, + val fancimmUrl: String, + val xUrl: String, + val youtubeUrl: String, + val kakaoOpenChatUrl: String +) 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 new file mode 100644 index 00000000..024b36fa --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt @@ -0,0 +1,338 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.dto + +import com.fasterxml.jackson.annotation.JsonProperty +import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType +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 java.time.LocalDateTime +import java.time.ZoneOffset + +data class CreatorChannelHomeResponse( + val creator: CreatorChannelCreatorResponse, + val currentLive: CreatorChannelLiveResponse?, + val latestAudioContent: CreatorChannelAudioContentResponse?, + val channelDonations: List, + val notices: List, + val schedules: List, + val audioContents: List, + val series: List, + val communities: List, + val fanTalk: CreatorChannelFanTalkSummaryResponse, + val introduce: String, + val activity: CreatorChannelActivityResponse, + val sns: CreatorChannelSnsResponse +) { + companion object { + fun from(home: CreatorChannelHome): CreatorChannelHomeResponse { + return CreatorChannelHomeResponse( + creator = CreatorChannelCreatorResponse.from(home.creator), + currentLive = home.currentLive?.let(CreatorChannelLiveResponse::from), + latestAudioContent = home.latestAudioContent?.let(CreatorChannelAudioContentResponse::from), + channelDonations = home.channelDonations.map(CreatorChannelDonationResponse::from), + notices = home.notices.map(CreatorChannelCommunityPostResponse::from), + schedules = home.schedules.map(CreatorChannelScheduleResponse::from), + audioContents = home.audioContents.map(CreatorChannelAudioContentResponse::from), + series = home.series.map(CreatorChannelSeriesResponse::from), + communities = home.communities.map(CreatorChannelCommunityPostResponse::from), + fanTalk = CreatorChannelFanTalkSummaryResponse.from(home.fanTalk), + introduce = home.introduce, + activity = CreatorChannelActivityResponse.from(home.activity), + sns = CreatorChannelSnsResponse.from(home.sns) + ) + } + } +} + +data class CreatorChannelCreatorResponse( + val creatorId: Long, + val nickname: String, + val profileImageUrl: String, + val followerCount: Int, + @JsonProperty("isAiChatAvailable") + val isAiChatAvailable: Boolean, + @JsonProperty("isDmAvailable") + val isDmAvailable: Boolean, + @JsonProperty("isFollow") + val isFollow: Boolean, + @JsonProperty("isNotify") + val isNotify: Boolean +) { + companion object { + fun from(creator: CreatorChannelCreator): CreatorChannelCreatorResponse { + return CreatorChannelCreatorResponse( + creatorId = creator.creatorId, + nickname = creator.nickname, + profileImageUrl = creator.profileImageUrl, + followerCount = creator.followerCount, + isAiChatAvailable = creator.isAiChatAvailable, + isDmAvailable = creator.isDmAvailable, + isFollow = creator.isFollow, + isNotify = creator.isNotify + ) + } + } +} + +data class CreatorChannelLiveResponse( + val liveId: Long, + val title: String, + val coverImageUrl: String?, + val beginDateTimeUtc: String, + val price: Int, + @JsonProperty("isAdult") + val isAdult: Boolean +) { + companion object { + fun from(live: CreatorChannelLive): CreatorChannelLiveResponse { + return CreatorChannelLiveResponse( + liveId = live.liveId, + title = live.title, + coverImageUrl = live.coverImageUrl, + beginDateTimeUtc = live.beginDateTime.toUtcIso(), + price = live.price, + isAdult = live.isAdult + ) + } + } +} + +data class CreatorChannelAudioContentResponse( + val audioContentId: Long, + val title: String, + val duration: String?, + val imageUrl: String?, + val price: Int, + @JsonProperty("isAdult") + val isAdult: Boolean, + @JsonProperty("isPointAvailable") + val isPointAvailable: Boolean, + @JsonProperty("isFirstContent") + val isFirstContent: Boolean, + val seriesName: String?, + @JsonProperty("isOriginalSeries") + val isOriginalSeries: Boolean? +) { + companion object { + fun from(audioContent: CreatorChannelAudioContent): CreatorChannelAudioContentResponse { + return CreatorChannelAudioContentResponse( + audioContentId = audioContent.audioContentId, + title = audioContent.title, + duration = audioContent.duration, + imageUrl = audioContent.imageUrl, + price = audioContent.price, + isAdult = audioContent.isAdult, + isPointAvailable = audioContent.isPointAvailable, + isFirstContent = audioContent.isFirstContent, + seriesName = audioContent.seriesName, + isOriginalSeries = audioContent.isOriginalSeries + ) + } + } +} + +data class CreatorChannelDonationResponse( + val donationId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val can: Int, + @JsonProperty("isSecret") + val isSecret: Boolean, + val message: String, + val createdAtUtc: String +) { + companion object { + fun from(donation: CreatorChannelDonation): CreatorChannelDonationResponse { + return CreatorChannelDonationResponse( + donationId = donation.donationId, + memberId = donation.memberId, + nickname = donation.nickname, + profileImageUrl = donation.profileImageUrl, + can = donation.can, + isSecret = donation.isSecret, + message = donation.message, + createdAtUtc = donation.createdAt.toUtcIso() + ) + } + } +} + +data class CreatorChannelScheduleResponse( + val scheduledAtUtc: String, + val title: String, + val type: CreatorActivityType, + val targetId: Long +) { + companion object { + fun from(schedule: CreatorChannelSchedule): CreatorChannelScheduleResponse { + return CreatorChannelScheduleResponse( + scheduledAtUtc = schedule.scheduledAt.toUtcIso(), + title = schedule.title, + type = schedule.type, + targetId = schedule.targetId + ) + } + } +} + +data class CreatorChannelSeriesResponse( + val seriesId: Long, + val title: String, + val coverImageUrl: String, + val publishedDaysOfWeek: String, + @JsonProperty("isComplete") + val isComplete: Boolean, + val numberOfContent: Int, + @JsonProperty("isNew") + val isNew: Boolean, + @JsonProperty("isPopular") + val isPopular: Boolean, + @JsonProperty("isOriginal") + val isOriginal: Boolean +) { + companion object { + fun from(series: CreatorChannelSeries): CreatorChannelSeriesResponse { + return CreatorChannelSeriesResponse( + seriesId = series.seriesId, + title = series.title, + coverImageUrl = series.coverImageUrl, + publishedDaysOfWeek = series.publishedDaysOfWeek, + isComplete = series.isComplete, + numberOfContent = series.numberOfContent, + isNew = series.isNew, + isPopular = series.isPopular, + isOriginal = series.isOriginal + ) + } + } +} + +data class CreatorChannelCommunityPostResponse( + val postId: Long, + val creatorId: Long, + val creatorNickname: String, + val creatorProfileUrl: String, + val imageUrl: String?, + val audioUrl: String?, + val content: String, + val price: Int, + val dateUtc: String, + val existOrdered: Boolean, + val likeCount: Int, + val commentCount: Int +) { + companion object { + fun from(post: CreatorChannelCommunityPost): CreatorChannelCommunityPostResponse { + return CreatorChannelCommunityPostResponse( + postId = post.postId, + creatorId = post.creatorId, + creatorNickname = post.creatorNickname, + creatorProfileUrl = post.creatorProfileUrl, + imageUrl = post.imageUrl, + audioUrl = post.audioUrl, + content = post.content, + price = post.price, + dateUtc = post.date.toUtcIso(), + existOrdered = post.existOrdered, + likeCount = post.likeCount, + commentCount = post.commentCount + ) + } + } +} + +data class CreatorChannelFanTalkSummaryResponse( + val totalCount: Int, + val latestFanTalk: CreatorChannelFanTalkResponse? +) { + companion object { + fun from(summary: CreatorChannelFanTalkSummary): CreatorChannelFanTalkSummaryResponse { + return CreatorChannelFanTalkSummaryResponse( + totalCount = summary.totalCount, + latestFanTalk = summary.latestFanTalk?.let(CreatorChannelFanTalkResponse::from) + ) + } + } +} + +data class CreatorChannelFanTalkResponse( + val fanTalkId: Long, + val memberId: Long, + val nickname: String, + val profileImageUrl: String, + val content: String, + val languageCode: String?, + val createdAtUtc: String +) { + companion object { + fun from(fanTalk: CreatorChannelFanTalk): CreatorChannelFanTalkResponse { + return CreatorChannelFanTalkResponse( + fanTalkId = fanTalk.fanTalkId, + memberId = fanTalk.memberId, + nickname = fanTalk.nickname, + profileImageUrl = fanTalk.profileImageUrl, + content = fanTalk.content, + languageCode = fanTalk.languageCode, + createdAtUtc = fanTalk.createdAt.toUtcIso() + ) + } + } +} + +data class CreatorChannelActivityResponse( + val debutDateUtc: String?, + val dDay: String, + val liveCount: Long, + val liveDurationHours: Long, + val liveContributorCount: Long, + val audioContentCount: Long, + val seriesCount: Long +) { + companion object { + fun from(activity: CreatorChannelActivity): CreatorChannelActivityResponse { + return CreatorChannelActivityResponse( + debutDateUtc = activity.debutDate?.toUtcIso(), + dDay = activity.dDay, + liveCount = activity.liveCount, + liveDurationHours = activity.liveDurationHours, + liveContributorCount = activity.liveContributorCount, + audioContentCount = activity.audioContentCount, + seriesCount = activity.seriesCount + ) + } + } +} + +data class CreatorChannelSnsResponse( + val instagramUrl: String, + val fancimmUrl: String, + val xUrl: String, + val youtubeUrl: String, + val kakaoOpenChatUrl: String +) { + companion object { + fun from(sns: CreatorChannelSns): CreatorChannelSnsResponse { + return CreatorChannelSnsResponse( + instagramUrl = sns.instagramUrl, + fancimmUrl = sns.fancimmUrl, + xUrl = sns.xUrl, + youtubeUrl = sns.youtubeUrl, + kakaoOpenChatUrl = sns.kakaoOpenChatUrl + ) + } + } +} + +private fun LocalDateTime.toUtcIso(): String { + return atOffset(ZoneOffset.UTC).toInstant().toString() +} 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 new file mode 100644 index 00000000..0427524e --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt @@ -0,0 +1,231 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.application + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType +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 kr.co.vividnext.sodalive.v2.creator.channel.dto.CreatorChannelHomeResponse +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import java.time.LocalDateTime + +class CreatorChannelHomeQueryServiceTest { + private val objectMapper = jacksonObjectMapper() + + @Test + @DisplayName("크리에이터 채널 홈 모델은 홈 탭 전체 섹션을 담고 응답 DTO로 변환된다") + fun shouldConvertCreatorChannelHomeToResponse() { + val home = createHome() + + val response = CreatorChannelHomeResponse.from(home) + + assertEquals(home.creator.creatorId, response.creator.creatorId) + assertEquals(home.currentLive?.liveId, response.currentLive?.liveId) + assertEquals(home.latestAudioContent?.audioContentId, response.latestAudioContent?.audioContentId) + assertEquals(home.channelDonations.first().donationId, response.channelDonations.first().donationId) + assertEquals(home.notices.first().postId, response.notices.first().postId) + assertEquals(home.schedules.first().targetId, response.schedules.first().targetId) + assertEquals(home.audioContents.first().audioContentId, response.audioContents.first().audioContentId) + assertEquals(home.series.first().seriesId, response.series.first().seriesId) + assertEquals(home.communities.first().postId, response.communities.first().postId) + assertEquals(home.fanTalk.latestFanTalk?.fanTalkId, response.fanTalk.latestFanTalk?.fanTalkId) + assertEquals(home.introduce, response.introduce) + assertEquals(home.activity.liveCount, response.activity.liveCount) + assertEquals(home.sns.instagramUrl, response.sns.instagramUrl) + } + + @Test + @DisplayName("응답 DTO는 날짜/시간을 UTC ISO-8601 문자열로 변환한다") + fun shouldConvertDateTimeFieldsToUtcIsoString() { + val response = CreatorChannelHomeResponse.from(createHome()) + + assertEquals("2026-06-12T01:00:00Z", response.currentLive?.beginDateTimeUtc) + assertEquals("2026-06-12T02:00:00Z", response.channelDonations.first().createdAtUtc) + assertEquals("2026-06-12T03:00:00Z", response.schedules.first().scheduledAtUtc) + assertEquals("2026-06-12T04:00:00Z", response.notices.first().dateUtc) + assertEquals("2026-06-12T05:00:00Z", response.fanTalk.latestFanTalk?.createdAtUtc) + assertEquals("2026-06-12T06:00:00Z", response.activity.debutDateUtc) + } + + @Test + @DisplayName("응답 DTO는 Boolean 공개 계약 필드를 보존한다") + fun shouldPreserveBooleanApiFields() { + val response = CreatorChannelHomeResponse.from(createHome()) + + assertTrue(response.creator.isAiChatAvailable) + assertFalse(response.creator.isDmAvailable) + assertTrue(response.creator.isFollow) + assertFalse(response.creator.isNotify) + assertTrue(response.currentLive?.isAdult == true) + assertTrue(response.latestAudioContent?.isPointAvailable == true) + assertTrue(response.latestAudioContent?.isFirstContent == true) + assertTrue(response.latestAudioContent?.isAdult == true) + assertTrue(response.series.first().isOriginal) + assertNotNull(response.latestAudioContent?.isOriginalSeries) + } + + @Test + @DisplayName("응답 DTO는 Boolean JSON 필드명을 is prefix로 유지한다") + fun shouldSerializeBooleanFieldsWithIsPrefix() { + val response = CreatorChannelHomeResponse.from(createHome()) + + val json = objectMapper.readTree(objectMapper.writeValueAsString(response)) + + assertTrue(json["creator"]["isAiChatAvailable"].asBoolean()) + assertFalse(json["creator"].has("aiChatAvailable")) + assertFalse(json["creator"]["isDmAvailable"].asBoolean()) + assertFalse(json["creator"].has("dmAvailable")) + assertTrue(json["latestAudioContent"]["isPointAvailable"].asBoolean()) + assertFalse(json["latestAudioContent"].has("pointAvailable")) + assertTrue(json["latestAudioContent"]["isFirstContent"].asBoolean()) + assertFalse(json["latestAudioContent"].has("firstContent")) + assertTrue(json["latestAudioContent"]["isAdult"].asBoolean()) + assertFalse(json["latestAudioContent"].has("adult")) + assertTrue(json["series"][0]["isOriginal"].asBoolean()) + assertFalse(json["series"][0].has("original")) + } + + 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, + 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 + ), + channelDonations = listOf( + CreatorChannelDonation( + donationId = 401L, + memberId = 2L, + nickname = "fan", + profileImageUrl = "fan.png", + can = 50, + isSecret = false, + 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 + ) + ), + 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 + ) + ), + series = listOf( + CreatorChannelSeries( + seriesId = 601L, + title = "series", + coverImageUrl = "series.png", + publishedDaysOfWeek = "MON", + isComplete = false, + numberOfContent = 3, + isNew = true, + isPopular = false, + 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" + ) + ) + } +}