Files
sodalive-backend-spring-boot/docs/20260612_크리에이터_채널_홈_API/plan-task.md

37 KiB

크리에이터 채널 홈 API Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development 또는 superpowers:executing-plans로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.

Goal: 인증 회원이 GET /api/v2/creator-channels/{creatorId}/home으로 크리에이터 채널 홈 탭 데이터를 한 번에 조회할 수 있게 한다.

Architecture: 신규 크리에이터 채널 홈 API는 메인 페이지 홈 API와 분리해 kr.co.vividnext.sodalive.v2.creator.channel 하위에 둔다. Controller는 인증/HTTP 계약만 담당하고, application service는 섹션 조립과 정책 적용을 담당하며, persistence adapter는 기존 explorer, content, live, series, chat_character 도메인 데이터를 조회 전용 record로 반환한다. 추천 페이지에서 쓰던 RecommendedActivityTypeCreatorActivityType으로 이름을 변경해 공용 패키지로 이동하고, 추천 페이지와 크리에이터 채널 홈이 함께 사용한다.

Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring Data JPA, QueryDSL 또는 native SQL, JUnit 5, MockMvc, Gradle Wrapper


0. 구현 전 확정 사항

  • API endpoint: GET /api/v2/creator-channels/{creatorId}/home
  • API 인증 정책: 인증 회원만 조회 가능. 비회원은 기존 인증 필요 API와 동일하게 common.error.bad_credentials 계열 오류를 반환한다.
  • 신규 기능 패키지: kr.co.vividnext.sodalive.v2.creator.channel
  • 공용 활동 타입 enum: 기존 RecommendedActivityTypeCreatorActivityType으로 이름 변경하고 kr.co.vividnext.sodalive.v2.common.domain 하위로 이동한다.
  • 스케줄 타입: LIVE, AUDIO만 사용한다. 오디오 콘텐츠가 다시보기 카테고리여도 AUDIO로 내려준다.
  • 스케줄 정렬/개수: 현재 시각 이후 예약 중 오늘 날짜와 가장 근접한 3개, 예약 시각 오름차순, 같은 예약 시각이면 라이브 먼저 표시한다.
  • 신규 오디오 콘텐츠와 오디오 목록은 중복 노출하지 않는다. latestAudioContent로 내려간 가장 최신 콘텐츠를 오디오 목록에서 제외한다.
  • 채널 후원 홈 섹션은 기존 채널 후원 목록과 동일하게 이번 달 기준 최신순 8개를 내려준다.
  • 오리지널 시리즈 여부는 Series.isOriginal == true로 판단한다.
  • 화보와 상단 탭별 전체보기 API는 이번 범위에서 제외한다.

1. 파일 구조 계획

공용 enum 및 추천 페이지 영향 범위

  • Move/Rename: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendedActivityType.ktsrc/main/kotlin/kr/co/vividnext/sodalive/v2/common/domain/CreatorActivityType.kt
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt

신규 creator.channel API/application/domain/port

  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeController.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicy.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt

테스트

  • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/common/domain/CreatorActivityTypeTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
  • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt

문서 산출물

  • Modify: docs/20260612_크리에이터_채널_홈_API/plan-task.md

2. Response data class 초안

구현 시 src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt에 아래 응답 DTO를 기준으로 작성한다. 필드명은 공개 API 계약이므로 구현 중 변경이 필요하면 먼저 이 문서를 갱신한다.

package kr.co.vividnext.sodalive.v2.creator.channel.dto

import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType

data class CreatorChannelHomeResponse(
    val creator: CreatorChannelCreatorResponse,
    val currentLive: CreatorChannelLiveResponse?,
    val latestAudioContent: CreatorChannelAudioContentResponse?,
    val channelDonations: List<CreatorChannelDonationResponse>,
    val notices: List<CreatorChannelCommunityPostResponse>,
    val schedules: List<CreatorChannelScheduleResponse>,
    val audioContents: List<CreatorChannelAudioContentResponse>,
    val series: List<CreatorChannelSeriesResponse>,
    val communities: List<CreatorChannelCommunityPostResponse>,
    val fanTalk: CreatorChannelFanTalkSummaryResponse,
    val introduce: String,
    val activity: CreatorChannelActivityResponse,
    val sns: CreatorChannelSnsResponse
)

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
)

