85 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로 반환한다. 추천 페이지에서 쓰던 RecommendedActivityType은 CreatorActivityType으로 이름을 변경해 공용 패키지로 이동하고, 추천 페이지와 크리에이터 채널 홈이 함께 사용한다.
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: 기존
RecommendedActivityType을CreatorActivityType으로 이름 변경하고kr.co.vividnext.sodalive.v2.common.domain하위로 이동한다. - 스케줄 타입:
LIVE,AUDIO만 사용한다. 오디오 콘텐츠가다시보기카테고리여도AUDIO로 내려준다. - 스케줄 정렬/개수: 현재 시각 이후 예약 중 오늘 날짜와 가장 근접한 3개, 예약 시각 오름차순, 같은 예약 시각이면 라이브 먼저 표시한다.
- 스케줄 성인 노출 정책: repository query에서 조회자의 성인 노출 정책을 먼저 반영하고, service 최종 조합에서도 내부 스케줄 후보의
isAdult로 한 번 더 보정한다. 공개 스케줄 응답에는isAdult를 노출하지 않는다. - 현재 라이브와 예약 라이브 스케줄은 기존 라이브 목록과 동일하게 성별 제한(
LiveRoom.genderRestriction)과 크리에이터 입장 제한(LiveRoom.isAvailableJoinCreator)을 반영한다. application service는 조회자의Auth.gender가 있으면 이를 우선하고, 없으면Member.gender를 사용하는effectiveViewerGender를 산출해 query port에 넘긴다. - 신규 오디오 콘텐츠와 오디오 목록은 중복 노출하지 않는다.
latestAudioContent로 내려간 가장 최신 콘텐츠를 오디오 목록에서 제외한다. - 채널 후원 홈 섹션은 기존 채널 후원 목록과 동일하게 이번 달 기준 최신순 8개를 내려준다. 응답 메시지는 기본 문구를 조합하지 않고 후원자가 입력한 추가 메시지만 내려준다.
- 오리지널 시리즈 여부는
Series.isOriginal == true로 판단한다. - 화보와 상단 탭별 전체보기 API는 이번 범위에서 제외한다.
1. 파일 구조 계획
공용 enum 및 추천 페이지 영향 범위
- Move/Rename:
src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendedActivityType.kt→src/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 characterId: 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 nickname: String,
val profileImageUrl: String,
val can: Int,
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 numberOfContent: Int,
@JsonProperty("isNew")
val isNew: 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
)
스케줄 성인 여부는 service 최종 보정에 필요한 내부 domain/record 필드로만 유지하고, 위 공개 응답 DTO에는 포함하지 않는다.
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
- Create:
- RED:
CreatorActivityTypeTest를 먼저 추가해LIVE,AUDIO,COMMUNITY,LIVE_REPLAY의code가 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을 사용할 수 있다.
- Files:
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
- Create:
- 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가 공유할 응답 표면이 고정된다.
- Files:
-
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
- Create:
- RED: 다음 정책 테스트를 작성한다.
- 스케줄은 예약 시각 오름차순 최대 3개만 남긴다.
- 스케줄은 현재 시각 이후 예약만 남긴다.
- 같은 예약 시각이면
CreatorActivityType.LIVE가AUDIO보다 먼저 온다. - 조회자의 성인 노출 정책이 false이면 성인 스케줄을 제외한다.
- 오디오 목록에서는
latestAudioContentId와 같은 콘텐츠를 제외한다. - 오디오 콘텐츠의 첫 공개 콘텐츠 여부는 공개 시각 오름차순, 동일 시각이면 id 오름차순으로 판정한다.
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest - GREEN:
limitSchedules(schedules, now, canViewAdultContent),excludeLatestAudioContent,markFirstAudioContent같은 순수 함수를 구현한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest - REFACTOR: DB 정렬과 application 보정이 중복되더라도 최종 응답 전 정책 함수가 한 번 더 보장하도록 service에서 재사용할 수 있게 둔다.
- 기대 결과: 날짜/중복/첫 콘텐츠 정책이 DB fixture 없이 빠르게 검증된다.
- Files:
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
- Create:
- 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가 의존할 조회 인터페이스가 고정된다.
- Files:
-
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
- Modify:
- 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 변환 함수를 분리하는 방식을 우선한다.
- 기대 결과: 기본 정보와 접근 차단 판단이 기존 정책과 맞는다.
- Files:
-
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
- Modify:
- RED: 다음 repository 통합 테스트를 작성한다.
- 현재 라이브는
channelName이 있고 활성 상태이며 크리에이터가 진행 중인 라이브만 반환한다. - 예약 라이브는
beginDateTime > now, 활성 상태인 row만 스케줄 후보로 반환한다. - 예약 오디오는
duration != null,releaseDate != null,releaseDate > now이면isActive상태와 관계없이 스케줄 후보로 반환한다. - 다시듣기 테마 예약 오디오도 스케줄 타입은
AUDIO다. - 같은 예약 시각이면 라이브가 오디오보다 먼저 온다.
- 성인 라이브/오디오는 조회자의 성인 노출 정책이 false이면 제외된다.
- 현재 라이브와 예약 라이브 스케줄은 query port의
effectiveViewerGender,viewerId,isViewerCreator입력으로 기존 라이브 목록의 성별 제한과 크리에이터 입장 제한을 반영한다. - service 최종 보정을 위해 스케줄 후보 record에는
isAdult가 포함된다.
- 현재 라이브는
- 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN:
LiveRoom,AudioContent,AudioContentTheme조회를 구현하고CreatorActivityType.LIVE/AUDIO와isAdult를 record에 담는다. 예약 오디오는isActive가 아니라duration과 미래releaseDate로 판정한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: 최종 3개 제한은 repository query와
CreatorChannelHomeQueryPolicy.limitSchedules양쪽 중복 방어를 허용하되, service에서 최종 보정한다. - 기대 결과: 스케줄 섹션이 PRD의 타입/정렬/개수 정책을 만족한다.
- Files:
-
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
- Modify:
- RED: 다음 repository 통합 테스트를 작성한다.
latestAudioContent는 예약 공개 전 콘텐츠를 제외하고 공개 시각 최신순 1개를 반환한다.- 오디오 목록은
latestAudioContent를 제외하고 최대 9개를 최신순으로 반환한다. releaseDate == null인 오디오는 최신/목록/첫 콘텐츠 판정에서 제외한다.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로 작성한다.releaseDate == null은 삭제/미공개 데이터로 보고 제외한다. - 기대 결과: 신규 오디오 영역과 오디오 목록이 중복 없이 구성된다.
- Files:
-
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
- Modify:
- RED: 다음 repository 통합 테스트를 작성한다.
- 채널 후원은 KST 기준 이번 달 범위의 최신순 8개만 반환한다.
- 채널 후원 응답에는 후원자 닉네임, 프로필 이미지, 후원 can, 후원자가 입력한 추가 메시지, UTC 생성 시각만 포함한다.
- 채널 후원
message는 기본 문구("%s캔을 비밀후원하셨습니다.","%s캔을 후원하셨습니다.")를 조합하지 않고additionalMessage만 반환한다. - 공지는
CreatorCommunity.isFixed == true,fixedAt != null인 데이터로 보고 최대 3개, 고정 시각 최신순으로 반환한다. - 커뮤니티는
isFixed == false, 최대 3개, 작성 시각 최신순으로 반환한다. - 성인 커뮤니티 글은 구매 여부와 무관하게 조회자의 성인 콘텐츠 노출 정책이 false이면 제외한다.
- 공지와 커뮤니티의 홈 응답 게시글 요약 필드는 기존 커뮤니티 전체보기 응답과 같은 의미로 계산한다.
- 팬 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 섹션이 기존 전체보기 의미와 맞게 내려간다.
- Files:
-
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
- Modify:
- RED: 다음 repository 통합 테스트를 작성한다.
- 시리즈는 최대 8개, 시리즈에 속한 공개 콘텐츠 최신 공개 시각 내림차순으로 반환한다.
- 시리즈 응답 record에는 id, 제목, 커버 이미지, 콘텐츠 개수, 신규 표시, 오리지널 시리즈 여부가 포함된다.
- 소개는
Member.introduce를 반환한다. - 데뷔일은 첫 라이브 시작 시각과 오디오 데뷔 후보 시각 중 빠른 값이다.
- 오디오 데뷔 후보는 업로드 순서 기준 첫 3개만 보고, 1~2번째 삭제 오디오는 건너뛰며, 3번째 삭제 오디오는 4번째로 넘어가지 않고 해당
createdAt을 후보로 쓴다. - 업로드 오디오 콘텐츠 개수는 예약 업로드를 제외한다.
- 라이브 진행 횟수/누적 시간/누적 참여자는 기존
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/시리즈가 구버전 상세 의미와 신규 홈 요구를 함께 만족한다.
- Files:
-
Task 3.7:
findCreator기본 정보 조회를 count/exists 중심으로 개선- 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
- Modify:
- RED: 기존
shouldFindCreatorProfileWithRelationshipFlags,shouldNotExposeNotifyForInactiveFollowing테스트를 유지하고, 활성 팔로워가 여러 명이어도followerCount결과가 정확한 회귀 테스트를 보강한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN:
findCreator에서 팔로워 수는select(id).fetch().size대신 DBcount()로 계산하고, AI 채팅 가능 여부는 필요한 id만 조회하는exists성격의 쿼리로 유지한다. creator 기본 정보처럼 record 생성자 인자로 바로 매핑할 수 있는 조회는 필요한 컬럼을Tuple로 가져와 재조립하지 않고 QueryDSLProjections.constructor로 record를 생성한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR:
CreatorChannelCreatorRecord조립을 위해 불필요한Memberentity 전체를 로딩하지 않는지 코드로 확인한다. QueryDSL@QueryProjection은 port record가 QueryDSL에 의존하게 되므로 사용하지 않는다. - 기대 결과:
findCreator가 기존 응답 의미를 유지하면서 대량 follower row를 애플리케이션 메모리로 가져오지 않는다.
- Files:
-
Task 3.8: 단건/단순 목록 조회를 필요한 컬럼 projection으로 개선
- 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
- Modify:
- RED:
findCurrentLive,findLatestAudioContent,findAudioContents,findChannelDonations,findSns의 기존 repository 테스트를 유지해 projection 변경 전후 응답 값이 같음을 고정한다. 단, 채널 후원 공개 응답 계약 변경은Task 3.15를 우선한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN: 위 메서드들이
selectFrom(entity).fetch().map { ... }로 모든 컬럼을 가져오지 않도록 필요한 컬럼만 조회한다. record 생성자와 조회 컬럼이 1:1로 대응되는 부분은Tuple조회 후 수동 재조립하지 않고 QueryDSLProjections.constructor로 바로 record를 생성한다.findAudioContents는 목록 row마다 series/first content 조회가 반복되지 않도록 시리즈 정보와 첫 콘텐츠 id를 bulk 또는 별도 1회 쿼리로 계산한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: port record에는 QueryDSL annotation을 붙이지 않고, QueryDSL 의존은 persistence adapter 내부에만 둔다. 계산/병합/그룹핑 때문에 constructor projection으로 표현하기 어려운 부분에만 최소 범위로
Tuple또는 별도 bulk map 조립을 허용한다. - 기대 결과: 현재 라이브, 오디오, 후원, SNS 조회가 필요한 컬럼만 읽고 projection 변경 자체로는 응답 값이 바뀌지 않는다. 채널 후원 공개 응답 계약 변경은
Task 3.15에서 별도로 반영한다.
- Files:
-
Task 3.9:
findSchedules스케줄 후보 조회를 projection으로 개선- 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
- Modify:
- RED: 기존 스케줄 테스트에 live/audio 후보가 여러 개 있을 때 예약 시각 오름차순, 같은 시각 live 우선,
limit적용 결과가 유지되는 케이스를 보강한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN: 예약 라이브와 예약 오디오 조회에서 entity 전체를 가져오지 않고
scheduledAt,title,targetId,isAdult등 record 구성에 필요한 컬럼만 조회한다. live/audio 후보 각각이 record 생성자 인자로 바로 매핑되는 경우 QueryDSLProjections.constructor를 사용하고, 후보 병합 이후 정렬과limit는 기존 정책과 동일하게 유지한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: 스케줄 공개 응답에는
isAdult가 노출되지 않고 내부 record에만 남는 구조를 유지한다. - 기대 결과: 스케줄 후보 조회가 불필요한
LiveRoom,AudioContent전체 컬럼을 읽지 않는다.
- Files:
-
Task 3.10:
findCommunityPosts게시글 요약 조회의 반복 쿼리와 전체 컬럼 조회 개선- 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
- Modify:
- RED: 공지/커뮤니티 테스트에 여러 게시글을 넣고
existOrdered,likeCount,commentCount, 성인 필터,fixedAt != null공지 조건이 기존 의미대로 유지되는지 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN: 게시글 본문 조회는 필요한 컬럼만 조회하되, 본문 record로 바로 매핑 가능한 부분은
Tuple로 받은 뒤 재조립하지 않고 QueryDSLProjections.constructor를 사용한다. 게시글별existsCommunityOrder,countCommunityLikes,countCommunityComments반복 호출은 subquery 또는 게시글 id 목록 기반 bulk 조회로 줄인다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: 기존 커뮤니티 전체보기의 구매 여부, 댓글 수, 댓글 불가 게시글
commentCount = 0의미가 깨지지 않도록 테스트명을 근거로 남긴다. - 기대 결과: 홈 공지/커뮤니티 최대 3개 조회가 게시글 수에 비례해 불필요한 추가 쿼리를 만들지 않는다.
- Files:
-
Task 3.11:
findSeries시리즈 조회의 전체 entity 로딩과 시리즈별 콘텐츠 조회 개선- 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
- Modify:
- RED: 시리즈 테스트에 여러 시리즈와 콘텐츠를 넣고 최신 공개 시각 정렬, 콘텐츠 개수, 신규 표시, 오리지널 시리즈 여부, 성인/콘텐츠 타입/차단 정책,
releaseDate == null콘텐츠 제외가 유지되는지 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN:
findSeries가selectFrom(series).fetch()뒤 시리즈마다publishedSeriesContents,hasNewSeriesContent를 호출하지 않도록SeriesContent/AudioContentjoin, group by, aggregate 기반 조회로 record를 만든다. aggregate 결과가 record 생성자 인자로 바로 대응되는 부분은 QueryDSLProjections.constructor를 사용하고, 후처리 집계가 필요한 경우에만 최소 범위로 bulk map 조립을 허용한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: 홈 시리즈 공개 응답에
publishedDaysOfWeek,isComplete,isPopular가 다시 포함되지 않도록 DTO와 mapper를 확인한다. - 기대 결과: 시리즈 최대 8개 조회가 시리즈 수만큼 콘텐츠 조회를 반복하지 않고 기존 시리즈 목록 의미를 유지한다.
- Files:
-
Task 3.12:
findFanTalkSummary전체 row fetch 제거- 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
- Modify:
- RED: 팬 Talk 테스트에 활성 최상위 글 여러 개와 비활성/답글/차단 작성자 글을 넣고
totalCount와 최신 1건이 기존 정책대로 계산되는지 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - GREEN:
findFanTalkSummary에서 조건에 맞는 전체CreatorCheersrow를fetch()하지 않고,totalCount는 DBcount()로 계산하며latestFanTalk는 필요한 컬럼만orderBy(...).limit(1)로 조회한다.latestFanTalk처럼 생성자 인자로 바로 매핑 가능한 record는 QueryDSLProjections.constructor로 생성한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest - REFACTOR: 차단 필터는 기존 팬 Talk 목록 정책과 동일하게 조회자 기준 양방향 차단만 반영한다.
- 기대 결과: 팬 Talk가 많은 크리에이터도 홈 조회에서 전체 팬 Talk row를 애플리케이션 메모리로 가져오지 않는다.
- Files:
-
Task 3.13: 크리에이터 기본 응답에
characterId추가- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt - 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/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
- Modify:
- RED: 활성
ChatCharacter가 있는 크리에이터는creator.characterId가 해당 캐릭터 ID로 내려가고, 활성 캐릭터가 없으면null로 내려가는 repository/service/controller 응답 계약 테스트를 먼저 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- GREEN: creator record/domain/response DTO에 nullable
characterId를 추가하고,findCreator가 활성ChatCharacterID를 projection으로 함께 조회하도록 구현한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- REFACTOR:
isAiChatAvailable은characterId != null기준과 의미가 어긋나지 않도록 한 곳에서 계산한다. - 기대 결과: 클라이언트가 AI 채팅 진입에 필요한
characterId를 홈 응답에서 바로 사용할 수 있다.
- Files:
-
Task 3.14: 시리즈 응답에서 연재 요일/완결/인기 필드 제거
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt - 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/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
- Modify:
- RED: 시리즈 JSON 응답에
publishedDaysOfWeek,isComplete,isPopular가 없고,seriesId,title,coverImageUrl,numberOfContent,isNew,isOriginal만 필요한 계약으로 내려가는 테스트를 먼저 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- GREEN: series record/domain/response DTO와 mapper에서
publishedDaysOfWeek,isComplete,isPopular를 제거하고, repository projection도 더 이상 해당 응답 필드 조립을 위해 조회하지 않도록 정리한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- REFACTOR:
rg -n "publishedDaysOfWeek|isComplete|isPopular" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel로 신규 홈 API 경계에 제거 대상 필드가 남지 않았는지 확인한다. - 기대 결과: 홈 시리즈 응답이 클라이언트 요청 계약에 맞게 축소된다.
- Files:
-
Task 3.15: 채널 후원 응답에서 기본 후원 문구와 내부 식별 필드 제거
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/port/out/CreatorChannelHomeQueryPort.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/domain/CreatorChannelHome.kt - 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/out/persistence/DefaultCreatorChannelHomeQueryRepositoryTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/application/CreatorChannelHomeQueryServiceTest.kt - Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/in/web/CreatorChannelHomeControllerTest.kt
- Modify:
- RED: 채널 후원 JSON 응답에
donationId,memberId,isSecret이 없고,nickname,profileImageUrl,can,message,createdAtUtc만 내려가는 테스트를 먼저 추가한다. repository 테스트에서는message가 기본 문구 조합 없이ChannelDonationMessage.additionalMessage값만 반환되고, 추가 메시지가 없으면 빈 문자열이 되는지 검증한다. 비밀 후원은 기존 정책처럼 후원자 본인과 받은 크리에이터만 조회 가능하고, 제3자는 같은 달 비밀 후원을 조회할 수 없는 negative case를 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- GREEN: donation record/domain/response DTO와 mapper에서
donationId,memberId,isSecret공개 응답 필드를 제거하고, repository는additionalMessage.orEmpty()를message로 매핑한다. 비밀 후원 노출 여부와 이번 달 최신순 8개 조회 정책은 유지하되,isSecret은 repository 조회 조건에만 사용하고 공개 응답에는 포함하지 않는다. record 생성자 인자로 바로 매핑되는 조회는 QueryDSLProjections.constructor를 사용한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.in.web.CreatorChannelHomeControllerTest
- REFACTOR:
SodaMessageSource/LangContext가 채널 후원 메시지 조합만을 위해 주입되어 있다면 제거한다.rg -n "donationId|memberId|isSecret|비밀후원|후원하셨습니다|SodaMessageSource|LangContext" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel로 신규 홈 API 경계에 제거 대상 응답 필드나 기본 문구 조합이 남지 않았는지 확인한다. - 기대 결과: 홈 채널 후원 섹션이 변경된 클라이언트 계약에 맞게 추가 메시지 중심의 최소 응답만 내려준다.
- Files:
-
Task 3.16: 구매한 삭제 유료 커뮤니티 게시글 조회 정책 보정
- 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
- Modify:
- RED: 유료 커뮤니티 게시글을 구매한 조회자는 크리에이터가 이후 삭제해
CreatorCommunity.isActive == false가 되어도 해당 게시글을 조회하고, 비구매자는 조회하지 못하는 repository 테스트를 먼저 추가한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon
- GREEN:
findCommunityPosts의 구현 방식은 게시글 후보 조회 후 구매 여부/좋아요 수/댓글 수를 bulk 조회하는 기존 구조를 유지한다. 게시글 후보 조건만 기존 커뮤니티 전체보기 의미에 맞춰CreatorCommunity.isActive == true또는 인증 조회자가 환불되지 않은PAID_COMMUNITY_POST구매 이력이 있는 경우로 보정한다. - 통과 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon
- REFACTOR: 좋아요/댓글/구매 여부 조회를
leftJoin하나로 합치지 않고, 현재의 id 목록 기반 bulk 조회 구조를 유지한다. - 기대 결과: 구매자는 삭제된 유료 게시글도 기존 전체보기 의미와 동일하게 접근할 수 있고, 비구매자는 삭제된 게시글을 조회하지 못한다.
- Files:
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
- Create:
- RED: fake port를 사용해 모든 섹션 record를 넣고, service가
CreatorChannelHome으로 전체 섹션을 조립하는 테스트를 작성한다.latestAudioContent와 오디오 목록 중복 제거, 스케줄 최대 3개 제한, 같은 시각 라이브 우선 정렬, 성인 스케줄 최종 제외도 service 테스트에서 검증한다. - RED: 조회자에게
Auth.gender가 있으면Member.gender보다Auth.gender를 우선해effectiveViewerGender를 산출하고,viewerId,isViewerCreator,effectiveViewerGender가findCurrentLive와findSchedules에 전달되는지 fake port로 검증한다. - 실패 확인:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest - GREEN: service에서 creator 검증, 성인 노출 정책 입력, 조회자 라이브 정책 컨텍스트(
viewerId,isViewerCreator,effectiveViewerGender) 산출, 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 호출만으로 홈 응답을 받을 수 있다.
- Files:
-
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
- Modify:
- RED: 다음 service 테스트를 작성한다.
- creatorId에 해당하는 회원이 없으면
SodaException(messageKey = "member.validation.user_not_found")를 던진다. - 대상 회원 role이
CREATOR가 아니면member.validation.creator_not_found를 던진다. - 조회자와 크리에이터 사이에 차단 관계가 있으면 구버전 채널 접근 정책과 동일한 접근 차단 예외를 던진다.
- creatorId에 해당하는 회원이 없으면
- 실패 확인:
./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 접근 정책이 구버전 채널 정책과 맞는다.
- Files:
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
- Create:
- 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와 인증 정책이 고정된다.
- Files:
-
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
- Modify:
- RED: MockMvc
jsonPath로 다음 최상위 필드를 검증한다.creatorcurrentLivelatestAudioContentchannelDonationsnoticesschedulesaudioContentsseriescommunitiesfanTalkintroduceactivitysns
- 실패 확인:
./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 스키마가 테스트로 고정된다.
- Files:
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
- Test:
- 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의 홈 전체 섹션이 한 요청에서 조립되는지 확인된다.
- Files:
-
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
- Modify:
- 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결과가 없어야 한다. - 기대 결과: 추천 페이지 최근 활동 타입 분류가 기존과 동일하게 유지된다.
- Files:
-
Task 6.3: 전체 검증 및 계획 문서 검증 기록 누적
- Files:
- Modify:
docs/20260612_크리에이터_채널_홈_API/plan-task.md
- Modify:
- 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 체크박스를 완료 처리하지 않는다.
- 기대 결과: 구현 완료 시 어떤 검증으로 완료 판단했는지 문서에 남는다.
- Files:
구현 중 주의사항
- 기존
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/CreatorChannelAudioContentResponse에isAdult를 추가하고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통과. - 2026-06-12: 스케줄 성인 노출 정책 보강 - PRD와 plan-task에 repository query 1차 필터 + service 최종 보정 방식을 명시하고, 내부
CreatorChannelSchedule.isAdult와CreatorChannelHomeQueryPolicy.limitSchedules(schedules, now, canViewAdultContent)를 반영했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 RED/GREEN 확인 -
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest실행 시Unresolved reference: DefaultCreatorChannelHomeQueryRepository컴파일 오류를 확인한 뒤 조회 port/record와DefaultCreatorChannelHomeQueryRepository를 구현해 통과. 추가로./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 예약 라이브 조건 보강 - 기존
LiveRoomRepository.getLiveRoomListReservationWithoutDate의미에 맞춰 예약 라이브 스케줄은channelName이 비어 있는 row만 조회하도록 테스트를 보강했다. 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest가 스케줄 assertion에서 실패하는 것을 확인했고,findScheduleslive 조건을channelName is null or empty로 수정한 뒤./gradlew clean test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 리뷰 보정 RED/GREEN 확인 - 비활성 팔로우 알림,
releaseDate == null공개 오디오, KST 월 경계/크리에이터 비밀 후원 열람, 팬 Talk 작성자→조회자 차단, 미래 라이브 데뷔일 제외 테스트를 추가한 뒤DefaultCreatorChannelHomeQueryRepositoryTest에서 5개 실패를 확인했다. 조회 조건을 기존 repository/service 의미에 맞게 보정한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 Task 3.7~3.12 RED 확인 -
findCreator다중 활성 팔로워 count 회귀 테스트와 projection/bulk 구조 회귀 테스트를 추가한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest실행 시shouldUseProjectionAndBulkQueriesForPhaseThreeOptimizedMethods가 기존selectFrom(...),fetch().size, per-row helper 사용을 잡아 실패하는 것을 확인했다. - 2026-06-12: Phase 3 Task 3.7~3.12 GREEN 확인 -
findCreator,findCurrentLive,findLatestAudioContent,findAudioContents,findChannelDonations,findSns,findSchedules,findCommunityPosts,findSeries,findFanTalkSummary를 필요한 컬럼 projection, DB count, id 목록 기반 bulk 조회로 개선하고findActivity의 id fetch count도 DB count로 보정했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest통과. - 2026-06-12: Phase 3 Task 3.7~3.12 회귀/정리 확인 -
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest통과,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 Task 3.9/3.11 리뷰 보정 확인 - 스케줄 후보 병합 후
limit적용 테스트와select(series)entity fetch 금지 테스트를 추가해 RED를 확인했고,findSeries를 tuple projection과publishedDaysOfWeekid 목록 기반 bulk join으로 변경했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과.rg -n "\.select\(series\)|selectFrom\(series\)|publishedSeriesContents\(|hasNewSeriesContent\(" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/adapter/out/persistence/DefaultCreatorChannelHomeQueryRepository.kt결과 없음. - 2026-06-12: Phase 3 추가 리뷰 보정 RED/GREEN 확인 - 커뮤니티 성인 필터/작성자 본인 구매 처리, 시리즈 성인·콘텐츠 타입·차단·신규 표시 정책,
releaseDate == null오디오의createdAt정렬 fallback, 팬 Talk 조회자 기준 차단 정책 테스트를 추가한 뒤 port/repository signature와 query 조건을 보정했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 reviewer follow-up 보정 - 리뷰에서 지적된 보이는 첫 오디오 판정의 성인 정책 반영 누락과 시리즈 콘텐츠 후보의
duration is not null조건 누락 가능성을 테스트로 고정했다. 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest에서 2개 실패를 확인했고,firstAudioContentId에canViewAdultContent를 반영하고 시리즈 콘텐츠/신규 판정에duration.isNotNull을 추가한 뒤./gradlew clean test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 리뷰 결과 보정 RED/GREEN 확인 - 커뮤니티 댓글 수의 기존 목록 의미(parent null/양방향 차단/비밀 댓글 권한), 채널 후원 메시지 기본 문구+캔 포맷+추가 메시지 조합, 단독 오디오 duration null 제외 정책을 repository 테스트로 추가했다. 보강 직후
DefaultCreatorChannelHomeQueryRepositoryTest에서 3개 실패(commentCount 5 -> 1, null duration 최신 오디오 선택, 후원 메시지 additionalMessage 단독 반환)를 확인했고, query/메시지 조합을 보정한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest통과. - 2026-06-12: Phase 3 리뷰어 게이트 보정 - Context mining 리뷰에서 지적된
isCommentAvailable == false게시글 댓글 수와 채널 후원 메시지 다국어 메시지 소스 의미를 추가 테스트로 고정했다. 보강 직후 constructor mismatch RED를 확인했고, repository에SodaMessageSource/LangContext기반 메시지 조합과 댓글 불가 게시글commentCount = 0처리를 반영한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest통과. - 2026-06-12: Phase 3 문서 정합성 보정 - 성인 커뮤니티 글은 구매 여부와 무관하게 조회자의 성인 콘텐츠 노출 정책이 우선한다는 점과
isFixed == true게시글은fixedAt != null인 데이터로 본다는 전제를 PRD/plan-task에 명시했다. - 2026-06-12: Phase 3 리뷰 결과 보정 -
isFixed == true이지만fixedAt == null인 공지 fixture와 팬 Talk 활성 최상위 글 전체 개수/최신 1건 테스트를 추가했다. 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest에서 공지 assertion 실패를 확인했고, 공지 조회에fixedAt.isNotNull조건을 추가하고 팬 Talk 요약을count쿼리와 최신limit(1).fetchFirst()쿼리로 분리한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest,./gradlew ktlintCheck통과. - 2026-06-12: Phase 3 조회 효율 개선 Task 문서화 -
findCreator의 count/exists 중심 개선과 entity 전체 컬럼 조회 후 record mapping을 줄이는 작업을Task 3.7~Task 3.12로 분리해 plan-task에 추가했다. QueryDSL@QueryProjection은 사용하지 않고, persistence adapter 내부 projection/tuple/bulk/count 쿼리로 개선하는 방향을 명시했다. - 2026-06-12: Phase 3 조회 효율 개선 Task 문서화 검증 - 문서 변경 후
./gradlew tasks --all을 실행해 Gradle 명령 유효성을 확인했다. sandbox 권한에서는~/.gradlelock 파일 접근 제한으로 실패했고, 권한 승격 후BUILD SUCCESSFUL을 확인했다. - 2026-06-12: 응답 계약 변경 문서화 - PRD와 plan-task에
creator.characterId추가, 시리즈 응답의publishedDaysOfWeek/isComplete/isPopular제거를 반영했다. Phase 3의 미완료 항목Task 3.7~Task 3.12체크를 해제하고, 변경 반영 구현 항목을Task 3.13,Task 3.14로 추가했다. - 2026-06-12: 응답 계약 변경 문서 검증 -
./gradlew tasks --all을 실행해 Gradle 명령 유효성을 확인했다. sandbox 권한에서는~/.gradlelock 파일 접근 제한으로 실패했고, 권한 승격 후BUILD SUCCESSFUL을 확인했다. - 2026-06-12: Phase 3 projection 구현 방향 문서화 -
Task 3.7~Task 3.12에서 record 생성자 인자로 바로 매핑 가능한 조회는Tuple조회 후 수동 재조립하지 않고 QueryDSLProjections.constructor를 사용하도록 task 문구를 보정했다. - 2026-06-12: Phase 3 projection 구현 방향 문서 검증 -
./gradlew tasks --all을 실행해 Gradle 명령 유효성을 확인했다. sandbox 권한에서는~/.gradlelock 파일 접근 제한으로 실패했고, 권한 승격 후BUILD SUCCESSFUL을 확인했다. - 2026-06-13: Phase 3 Task 3.13/3.14 RED 확인 -
characterId와 시리즈 응답 축소 계약 테스트를 먼저 추가한 뒤./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실행 시Unresolved reference: characterId,No value passed for parameter 'publishedDaysOfWeek'/'isComplete'/'isPopular'컴파일 오류를 확인했다. - 2026-06-13: Phase 3 Task 3.13/3.14 GREEN 확인 - creator record/domain/response에 nullable
characterId를 추가하고findCreator가 활성ChatCharacterid를 조회하도록 보정했다. series record/domain/response/repository projection에서는publishedDaysOfWeek,isComplete,isPopular를 제거했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --no-daemon,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon통과.rg -n "publishedDaysOfWeek|SeriesPublishedDaysOfWeek|SeriesState|isComplete|isPopular" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel결과 없음. - 2026-06-13: Phase 3 보안 리뷰 보정 RED/GREEN 확인 - 리뷰어 게이트에서 유료 커뮤니티 게시글 비구매자에게 원문
content와audioPath가 노출될 수 있다는 차단 이슈를 확인했다. 비구매자/구매자/작성자 유료 커뮤니티 접근 테스트를 추가한 뒤DefaultCreatorChannelHomeQueryRepositoryTest에서shouldMaskPaidCommunityContentAndAudioForNonBuyer실패를 확인했고, 기존 커뮤니티 목록과 동일하게 유료/미구매/비작성자 본문 축약 및 오디오 숨김을 반영한 뒤./gradlew clean test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon통과. - 2026-06-13: Phase 3 Task 3.7~3.12 constructor projection 조건 RED/GREEN 확인 - 직접 record 생성자 인자로 매핑 가능한 조회가
Projections.constructor를 사용하도록 source guardrail을 추가한 뒤shouldUseProjectionAndBulkQueriesForPhaseThreeOptimizedMethods실패를 확인했다.findCurrentLive,findScheduleslive/audio 후보,findFanTalkSummary최신 글,findSns를 QueryDSLProjections.constructor로 변경하고 후처리/마스킹/집계가 필요한 creator/audio/donation/community/series/activity 조회는 수동 조립을 유지했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --no-daemon,./gradlew ktlintCheck --no-daemon통과.rg -n "publishedDaysOfWeek|SeriesPublishedDaysOfWeek|SeriesState|isComplete|isPopular" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel결과 없음. - 2026-06-13: Phase 3 오디오 공개 조건 문서/구현 정합성 보정 - 공개 또는 예약 공개 오디오는
releaseDate != null이고,releaseDate == null은 삭제/미공개 데이터로 조회에서 제외한다는 전제를 PRD/plan-task에 반영했다.releaseDate == null오디오가 최신/목록/첫 콘텐츠/시리즈 공개 콘텐츠 집계에서 제외되는 테스트를 추가했고, 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest에서 3개 실패를 확인했다.findAudioContentRows,firstAudioContentId,seriesContentStats의 공개 조건을releaseDate != null && releaseDate <= now로 보정한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon통과. - 2026-06-13: Phase 3 데뷔일 오디오 후보 정책 보정 - 오디오 데뷔 후보는 업로드 순서 기준 첫 3개만 보며, 1~2번째 삭제 오디오는 건너뛰고 3번째 삭제 오디오는 4번째로 넘어가지 않고 해당
createdAt을 후보로 쓰는 정책을 PRD/plan-task에 반영했다. 3번째 삭제 시 4번째 공개 오디오로 넘어가던 RED를DefaultCreatorChannelHomeQueryRepositoryTest에서 확인했고,findActivity가firstAudioDebutAt으로 오디오 후보를 계산하도록 보정했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew ktlintCheck --no-daemon통과. - 2026-06-13: Phase 3 P1/P2 리뷰 보정 RED/GREEN 확인 - 예약 오디오는
duration != null && releaseDate != null && releaseDate > now이면isActive와 관계없이 스케줄 후보라는 정책과, 시리즈 신규 표시가 기존 목록처럼 7일 전/현재 시각 경계를 포함한다는 정책을 repository 테스트로 추가했다. 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest에서 2개 실패를 확인했고,findSchedules예약 오디오 조건과newSeriesIds의between(now.minusDays(7), now)조건을 보정했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew ktlintCheck --no-daemon통과. - 2026-06-13: 채널 후원 응답 계약 변경 문서화 - 채널 후원 홈 응답에서 기본 후원 문구 조합과 공개 응답의
donationId/memberId/isSecret을 제거하고, 후원자 닉네임/프로필 이미지/후원 can/추가 메시지/UTC 생성 시각만 내려주는 요구사항을 PRD와 plan-task에 반영했다. Phase 3에Task 3.15를 추가해 repository/service/controller 테스트와 구현 범위를 문서화했다.git diff --check,./gradlew tasks --all --no-daemon통과. - 2026-06-13: 채널 후원 비밀 후원 정책/Projection 구현 기준 문서화 -
Task 3.15에 비밀 후원은 후원자 본인과 받은 크리에이터만 조회 가능하고 제3자는 조회할 수 없는 negative case를 추가했다.isSecret은 repository 조회 조건에만 사용하고 공개 응답에는 포함하지 않는다고 명시했으며, record 생성자 인자로 바로 매핑되는 조회는 QueryDSLProjections.constructor를 사용하도록 구현 기준을 보강했다.git diff --check,./gradlew tasks --all --no-daemon통과. - 2026-06-13: Phase 3 Task 3.16 RED/GREEN 확인 - 구매한 유료 커뮤니티 게시글은 크리에이터가 이후 삭제해
CreatorCommunity.isActive == false가 되어도 구매자에게 조회되고, 비구매자에게는 조회되지 않는 요구사항을 PRD/plan-task에 반영했다.shouldExposeDeletedPaidCommunityContentToBuyer테스트 추가 직후./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon에서 해당 테스트 실패를 확인했고,findCommunityPosts게시글 후보 조건을isActive == true또는 환불되지 않은PAID_COMMUNITY_POST구매 이력 존재로 보정했다. 구현 방식은 게시글 후보 조회 후 구매 여부/좋아요 수/댓글 수를 id 목록 기반 bulk 조회하는 기존 구조를 유지했다. 보정 후 같은 repository 테스트 통과. - 2026-06-13: Phase 3 Task 3.16 정리 검증 -
git diff --check,./gradlew ktlintCheck --no-daemon,./gradlew tasks --all --no-daemon통과. - 2026-06-13: Phase 3 리뷰 반영 RED/GREEN 확인 -
LiveRoomStatus자체가 아니라 라이브 홈 조회의 노출 정책만 보정하기 위해 현재/예약 라이브에 조회자 성별 제한과 크리에이터 입장 제한 테스트를 추가하고, 팬 Talk 작성자 닉네임의deleted_prefix 제거 테스트를 추가했다. 보강 직후DefaultCreatorChannelHomeQueryRepositoryTest가viewerId/isViewerCreator/effectiveViewerGender미정의 컴파일 오류로 실패하는 것을 확인했고,findCurrentLive/findSchedules에 조회자 라이브 정책 입력을 추가해 라이브 후보만 필터링하고findFanTalkSummary최신 글 닉네임에removeDeletedNicknamePrefix()를 적용했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon통과. - 2026-06-13: Phase 3 리뷰 반영 정리 검증 -
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,git diff --check,./gradlew ktlintCheck --no-daemon통과. - 2026-06-13: Phase 3 P2/P3 리뷰 반영 RED/GREEN 확인 - query port 계약을 기존 raw 조회자 성별 파라미터명이 아니라 기존 라이브 목록 의미와 같은
effectiveViewerGender로 명확히 바꾸기 위해 repository 테스트 호출부를 먼저 변경했고,DefaultCreatorChannelHomeQueryRepositoryTest에서Cannot find a parameter with this name: effectiveViewerGender컴파일 실패를 확인했다. 이후CreatorChannelHomeQueryPort와DefaultCreatorChannelHomeQueryRepository의 현재/예약 라이브 조회 계약을effectiveViewerGender로 변경했다. PRD와 plan-task에는 현재/예약 라이브의 성별 제한·크리에이터 입장 제한 정책, service의Auth.gender우선 effective gender 산출, Phase 4 service fake port 테스트 요구사항을 명시했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,git diff --check,./gradlew ktlintCheck --no-daemon통과.rg -n "viewer""Gender" docs/20260612_크리에이터_채널_홈_API src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel결과 없음. - 2026-06-13: Phase 4 Task 4.1/4.2 RED 확인 -
CreatorChannelHomeQueryServiceTest에 fake port 기반 정상 조립/최종 정책 테스트와 user_not_found/creator_not_found/blocked_access 예외 테스트를 추가한 뒤./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --no-daemon에서Unresolved reference: CreatorChannelHomeQueryService,findCreatorRole overrides nothing컴파일 오류를 확인했다. - 2026-06-13: Phase 4 Task 4.1/4.2 GREEN 확인 -
CreatorChannelHomeQueryService를 추가해 port record를 domain 모델로 조립하고,Auth.gender우선effectiveViewerGender, 조회자 본인 여부, 성인 노출 정책, 최신 오디오 중복 제거, 스케줄 최종 제한/정렬/성인 제외, CloudFront URL 변환을 적용했다. 비크리에이터 예외 구분을 위해CreatorChannelHomeQueryPort.findCreatorRole과 repository 구현을 최소 추가했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --no-daemon통과. - 2026-06-13: Phase 4 정리 검증 -
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.domain.CreatorChannelHomeQueryPolicyTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon,./gradlew ktlintCheck --no-daemon,git diff --check통과.rg -n "CreatorChannelHomeController|/api/v2/creator-channels" src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel결과 없음. - 2026-06-13: Phase 4 리뷰 보정 RED/GREEN 확인 - 기존 채널 상세 정책과 동일하게 대상 회원 존재 확인 후 차단 관계를 먼저 검사하고 role 검사는 그 다음 수행하도록 조정했다.
findCreatorRole별도 port를 제거하고CreatorChannelCreatorRecord.role로 기본 회원 조회 record에서 role을 함께 반환하게 변경했다. 차단 관계가 있으면 대상 회원이 비크리에이터여도 접근 차단 예외가 우선되는 service RED와, 비크리에이터 기본 회원도 role과 함께 반환되는 repository 계약 테스트를 추가했다../gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.application.CreatorChannelHomeQueryServiceTest --tests kr.co.vividnext.sodalive.v2.creator.channel.adapter.out.persistence.DefaultCreatorChannelHomeQueryRepositoryTest --no-daemon통과.