docs(creator-channel): 후원 탭 Phase 1 기록을 갱신한다
This commit is contained in:
@@ -41,8 +41,11 @@
|
||||
- `CreatorDonationRankingService.getMemberDonationRanking(...)`를 통해 legacy `CreatorDonationRankingQueryRepository.getMemberDonationRanking` 결과를 재사용한다.
|
||||
- Top 8 조회는 `offset = 0`, `limit = 8`을 사용한다.
|
||||
- 기간은 크리에이터의 `donationRankingPeriod`를 따르고, 값이 없으면 `DonationRankingPeriod.CUMULATIVE`를 사용한다.
|
||||
- `DonationRankingPeriod.WEEKLY`이면 legacy service의 주간 범위를 그대로 사용한다.
|
||||
- `DonationRankingPeriod.CUMULATIVE`이면 legacy service의 전체 누적 범위를 그대로 사용한다.
|
||||
- 조회자가 크리에이터 본인이거나 크리에이터의 `isVisibleDonationRank`가 `true`이면 `rankings`를 내려준다.
|
||||
- 조회자가 크리에이터 본인이 아니고 크리에이터의 `isVisibleDonationRank`가 `false`이면 `rankings`는 빈 배열이다.
|
||||
- `rankings`가 빈 배열이어도 `donationCount`, `donations`, `page`, `size`, `hasNext`는 후원 목록 조건대로 조회한다.
|
||||
- `donationCan`은 기존 프로필 정책과 동일하게 크리에이터 본인 조회 시 실제 값을 내려주고, 일반 회원 조회 시 `0`으로 내려준다.
|
||||
- creator 검증:
|
||||
- 조회 대상 회원이 없으면 `member.validation.user_not_found`
|
||||
@@ -275,7 +278,7 @@ data class CreatorChannelDonationRankingRecord(
|
||||
|
||||
### Phase 1: 공개 계약과 순수 정책 추가
|
||||
|
||||
- [ ] **Task 1.1: 후원 탭 domain model, port, page/month 정책 추가**
|
||||
- [x] **Task 1.1: 후원 탭 domain model, port, page/month 정책 추가**
|
||||
- 파일:
|
||||
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/domain/CreatorChannelDonationQueryPolicyTest.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/domain/CreatorChannelDonationTab.kt`
|
||||
@@ -301,8 +304,11 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- REFACTOR: 중복 상수와 월 범위 계산을 읽기 쉽게 정리하되 기존 `CreatorChannelPage`를 재사용한다.
|
||||
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationQueryPolicyTest`
|
||||
- Expected: `BUILD SUCCESSFUL`
|
||||
- 실행 기록:
|
||||
- RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.domain.CreatorChannelDonationQueryPolicyTest` 실행, 신규 domain/port/policy 타입 부재로 `compileTestKotlin` 실패 확인.
|
||||
- GREEN: 동일 명령 재실행, `BUILD SUCCESSFUL` 확인.
|
||||
|
||||
- [ ] **Task 1.2: response DTO와 facade 매핑 추가**
|
||||
- [x] **Task 1.2: response DTO와 facade 매핑 추가**
|
||||
- 파일:
|
||||
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation/application/CreatorChannelDonationFacadeTest.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation/dto/CreatorChannelDonationTabResponse.kt`
|
||||
@@ -324,8 +330,14 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- REFACTOR: DTO가 도메인 model만 import하고 persistence/legacy 타입을 import하지 않는지 확인한다.
|
||||
- Run: `rg -n "adapter\\.out|explorer\\.profile" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation`
|
||||
- Expected: 검색 결과 0건
|
||||
- 실행 기록:
|
||||
- RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.donation.application.CreatorChannelDonationFacadeTest` 실행, DTO/facade/query service 경계 부재로 `compileTestKotlin` 실패 확인.
|
||||
- GREEN: 동일 명령 재실행, `BUILD SUCCESSFUL` 확인.
|
||||
- 보완: Phase 2 전 공개 endpoint가 내부 `UnsupportedOperationException`으로 실패하지 않도록 query service placeholder를 `SodaException(messageKey = "common.error.invalid_request")`로 고정하고 `CreatorChannelDonationQueryServiceTest`를 추가했다.
|
||||
- 보완 검증: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryServiceTest` 실행, RED에서 `UnsupportedOperationException` 실패 확인 후 GREEN에서 `BUILD SUCCESSFUL` 확인.
|
||||
- REFACTOR: `rg -n "adapter\\.out|explorer\\.profile" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation` 실행, 검색 결과 0건 확인.
|
||||
|
||||
- [ ] **Task 1.3: controller와 인증/API 계약 추가**
|
||||
- [x] **Task 1.3: controller와 인증/API 계약 추가**
|
||||
- 파일:
|
||||
- Create: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation/adapter/in/web/CreatorChannelDonationControllerTest.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation/adapter/in/web/CreatorChannelDonationController.kt`
|
||||
@@ -343,6 +355,12 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- REFACTOR: 기존 FanTalk/커뮤니티 controller와 request mapping 스타일이 같은지 확인한다.
|
||||
- Run: `rg -n "class CreatorChannelDonationController|/\\{creatorId\\}/donations" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation`
|
||||
- Expected: controller class와 endpoint mapping 각 1건 확인
|
||||
- 실행 기록:
|
||||
- RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.donation.adapter.in.web.CreatorChannelDonationControllerTest` 실행, controller 부재로 `compileTestKotlin` 실패 확인.
|
||||
- GREEN: 동일 명령 재실행, `BUILD SUCCESSFUL` 확인.
|
||||
- 보완: Phase 2 전 미완성 endpoint가 기본 운영 컨텍스트에 노출되지 않도록 `@ConditionalOnProperty(name = ["creator-channel.donation-tab.enabled"], havingValue = "true")`를 추가했다.
|
||||
- 보완 검증: controller annotation 계약 테스트를 추가하고 RED에서 조건부 등록 annotation 부재 실패 확인 후 GREEN에서 `BUILD SUCCESSFUL` 확인.
|
||||
- REFACTOR: `rg -n "class CreatorChannelDonationController|/\\{creatorId\\}/donations" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation` 실행, controller class와 endpoint mapping 각 1건 확인.
|
||||
|
||||
### Phase 2: 도메인 조회 서비스와 legacy ranking 재사용 추가
|
||||
|
||||
@@ -355,9 +373,12 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- creator role이 `CREATOR`가 아니면 `member.validation.creator_not_found` 예외를 던지는지 검증한다.
|
||||
- 조회자와 크리에이터 사이 차단 관계가 있으면 기존 차단 메시지 예외를 던지는지 검증한다.
|
||||
- `page = -1`, `size = 10` 요청이 `offset = 0`, `limit = 21`로 port에 전달되고 응답은 size 20으로 잘리는지 검증한다.
|
||||
- 조회자 본인이 크리에이터이면 ranking port에 `withDonationCan = true`가 전달되는지 검증한다.
|
||||
- 조회자 본인이 아니고 `isVisibleDonationRank = true`이면 ranking port에 `withDonationCan = false`가 전달되고 `rankings`가 반환되는지 검증한다.
|
||||
- 조회자 본인이 크리에이터이면 `isVisibleDonationRank = false`여도 ranking port를 호출하고 `withDonationCan = true`가 전달되는지 검증한다.
|
||||
- 조회자 본인이 아니고 `isVisibleDonationRank = true`, `donationRankingPeriod = WEEKLY`이면 ranking port에 `period = WEEKLY`, `withDonationCan = false`가 전달되고 `rankings`가 반환되는지 검증한다.
|
||||
- 조회자 본인이 아니고 `isVisibleDonationRank = true`, `donationRankingPeriod = CUMULATIVE`이면 ranking port에 `period = CUMULATIVE`, `withDonationCan = false`가 전달되는지 검증한다.
|
||||
- 조회자 본인이 아니고 `isVisibleDonationRank = true`, `donationRankingPeriod = null`이면 ranking port에 `period = CUMULATIVE`가 전달되는지 검증한다.
|
||||
- 조회자 본인이 아니고 `isVisibleDonationRank = false`이면 ranking port를 호출하지 않고 `rankings`가 빈 배열인지 검증한다.
|
||||
- 후원 순위가 비공개라 `rankings`가 빈 배열이어도 `donationCount`, `donations`, `page`, `size`, `hasNext`가 정상 조립되는지 검증한다.
|
||||
- donation 작성자 닉네임의 삭제 prefix 제거, profileImagePath CDN 변환, 기본 프로필 이미지 fallback, null message의 빈 문자열 변환을 검증한다.
|
||||
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryServiceTest`
|
||||
- Expected: query service가 없어 컴파일 실패한다.
|
||||
@@ -365,8 +386,16 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- `ObjectProvider<CreatorChannelDonationQueryPort>` 패턴을 사용해 기존 FanTalk query service와 같은 순환 의존 회피 스타일을 따른다.
|
||||
- `CreatorChannelDonationRankingPort`는 생성자 주입한다.
|
||||
- `DonationRankingPeriod`는 creator record 값이 null이면 `DonationRankingPeriod.CUMULATIVE`로 보정한다.
|
||||
- `isVisibleDonationRank`가 false이고 조회자가 크리에이터 본인이 아니면 ranking port를 호출하지 않는다.
|
||||
- `isVisibleDonationRank`가 true이거나 조회자가 크리에이터 본인이면 ranking port를 호출하고 creator의 ranking period를 그대로 전달한다.
|
||||
- `findChannelDonations(...)` 결과는 `limitItems` 적용 후 domain으로 변환한다.
|
||||
- `hasNext`는 fetch 결과 크기로 계산한다.
|
||||
- Phase 1 임시 보호장치를 함께 정리한다.
|
||||
- `CreatorChannelDonationQueryService.getDonationTab(...)`의 placeholder `SodaException(messageKey = "common.error.invalid_request")`를 실제 구현으로 대체한다.
|
||||
- placeholder 전용 `CreatorChannelDonationQueryServiceTest`는 실제 query service 동작 테스트로 교체하고, placeholder 오류 검증은 제거한다.
|
||||
- `CreatorChannelDonationController`의 `@ConditionalOnProperty(name = ["creator-channel.donation-tab.enabled"], havingValue = "true")`와 관련 import를 제거해 endpoint가 기본 Spring context에 등록되도록 한다.
|
||||
- `CreatorChannelDonationControllerTest`의 `@TestPropertySource(properties = ["creator-channel.donation-tab.enabled=true"])`와 conditional annotation 검증 테스트를 제거한다.
|
||||
- 별도 feature flag rollout 정책을 유지하기로 결정한 경우에만 위 controller 조건부 등록을 남기고, 그 결정 사유와 활성화 설정 위치를 이 문서에 추가한다.
|
||||
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.application.CreatorChannelDonationQueryServiceTest`
|
||||
- Expected: `BUILD SUCCESSFUL`
|
||||
- REFACTOR: query service가 API DTO를 import하지 않는지 확인한다.
|
||||
@@ -407,6 +436,7 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- Verify: `src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/CreatorDonationRankingService.kt`
|
||||
- RED: mock `CreatorDonationRankingService`를 사용해 adapter 테스트를 먼저 작성한다.
|
||||
- `findTopRankings(creatorId = 1, period = CUMULATIVE, withDonationCan = false)` 호출 시 legacy service에 `offset = 0`, `limit = 8`, `withDonationCan = false`, 같은 period가 전달되는지 검증한다.
|
||||
- `findTopRankings(creatorId = 1, period = WEEKLY, withDonationCan = true)` 호출 시 legacy service에 `offset = 0`, `limit = 8`, `withDonationCan = true`, `period = WEEKLY`가 전달되는지 검증한다.
|
||||
- legacy `MemberDonationRankingResponse` 결과가 `CreatorChannelDonationRankingRecord`로 필드 손실 없이 변환되는지 검증한다.
|
||||
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.donation.adapter.out.legacy.LegacyCreatorChannelDonationRankingAdapterTest`
|
||||
- Expected: adapter가 없어 컴파일 실패한다.
|
||||
@@ -429,10 +459,12 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/adapter/out/persistence/DefaultCreatorChannelDonationQueryRepository.kt`
|
||||
- Verify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/adapter/out/legacy/LegacyCreatorChannelDonationRankingAdapter.kt`
|
||||
- RED: `@SpringBootTest` + `MockMvc` 통합 테스트를 먼저 작성한다.
|
||||
- 별도 `creator-channel.donation-tab.enabled` 테스트 property 없이 기본 Spring context에서 후원 탭 endpoint가 등록되는지 검증한다.
|
||||
- controller-service-repository를 거쳐 후원 탭 API가 `donationCount`, `donations`, `page`, `size`, `hasNext`를 반환하는지 검증한다.
|
||||
- `page` 범위 밖 요청은 빈 `donations`, 유지된 `donationCount`, `hasNext = false`를 반환하는지 검증한다.
|
||||
- `page = -1`, `size = 100` 요청은 응답의 `page = 0`, `size = 50`으로 보정되는지 검증한다.
|
||||
- 일반 조회자에게 크리에이터의 비공개 후원은 숨기고 조회자 본인의 비공개 후원은 노출하는지 검증한다.
|
||||
- 일반 조회자가 `isVisibleDonationRank = false`인 크리에이터 채널을 조회하면 `rankings`는 빈 배열이고 `donationCount`, `donations`, `page`, `size`, `hasNext`는 정상 반환되는지 검증한다.
|
||||
- 크리에이터 본인 조회 시 비공개 후원과 `donationCan` 값이 포함된 ranking이 내려오는지 검증한다.
|
||||
- Run: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.donation.adapter.in.web.CreatorChannelDonationEndToEndTest`
|
||||
- Expected: 통합 wiring 또는 신규 API가 없어 실패한다.
|
||||
@@ -462,6 +494,8 @@ data class CreatorChannelDonationRankingRecord(
|
||||
- Expected: 검색 결과 0건
|
||||
- Run: `rg -n "class CreatorChannelDonationController|/\\{creatorId\\}/donations" src/main/kotlin/kr/co/vividnext/sodalive/v2`
|
||||
- Expected: 후원 탭 controller와 endpoint mapping 각 1건 확인
|
||||
- Run: `rg -n "ConditionalOnProperty|creator-channel\\.donation-tab\\.enabled" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation`
|
||||
- Expected: 별도 feature flag rollout 정책을 유지하기로 문서화한 경우가 아니라면 검색 결과 0건
|
||||
- Run: `./gradlew ktlintCheck`
|
||||
- Expected: `BUILD SUCCESSFUL`
|
||||
|
||||
@@ -478,4 +512,4 @@ data class CreatorChannelDonationRankingRecord(
|
||||
|
||||
## 6. 전체 검증 기록
|
||||
|
||||
- 아직 구현 전이므로 검증 기록 없음.
|
||||
- Phase 1 검증은 각 Task 실행 기록에 누적했다.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- 후원 탭은 홈 요약보다 더 많은 채널 후원 목록을 추가 로딩해야 하고, 전체 채널 후원 개수와 후원 순위 Top 8을 함께 표시해야 한다.
|
||||
- 레거시 채널 후원 목록 API는 `/explorer/profile/channel-donation`에 있고, V2 크리에이터 채널 탭 API의 패키지 분리 구조와 맞지 않는다.
|
||||
- 후원 순위는 기존 레거시 `CreatorDonationRankingQueryRepository.getMemberDonationRanking` 결과와 동일해야 하므로 새 집계 기준을 임의로 만들면 안 된다.
|
||||
- 레거시 프로필의 후원 순위는 크리에이터 설정에 따라 비공개, 주간 공개, 전체 공개가 가능하므로 후원 탭 API도 같은 공개 범위와 기간 정책을 따라야 한다.
|
||||
- 신규 API는 기존 V2 크리에이터 채널 탭과 동일하게 공개 API 조립 계층과 도메인 조회 계층을 분리해야 한다.
|
||||
|
||||
---
|
||||
@@ -50,6 +51,8 @@
|
||||
## 6. User Stories
|
||||
- 사용자는 크리에이터 채널 후원 탭에 들어가면 전체 채널 후원 개수를 확인하고 싶다.
|
||||
- 사용자는 해당 크리에이터의 후원 순위 Top 8을 확인하고 싶다.
|
||||
- 사용자는 크리에이터가 후원 순위를 공개하지 않은 채널에서는 후원 순위 없이 채널 후원 목록만 확인한다.
|
||||
- 크리에이터는 후원 순위를 공개하지 않은 경우에도 본인 채널에서 자신의 후원 순위를 확인하고 싶다.
|
||||
- 사용자는 채널 후원 목록을 최신순으로 추가 로딩하고 싶다.
|
||||
- 사용자는 후원자 닉네임, 프로필 이미지, 후원 캔 수, 메시지, 후원 시간을 목록 item에서 바로 확인하고 싶다.
|
||||
- 앱 클라이언트는 page, size, hasNext를 이용해 추가 로딩 상태를 안정적으로 제어하고 싶다.
|
||||
@@ -134,6 +137,7 @@ data class CreatorChannelDonationResponse(
|
||||
#### Edge Cases
|
||||
- 조회 가능한 채널 후원이 없으면 `donationCount`는 `0`, `donations`는 빈 배열, `hasNext`는 `false`로 내려준다.
|
||||
- 노출 가능한 후원 순위가 없으면 `rankings`는 빈 배열로 내려준다.
|
||||
- 크리에이터가 후원 순위를 공개하지 않았고 조회자가 크리에이터 본인이 아니면 채널 후원 목록은 정상 조회하되 `rankings`만 빈 배열로 내려준다.
|
||||
- 작성자 프로필 이미지가 없으면 기존 V2 크리에이터 채널 API와 동일하게 기본 프로필 이미지 URL을 내려준다.
|
||||
- `createdAtUtc`는 `ChannelDonationMessage.createdAt`을 UTC 기준 ISO-8601 문자열로 내려준다.
|
||||
- Boolean 응답 필드는 Jackson 직렬화 필드명을 명시한다.
|
||||
@@ -177,15 +181,18 @@ data class CreatorChannelDonationResponse(
|
||||
- 기간은 크리에이터의 `donationRankingPeriod` 설정을 따른다.
|
||||
- `donationRankingPeriod`가 없으면 `DonationRankingPeriod.CUMULATIVE`를 사용한다.
|
||||
- `DonationRankingPeriod.WEEKLY`는 기존 레거시 서비스의 주간 범위 계산을 따른다.
|
||||
- `DonationRankingPeriod.CUMULATIVE`는 기존 레거시 서비스의 전체 누적 범위 계산을 따른다.
|
||||
- 후원 순위 노출 정책은 기존 프로필 정책과 동일하게 유지한다.
|
||||
- 조회자가 크리에이터 본인이거나 크리에이터의 `isVisibleDonationRank`가 `true`이면 `rankings`를 내려준다.
|
||||
- 조회자가 크리에이터 본인이 아니고 크리에이터의 `isVisibleDonationRank`가 `false`이면 `rankings`는 빈 배열로 내려준다.
|
||||
- 조회자가 크리에이터 본인이 아니고 크리에이터의 `isVisibleDonationRank`가 `false`인 경우에도 `donationCount`, `donations`, `page`, `size`, `hasNext`는 후원 목록 조건대로 정상 조회한다.
|
||||
- `donationCan` 노출 여부는 기존 프로필 정책과 동일하게 크리에이터 본인 조회 시 실제 값을 내려주고, 일반 회원 조회 시 `0`으로 내려준다.
|
||||
|
||||
#### Edge Cases
|
||||
- 순위 대상 회원이 8명보다 적으면 있는 만큼만 내려준다.
|
||||
- 같은 후원 캔 금액이면 레거시 쿼리와 동일하게 회원 ID 내림차순으로 정렬한다.
|
||||
- 순위 조회 결과가 없어도 후원 탭 API는 성공 처리한다.
|
||||
- 후원 순위 비공개로 `rankings`가 빈 배열인 경우와 실제 순위 결과가 없어 `rankings`가 빈 배열인 경우 모두 같은 응답 스키마를 사용한다.
|
||||
|
||||
### Feature E. V2 재사용 범위와 계층 분리
|
||||
|
||||
@@ -230,6 +237,7 @@ data class CreatorChannelDonationResponse(
|
||||
- 채널 후원 목록 item은 크리에이터 채널 홈 API의 `CreatorChannelDonationResponse`와 같은 필드 의미를 사용한다.
|
||||
- 후원 순위 Top 8 item은 기존 `MemberDonationRankingResponse`와 같은 필드 의미를 사용한다.
|
||||
- 후원 순위 산식은 `CreatorDonationRankingQueryRepository.getMemberDonationRanking` 기준을 변경하지 않는다.
|
||||
- 후원 순위 공개 여부는 `isVisibleDonationRank`, 기간은 `donationRankingPeriod` 기준으로 판단한다.
|
||||
- 채널 후원 목록과 개수의 기간은 홈 후원 섹션과 동일하게 현재 KST 월 범위로 한다.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user