data class CreatorChannelLiveResponse(
    val liveId: Long,
    val title: String,
    val coverImageUrl: String?,
    val beginDateTimeUtc: String,
    val price: Int,
    @JsonProperty("isAdult")
    val isAdult: Boolean
)

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?
)

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
)

data class CreatorChannelScheduleResponse(
    val scheduledAtUtc: String,
    val title: String,
    val type: CreatorActivityType,
    val targetId: Long
)

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
)

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
)

data class CreatorChannelFanTalkSummaryResponse(
    val totalCount: Int,
    val latestFanTalk: CreatorChannelFanTalkResponse?
)

data class CreatorChannelFanTalkResponse(
    val fanTalkId: Long,
    val memberId: Long,
    val nickname: String,
    val profileImageUrl: String,
    val content: String,
    val languageCode: String?,
    val createdAtUtc: String
)

data class CreatorChannelActivityResponse(
    val debutDateUtc: String?,
    val dDay: String,
    val liveCount: Long,
    val liveDurationHours: Long,
    val liveContributorCount: Long,
    val audioContentCount: Long,
    val seriesCount: Long
)

data class CreatorChannelSnsResponse(
    val instagramUrl: String,
    val fancimmUrl: String,
    val xUrl: String,
    val youtubeUrl: String,
    val kakaoOpenChatUrl: String
)

Phase 1: 공용 활동 타입 정리

  • Task 1.1: RecommendedActivityType을 공용 CreatorActivityType으로 이동
    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/common/domain/CreatorActivityType.kt
      • Delete: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendedActivityType.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/common/domain/CreatorActivityTypeTest.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: CreatorActivityTypeTest를 먼저 추가해 LIVE, AUDIO, COMMUNITY, LIVE_REPLAYcode가 enum name과 같은지 검증한다. 추천 서비스/리포지토리 테스트 import를 CreatorActivityType으로 바꿔 기존 파일이 컴파일 실패하는 것을 확인한다.
    • 실패 확인:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityTypeTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
    • GREEN: enum을 공용 패키지로 이동하고 추천 페이지 코드의 import/type을 모두 CreatorActivityType으로 변경한다.
    • 통과 확인:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityTypeTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • REFACTOR: 더 이상 RecommendedActivityType 문자열이 남지 않도록 rg -n "RecommendedActivityType" src/main/kotlin src/test/kotlin로 확인한다.
    • 기대 결과: 추천 페이지 기존 동작은 유지되고, 크리에이터 채널 홈 스케줄도 같은 enum을 사용할 수 있다.

Phase 2: 응답 모델과 순수 정책

  • Task 2.1: 크리에이터 채널 홈 domain/response 모델 작성

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt
    • RED: service 테스트에서 CreatorChannelHome이 PRD 섹션 전체를 담는지 컴파일 기준으로 먼저 고정한다. 필드는 creator, currentLive, latestAudioContent, channelDonations, notices, schedules, audioContents, series, communities, fanTalk, introduce, activity, sns를 포함한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • GREEN: domain 모델과 response DTO를 추가하고, response는 domain model을 받아 API 노출 필드만 변환하는 from(home: CreatorChannelHome) factory를 둔다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • REFACTOR: API DTO는 JPA entity나 QueryDSL projection에 직접 의존하지 않도록 유지한다.
    • 기대 결과: 이후 persistence/application/controller가 공유할 응답 표면이 고정된다.
  • Task 2.2: 홈 섹션 정렬/필터 순수 정책 작성

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicy.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicyTest.kt
    • RED: 다음 정책 테스트를 작성한다.
      • 스케줄은 예약 시각 오름차순 최대 3개만 남긴다.
      • 같은 예약 시각이면 CreatorActivityType.LIVEAUDIO보다 먼저 온다.
      • 오디오 목록에서는 latestAudioContentId와 같은 콘텐츠를 제외한다.
      • 오디오 콘텐츠의 첫 공개 콘텐츠 여부는 공개 시각 오름차순, 동일 시각이면 id 오름차순으로 판정한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest
    • GREEN: limitSchedules, excludeLatestAudioContent, markFirstAudioContent 같은 순수 함수를 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest
    • REFACTOR: DB 정렬과 application 보정이 중복되더라도 최종 응답 전 정책 함수가 한 번 더 보장하도록 service에서 재사용할 수 있게 둔다.
    • 기대 결과: 날짜/중복/첫 콘텐츠 정책이 DB fixture 없이 빠르게 검증된다.

