From 0934a7ec517bcab4ae0d6a5026d4b381452413c4 Mon Sep 17 00:00:00 2001 From: klaus Date: Sat, 13 Jun 2026 16:21:53 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator):=20=EC=B1=84=EB=84=90=20=ED=99=88?= =?UTF-8?q?=20API=20=EB=AA=A8=EB=8D=B8=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../channel/data/CreatorChannelHomeApi.kt | 15 ++ .../channel/data/CreatorChannelHomeModels.kt | 144 ++++++++++++++++++ .../channel/CreatorChannelHomeModelsTest.kt | 82 ++++++++++ 3 files changed, 241 insertions(+) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt create mode 100644 app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt new file mode 100644 index 00000000..a5491c0b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeApi.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.data + +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path + +interface CreatorChannelHomeApi { + @GET("/api/v2/creator-channels/{creatorId}/home") + fun getHome( + @Path("creatorId") creatorId: Long, + @Header("Authorization") authHeader: String + ): Single> +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt new file mode 100644 index 00000000..56eb8c5c --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt @@ -0,0 +1,144 @@ +package kr.co.vividnext.sodalive.v2.creator.channel.data + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.v2.common.CreatorActivityType + +@Keep +data class CreatorChannelHomeResponse( + @SerializedName("creator") val creator: CreatorChannelCreatorResponse, + @SerializedName("currentLive") val currentLive: CreatorChannelLiveResponse?, + @SerializedName("latestAudioContent") val latestAudioContent: CreatorChannelAudioContentResponse?, + @SerializedName("channelDonations") val channelDonations: List, + @SerializedName("notices") val notices: List, + @SerializedName("schedules") val schedules: List, + @SerializedName("audioContents") val audioContents: List, + @SerializedName("series") val series: List, + @SerializedName("communities") val communities: List, + @SerializedName("fanTalk") val fanTalk: CreatorChannelFanTalkSummaryResponse, + @SerializedName("introduce") val introduce: String, + @SerializedName("activity") val activity: CreatorChannelActivityResponse, + @SerializedName("sns") val sns: CreatorChannelSnsResponse +) + +@Keep +data class CreatorChannelCreatorResponse( + @SerializedName("creatorId") val creatorId: Long, + @SerializedName("characterId") val characterId: Long?, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String, + @SerializedName("followerCount") val followerCount: Int, + @SerializedName("isAiChatAvailable") val isAiChatAvailable: Boolean, + @SerializedName("isDmAvailable") val isDmAvailable: Boolean, + @SerializedName("isFollow") val isFollow: Boolean, + @SerializedName("isNotify") val isNotify: Boolean +) + +@Keep +data class CreatorChannelLiveResponse( + @SerializedName("liveId") val liveId: Long, + @SerializedName("title") val title: String, + @SerializedName("coverImageUrl") val coverImageUrl: String?, + @SerializedName("beginDateTimeUtc") val beginDateTimeUtc: String, + @SerializedName("price") val price: Int, + @SerializedName("isAdult") val isAdult: Boolean +) + +@Keep +data class CreatorChannelAudioContentResponse( + @SerializedName("audioContentId") val audioContentId: Long, + @SerializedName("title") val title: String, + @SerializedName("duration") val duration: String?, + @SerializedName("imageUrl") val imageUrl: String?, + @SerializedName("price") val price: Int, + @SerializedName("isPointAvailable") val isPointAvailable: Boolean, + @SerializedName("isFirstContent") val isFirstContent: Boolean, + @SerializedName("seriesName") val seriesName: String?, + @SerializedName("isOriginalSeries") val isOriginalSeries: Boolean? +) + +@Keep +data class CreatorChannelDonationResponse( + @SerializedName("donationId") val donationId: Long, + @SerializedName("memberId") val memberId: Long, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String, + @SerializedName("can") val can: Int, + @SerializedName("isSecret") val isSecret: Boolean, + @SerializedName("message") val message: String, + @SerializedName("createdAtUtc") val createdAtUtc: String +) + +@Keep +data class CreatorChannelScheduleResponse( + @SerializedName("scheduledAtUtc") val scheduledAtUtc: String, + @SerializedName("title") val title: String, + @SerializedName("type") val type: CreatorActivityType, + @SerializedName("targetId") val targetId: Long +) + +@Keep +data class CreatorChannelSeriesResponse( + @SerializedName("seriesId") val seriesId: Long, + @SerializedName("title") val title: String, + @SerializedName("coverImageUrl") val coverImageUrl: String, + @SerializedName("publishedDaysOfWeek") val publishedDaysOfWeek: String, + @SerializedName("isComplete") val isComplete: Boolean, + @SerializedName("numberOfContent") val numberOfContent: Int, + @SerializedName("isNew") val isNew: Boolean, + @SerializedName("isPopular") val isPopular: Boolean, + @SerializedName("isOriginal") val isOriginal: Boolean +) + +@Keep +data class CreatorChannelCommunityPostResponse( + @SerializedName("postId") val postId: Long, + @SerializedName("creatorId") val creatorId: Long, + @SerializedName("creatorNickname") val creatorNickname: String, + @SerializedName("creatorProfileUrl") val creatorProfileUrl: String, + @SerializedName("imageUrl") val imageUrl: String?, + @SerializedName("audioUrl") val audioUrl: String?, + @SerializedName("content") val content: String, + @SerializedName("price") val price: Int, + @SerializedName("dateUtc") val dateUtc: String, + @SerializedName("existOrdered") val existOrdered: Boolean, + @SerializedName("likeCount") val likeCount: Int, + @SerializedName("commentCount") val commentCount: Int +) + +@Keep +data class CreatorChannelFanTalkSummaryResponse( + @SerializedName("totalCount") val totalCount: Int, + @SerializedName("latestFanTalk") val latestFanTalk: CreatorChannelFanTalkResponse? +) + +@Keep +data class CreatorChannelFanTalkResponse( + @SerializedName("fanTalkId") val fanTalkId: Long, + @SerializedName("memberId") val memberId: Long, + @SerializedName("nickname") val nickname: String, + @SerializedName("profileImageUrl") val profileImageUrl: String, + @SerializedName("content") val content: String, + @SerializedName("languageCode") val languageCode: String?, + @SerializedName("createdAtUtc") val createdAtUtc: String +) + +@Keep +data class CreatorChannelActivityResponse( + @SerializedName("debutDateUtc") val debutDateUtc: String?, + @SerializedName("dDay") val dDay: String, + @SerializedName("liveCount") val liveCount: Long, + @SerializedName("liveDurationHours") val liveDurationHours: Long, + @SerializedName("liveContributorCount") val liveContributorCount: Long, + @SerializedName("audioContentCount") val audioContentCount: Long, + @SerializedName("seriesCount") val seriesCount: Long +) + +@Keep +data class CreatorChannelSnsResponse( + @SerializedName("instagramUrl") val instagramUrl: String, + @SerializedName("fancimmUrl") val fancimmUrl: String, + @SerializedName("xUrl") val xUrl: String, + @SerializedName("youtubeUrl") val youtubeUrl: String, + @SerializedName("kakaoOpenChatUrl") val kakaoOpenChatUrl: String +) diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt new file mode 100644 index 00000000..9c79a23d --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt @@ -0,0 +1,82 @@ +package kr.co.vividnext.sodalive.v2.creator.channel + +import com.google.gson.Gson +import kr.co.vividnext.sodalive.v2.common.CreatorActivityType +import kr.co.vividnext.sodalive.v2.creator.channel.data.CreatorChannelHomeResponse +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class CreatorChannelHomeModelsTest { + private val gson = Gson() + + @Test + fun `home response parses schedule type to creator activity type`() { + val response = gson.fromJson(homeJson(scheduleType = "LIVE_REPLAY"), CreatorChannelHomeResponse::class.java) + + assertEquals(CreatorActivityType.LiveReplay, response.schedules.single().type) + } + + @Test + fun `home response preserves nullable fields`() { + val response = gson.fromJson(homeJson(scheduleType = "AUDIO"), CreatorChannelHomeResponse::class.java) + + assertNull(response.creator.characterId) + assertNull(response.currentLive) + assertNull(response.latestAudioContent) + assertNull(response.fanTalk.latestFanTalk) + assertNull(response.activity.debutDateUtc) + } + + private fun homeJson(scheduleType: String): String = """ + { + "creator": { + "creatorId": 1, + "characterId": null, + "nickname": "creator", + "profileImageUrl": "https://example.com/profile.png", + "followerCount": 10, + "isAiChatAvailable": true, + "isDmAvailable": true, + "isFollow": false, + "isNotify": false + }, + "currentLive": null, + "latestAudioContent": null, + "channelDonations": [], + "notices": [], + "schedules": [ + { + "scheduledAtUtc": "2026-06-12T00:00:00Z", + "title": "schedule", + "type": "$scheduleType", + "targetId": 11 + } + ], + "audioContents": [], + "series": [], + "communities": [], + "fanTalk": { + "totalCount": 0, + "latestFanTalk": null + }, + "introduce": "intro", + "activity": { + "debutDateUtc": null, + "dDay": "D+1", + "liveCount": 1, + "liveDurationHours": 2, + "liveContributorCount": 3, + "audioContentCount": 4, + "seriesCount": 5 + }, + "sns": { + "instagramUrl": "", + "fancimmUrl": "", + "xUrl": "", + "youtubeUrl": "", + "kakaoOpenChatUrl": "" + } + } + """.trimIndent() +}