docs(recommendation): 크리에이터 채널 홈 계획을 추가한다
This commit is contained in:
503
docs/20260612_크리에이터_채널_홈_API/plan-task.md
Normal file
503
docs/20260612_크리에이터_채널_홈_API/plan-task.md
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
# 크리에이터 채널 홈 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개, 예약 시각 오름차순, 같은 예약 시각이면 라이브 먼저 표시한다.
|
||||||
|
- 신규 오디오 콘텐츠와 오디오 목록은 중복 노출하지 않는다. `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 계약이므로 구현 중 변경이 필요하면 먼저 이 문서를 갱신한다.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
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("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: 공용 활동 타입 정리
|
||||||
|
|
||||||
|
- [x] **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_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을 사용할 수 있다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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.LIVE`가 `AUDIO`보다 먼저 온다.
|
||||||
|
- 오디오 목록에서는 `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` 통과.
|
||||||
Reference in New Issue
Block a user