Phase 3: 조회 port와 persistence adapter

  • Task 3.1: 조회 port와 record 타입 정의

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: repository 테스트에서 port 메서드 이름을 먼저 사용해 컴파일 실패를 만든다. 최소 port 메서드는 findCreator, existsBlockedBetween, findCurrentLive, findLatestAudioContent, findChannelDonations, findCommunityPosts, findSchedules, findAudioContents, findSeries, findFanTalkSummary, findActivity, findSns로 둔다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: port record와 DefaultCreatorChannelHomeQueryRepository 골격을 추가한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: record 타입은 JPA entity를 노출하지 않는 data class로 둔다.
    • 기대 결과: application service가 의존할 조회 인터페이스가 고정된다.
  • Task 3.2: 크리에이터 기본 정보/차단/팔로우/AI 채팅/DM 조회 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 다음 repository 통합 테스트를 작성한다.
      • 활성 팔로워 수만 followerCount에 포함한다.
      • ChatCharacter.creatorMember.id == creatorId이고 활성 캐릭터가 있으면 isAiChatAvailable=true다.
      • Member.memberKind == AI_CHARACTER이면 isDmAvailable=false다.
      • 인증 회원의 CreatorFollowing.isFollow, isNotify가 응답에 반영된다.
      • 양방향 차단 관계가 있으면 existsBlockedBetween(viewerId, creatorId)=true다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: Member, CreatorFollowing, BlockMember, ChatCharacter 기반 조회를 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 프로필 이미지 URL 조합은 application/DTO에서 cloudFrontHost로 처리할지 repository에서 처리할지 한 곳으로 고정한다. 기존 v2 홈 DTO 관례처럼 path record와 URL 변환 함수를 분리하는 방식을 우선한다.
    • 기대 결과: 기본 정보와 접근 차단 판단이 기존 정책과 맞는다.
  • Task 3.3: 현재 라이브와 예약 스케줄 조회 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 다음 repository 통합 테스트를 작성한다.
      • 현재 라이브는 channelName이 있고 활성 상태이며 크리에이터가 진행 중인 라이브만 반환한다.
      • 예약 라이브는 beginDateTime > now, 활성 상태인 row만 스케줄 후보로 반환한다.
      • 예약 오디오는 releaseDate > now인 콘텐츠만 스케줄 후보로 반환한다.
      • 다시듣기 테마 예약 오디오도 스케줄 타입은 AUDIO다.
      • 같은 예약 시각이면 라이브가 오디오보다 먼저 온다.
      • 성인 라이브/오디오는 조회자의 성인 노출 정책이 false이면 제외된다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: LiveRoom, AudioContent, AudioContentTheme 조회를 구현하고 CreatorActivityType.LIVE/AUDIO를 record에 담는다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 최종 3개 제한은 repository query와 CreatorChannelHomeQueryPolicy.limitSchedules 양쪽 중복 방어를 허용하되, service에서 최종 보정한다.
    • 기대 결과: 스케줄 섹션이 PRD의 타입/정렬/개수 정책을 만족한다.
  • Task 3.4: 최신 오디오와 오디오 목록 조회 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 다음 repository 통합 테스트를 작성한다.
      • latestAudioContent는 예약 공개 전 콘텐츠를 제외하고 공개 시각 최신순 1개를 반환한다.
      • 오디오 목록은 latestAudioContent를 제외하고 최대 9개를 최신순으로 반환한다.
      • isPointAvailable, duration, cover image, price가 record에 포함된다.
      • 공개 순서상 첫 콘텐츠만 isFirstContent=true다.
      • 시리즈 콘텐츠이면 시리즈 이름과 Series.isOriginal이 포함된다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: AudioContent, SeriesContent, Series 기반 조회를 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 예약 공개 여부 조건은 releaseDate == null || releaseDate <= now처럼 기존 콘텐츠 목록 정책과 어긋나지 않도록 작성한다.
    • 기대 결과: 신규 오디오 영역과 오디오 목록이 중복 없이 구성된다.
  • Task 3.5: 채널 후원, 공지, 커뮤니티, 팬 Talk 조회 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 다음 repository 통합 테스트를 작성한다.
      • 채널 후원은 KST 기준 이번 달 범위의 최신순 8개만 반환한다.
      • 공지는 CreatorCommunity.isFixed == true, 최대 3개, 고정 시각 최신순으로 반환한다.
      • 커뮤니티는 isFixed == false, 최대 3개, 작성 시각 최신순으로 반환한다.
      • 공지와 커뮤니티의 홈 응답 게시글 요약 필드는 기존 커뮤니티 전체보기 응답과 같은 의미로 계산한다.
      • 팬 Talk는 CreatorCheers.parent == null, isActive == true인 최신 1개와 전체 개수를 반환한다.
      • 차단 관계가 있는 팬 Talk 작성자는 기존 팬 Talk 목록 정책과 동일하게 제외한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: ChannelDonationMessage, CreatorCommunity, CreatorCheers 기반 조회를 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 커뮤니티 유료 이미지/오디오 구매 여부(existOrdered)는 인증 회원 기준으로 기존 community query 의미와 동일하게 계산한다.
    • 기대 결과: 홈 후원/공지/커뮤니티/팬 Talk 섹션이 기존 전체보기 의미와 맞게 내려간다.
  • Task 3.6: 시리즈, 소개, 활동, SNS 조회 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 다음 repository 통합 테스트를 작성한다.
      • 시리즈는 최대 8개, 시리즈에 속한 공개 콘텐츠 최신 공개 시각 내림차순으로 반환한다.
      • 시리즈 응답 record에는 id, 제목, 커버 이미지, 연재 요일, 완결 여부, 콘텐츠 개수, 신규/인기 표시 정보가 포함된다.
      • 소개는 Member.introduce를 반환한다.
      • 데뷔일은 첫 라이브 시작 시각과 첫 공개 오디오 공개 시각 중 빠른 값이다.
      • 업로드 오디오 콘텐츠 개수는 예약 업로드를 제외한다.
      • 라이브 진행 횟수/누적 시간/누적 참여자는 기존 ExplorerQueryRepository 의미와 맞는다.
      • SNS는 instagramUrl, fancimmUrl, xUrl, youtubeUrl, websiteUrl을 기존 상세 API 의미로 반환한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: Series, SeriesContent, Member, LiveRoom, LiveRoomVisit, AudioContent 기반 조회를 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 기존 ExplorerService.getCreatorDetail과 의미가 같은 계산은 테스트명에 근거를 남기고, 구버전 service를 직접 호출하지 않는다.
    • 기대 결과: 활동/SNS/시리즈가 구버전 상세 의미와 신규 홈 요구를 함께 만족한다.

Phase 4: application service 조립

  • Task 4.1: CreatorChannelHomeQueryService 정상 응답 조립 구현

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHomeQueryPolicy.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt
    • RED: fake port를 사용해 모든 섹션 record를 넣고, service가 CreatorChannelHome으로 전체 섹션을 조립하는 테스트를 작성한다. latestAudioContent와 오디오 목록 중복 제거, 스케줄 최대 3개 제한, 같은 시각 라이브 우선 정렬도 service 테스트에서 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • GREEN: service에서 creator 검증, 성인 노출 정책 입력, port 호출, policy 적용, URL 변환에 필요한 host 전달을 구현한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • REFACTOR: service는 트랜잭션 경계 @Transactional(readOnly = true)를 갖고, persistence adapter의 세부 query에 의존하지 않도록 port만 사용한다.
    • 기대 결과: controller가 단일 service 호출만으로 홈 응답을 받을 수 있다.
  • Task 4.2: 예외/접근 정책 구현

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryService.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt
    • RED: 다음 service 테스트를 작성한다.
      • creatorId에 해당하는 회원이 없으면 SodaException(messageKey = "member.validation.user_not_found")를 던진다.
      • 대상 회원 role이 CREATOR가 아니면 member.validation.creator_not_found를 던진다.
      • 조회자와 크리에이터 사이에 차단 관계가 있으면 구버전 채널 접근 정책과 동일한 접근 차단 예외를 던진다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • GREEN: port의 creator/blocked 조회 결과에 따라 SodaException을 던진다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
    • REFACTOR: 차단 예외 메시지 조합에 SodaMessageSource가 필요하면 기존 ExplorerService.getCreatorDetail 패턴을 따른다.
    • 기대 결과: 신규 API 접근 정책이 구버전 채널 정책과 맞는다.

Phase 5: web API와 응답 계약

  • Task 5.1: Controller 인증 정책과 endpoint 구현

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeController.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
    • RED: MockMvc 테스트를 작성한다.
      • GET /api/v2/creator-channels/{creatorId}/home 비회원 요청은 실패한다.
      • 인증 회원 요청은 service를 호출해 ApiResponse.ok(...) 형식으로 성공 응답을 반환한다.
      • path variable creatorId가 service에 전달된다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
    • GREEN: @RestController, @RequestMapping("/api/v2/creator-channels"), @GetMapping("/{creatorId}/home") controller를 구현하고 @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") 패턴을 사용한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
    • REFACTOR: 인증 null 가드는 기존 v2 controller와 동일하게 SodaException(messageKey = "common.error.bad_credentials")를 사용한다.
    • 기대 결과: 공개 API endpoint와 인증 정책이 고정된다.
  • Task 5.2: 응답 JSON 필드 계약 고정

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/dto/CreatorChannelHomeResponse.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
    • RED: MockMvc jsonPath로 다음 최상위 필드를 검증한다.
      • creator
      • currentLive
      • latestAudioContent
      • channelDonations
      • notices
      • schedules
      • audioContents
      • series
      • communities
      • fanTalk
      • introduce
      • activity
      • sns
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
    • GREEN: response DTO factory에서 domain model을 JSON 계약에 맞게 변환한다. Boolean 필드는 isAiChatAvailable, isDmAvailable, isPointAvailable, isFirstContent, isOriginalSeries처럼 앱 계약이 읽기 쉬운 이름을 사용한다.
    • 통과 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
    • REFACTOR: nullable 섹션은 단건이면 null, 목록이면 빈 배열로 일관되게 내려준다.
    • 기대 결과: 클라이언트가 사용할 JSON 스키마가 테스트로 고정된다.

Phase 6: 통합 회귀와 문서 갱신

  • Task 6.1: 크리에이터 채널 홈 통합 시나리오 검증

    • Files:
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt
    • RED: 현실적인 fixture로 한 크리에이터에 라이브, 예약 라이브, 예약 오디오, 최신 오디오, 오디오 목록, 시리즈, 공지, 커뮤니티, 후원, 팬 Talk, SNS, 활동 데이터를 넣고 홈 응답 핵심 필드가 모두 내려오는 통합 테스트를 작성한다.
    • 실패 확인:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • GREEN: 누락된 mapping이나 query 조건을 최소 수정한다.
    • 통과 확인:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
    • REFACTOR: 테스트 fixture helper가 과도하게 길어지면 같은 테스트 파일 내부 private helper로만 분리하고 운영 코드에는 테스트 편의를 위한 API를 추가하지 않는다.
    • 기대 결과: PRD의 홈 전체 섹션이 한 요청에서 조립되는지 확인된다.
  • Task 6.2: 추천 페이지 enum rename 회귀 확인

    • Files:
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: 해당 없음. TDD 예외 사유: Task 1.1에서 이미 RED/GREEN으로 enum rename을 처리했고, 이 task는 영향 범위 회귀 실행이다.
    • 대체 검증 방법:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: 실패가 있으면 import/type mismatch 또는 enum value mapping만 최소 수정한다.
    • REFACTOR: rg -n "RecommendedActivityType" src/main/kotlin src/test/kotlin 결과가 없어야 한다.
    • 기대 결과: 추천 페이지 최근 활동 타입 분류가 기존과 동일하게 유지된다.
  • Task 6.3: 전체 검증 및 계획 문서 검증 기록 누적

    • Files:
      • Modify: docs/20260612_크리에이터_채널_홈_API/plan-task.md
    • RED: 테스트 작성 예외. TDD 예외 사유: 검증 기록 문서화 task다.
    • 대체 검증 방법:
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityTypeTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
      • ./gradlew ktlintCheck
    • GREEN: 모든 명령 결과를 아래 검증 기록에 누적한다.
    • REFACTOR: 실패한 검증이 있으면 해당 phase/task로 돌아가 plan-task 체크박스를 완료 처리하지 않는다.
    • 기대 결과: 구현 완료 시 어떤 검증으로 완료 판단했는지 문서에 남는다.

구현 중 주의사항

  • 기존 ExplorerService.getCreatorDetail의 활동/SNS 의미를 유지하되, 신규 API에서 구버전 service를 직접 호출하지 않는다.
  • 메인 페이지 홈 패키지(kr.co.vividnext.sodalive.v2.api.home)와 크리에이터 채널 홈 패키지를 섞지 않는다.
  • 공개 시간은 UTC ISO-8601 문자열로 내려주고, 앱 표시 포맷은 서버에서 조합하지 않는다.
  • 목록 섹션은 데이터가 없으면 빈 배열, 단건 섹션은 없으면 null로 내려준다.
  • 신규 API 공개 스키마 변경은 이 문서의 task 범위 안에서만 수행한다.

검증 기록

  • 2026-06-12: plan-task 문서 생성 전 docs/agent-guides/코드스타일.md, docs/agent-guides/테스트스타일.md, docs/agent-guides/실행명령어.md, 기존 docs/20260608_크리에이터_랭킹/plan-task.md, docs/20260612_크리에이터_채널_홈_API/prd.md를 확인했다.
  • 2026-06-12: Phase 1 RED 확인 - ./gradlew test --tests kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityTypeTest 실행 시 Unresolved reference: CreatorActivityType 컴파일 오류를 확인했다.
  • 2026-06-12: Phase 1 GREEN/회귀 확인 - ./gradlew test --tests kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityTypeTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest 통과.
  • 2026-06-12: Phase 1 정리 확인 - rg -n "RecommendedActivityType" src/main/kotlin src/test/kotlin 결과 없음, ./gradlew ktlintCheck 통과.
  • 2026-06-12: Phase 2 RED 확인 - ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest 실행 시 Unresolved reference: CreatorChannelHome, Unresolved reference: CreatorChannelHomeResponse, Unresolved reference: CreatorChannelHomeQueryPolicy 컴파일 오류를 확인했다.
  • 2026-06-12: Phase 2 GREEN 확인 - ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest 통과.
  • 2026-06-12: Phase 2 정리 확인 - ./gradlew ktlintCheck 통과.
  • 2026-06-12: Phase 2 리뷰 보정 RED 확인 - 오디오 콘텐츠 isAdult와 스케줄 현재시각 필터 테스트 추가 후 ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest 실행 시 Unresolved reference: isAdult, Too many arguments for limitSchedules 컴파일 오류를 확인했다.
  • 2026-06-12: Phase 2 리뷰 보정 GREEN 확인 - CreatorChannelAudioContent/CreatorChannelAudioContentResponseisAdult를 추가하고 CreatorChannelHomeQueryPolicy.limitSchedules(schedules, now)scheduledAt > now만 남기도록 수정한 뒤 ./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest 통과.