diff --git a/docs/20261104_크리에이터채널_비밀후원_익명조회/plan-task.md b/docs/20261104_크리에이터채널_비밀후원_익명조회/plan-task.md index 8eea60c9..44b4354a 100644 --- a/docs/20261104_크리에이터채널_비밀후원_익명조회/plan-task.md +++ b/docs/20261104_크리에이터채널_비밀후원_익명조회/plan-task.md @@ -2,19 +2,19 @@ | 문서 항목 | 내용 | |---|---| -| 상태 | `P1-T1`, `P1-T2`, 범위 한정 `P1-GATE` 완료. 전체 suite는 timeout으로 미검증 | +| 상태 | Phase 1 및 `P2-T1`·`P2-GATE` 완료. 전체 테스트는 사용자 실행 | | 작성일 | 2026-11-04 | | 요구사항 기준 | [prd.md](prd.md) | | API 기준 | [prd.md §8](prd.md#8-api-계약) | -| 현재 Phase | Phase 1: 두 후원 조회 경로 완성 | +| 현재 Phase | Phase 2: 응답 isSecret UI 표시 의미 변경 | | 현재 활성 Goal | 없음 | -| 다음 Goal | 없음. 전체 suite 완료 여부는 별도 미해결 검증 한계 | +| 다음 Goal | 없음 | > 실행 준비 기준: goal별로 한 번에 하나씩 실행하며, 각 Task의 명령과 체크박스는 실행 후에만 완료 처리한다. 구현 방법은 본 계획의 TDD 순서를 따른다. **실행 방식:** 사용자 지시에 따라 worktree 없이 현재 `test` 브랜치에서 Goal을 순차 실행한다. 커밋·푸시·배포는 하지 않는다. -**Architecture:** 각 endpoint의 persistence record부터 domain과 DTO까지 persisted `isSecret`을 전달하고, 기존 local `toDomain` mapper에서 viewer/creator/donor 권한으로 표시 필드를 정한다. 익명 이미지 URL은 기존 default-profile과 같은 직접 host/path 조합으로 만들며 각 endpoint의 filter 제거와 masking은 end-to-end Task 하나에서 함께 완성한다. +**Architecture:** persistence record는 실제 `isSecret`을 유지한다. 서비스의 기존 `toDomain` mapper가 조회자별 `isMasked`로 표시 필드와 응답 domain의 `isSecret`을 정한다. DTO 필드명·타입과 기존 이미지 URL 조합은 바꾸지 않는다. **Spec:** [prd.md](prd.md), 특히 §7~§10. @@ -24,7 +24,7 @@ - 비밀 행은 USER 및 본인이 아닌 CREATOR role도 익명: 양 Task의 role별 HTTP E2E. - null/empty 메시지, localized nickname, 마스킹 이미지 URL과 원본/default-profile 회귀: 양 Task의 mapper/service 검증. - 월 경계·정렬·limit/page의 비밀 행 포함: repository와 HTTP E2E의 경계·초과 건수 검증. -- `isSecret`은 모든 role에서 persisted Boolean이며 랭킹은 그대로: 양 endpoint JSON과 기존 ranking assertion. +- 응답 `isSecret`은 조회자별 마스킹 Boolean, DB/record는 원래 비밀 Boolean이다. 양 endpoint 역할별 JSON과 저장값 불변을 검증하고 랭킹은 유지한다. ## 목표 @@ -35,6 +35,7 @@ | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| | 1 | 범위 한정 완료 | `2/2` | 없음 | 전체 suite는 600000ms timeout으로 결과 미확정. 전체 suite 통과로 간주하지 않음 | +| 2 | 완료 | `1/1` | 없음 | 관련 196개 회귀 및 최종 HTTP 55개 통과. 전체 테스트는 사용자 실행 | - 구현 단계의 실행 체크박스는 실제 테스트 및 검토 증거가 확보된 뒤에만 완료 처리한다. - 미완료 Goal은 동시에 하나만 실행한다. @@ -44,7 +45,7 @@ ### 포함 -- 홈 및 후원 탭의 현재 KST 월 비밀 후원 조회, persisted `isSecret`, 서비스 계층 마스킹. +- 홈 및 후원 탭의 현재 KST 월 비밀 후원 조회, DB 비밀 설정 보존, 조회자별 UI flag `isSecret` 및 서비스 계층 마스킹. - 양쪽 DTO 직렬화와 repository, service, controller, facade 및 HTTP MockMvc E2E 검증. - 기존 접근 제어, 정렬, count/page, 랭킹, 날짜·금액 계약 보존. @@ -58,7 +59,7 @@ ## 기술적 제약 - Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper, QueryDSL 및 기존 `SodaMessageSource`/CDN URL 변환을 사용한다. -- 공개 API 목록 DTO에 donor `memberId`나 donation ID를 추가하지 않는다. 내부 record/domain의 기존 `memberId`는 권한 판정에 유지한다. +- 이번 변경에서 공개 API 필드를 추가하지 않는다. 내부 `memberId`, 현재 채널 주인 전용 `donationId` 및 숨김 정책은 유지한다. - persistence에서 비밀 row를 유지하되 서비스가 response domain을 만들기 전 익명화한다. 두 계층 모두 수정과 테스트를 하나의 end-to-end Task로 수행한다. - 각 Task의 `isSecret` 추가는 해당 탭의 port record, domain model, QueryDSL constructor projection 순서와 해당 테스트 fixture constructor를 함께 갱신한다. - 로컬 변환 함수 `toDomain`이 이미 있으므로 기존 함수가 있는 service 안에서 viewer/creator/lang 판정을 수행한다. 새 공유 추상화는 만들지 않는다. @@ -232,8 +233,73 @@ - EN/JA는 사용자 승인 문구가 아닌 작성자 기술 초안이다. implementation review에서 승인/수정 확인을 기록하며 사용자 확정 문구로 간주하지 않는다. - 회귀가 발견되면 해당 Task의 새 회귀 수정 체크박스와 증거를 계획에 추가한 뒤 수정한다. 성공을 추정하지 않는다. +### Phase 2: 응답 isSecret을 익명 UI 표시 여부로 변경 + +**결과:** 두 탭에서 익명 표시가 필요한 타인의 비밀 후원만 `isSecret=true`를 반환한다. Phase 1 완료 기록은 당시 저장값 전달 계약의 이력으로 보존하며 현재 의미는 이 Phase가 우선한다. + +- [x] **Task 2.1: 조회자별 UI flag 전달** + +**Goal `P2-T1`:** 기존 마스킹 판정과 응답 flag를 일치시키되 DB 비밀 설정과 나머지 응답·권한 정책을 유지한다. +- 시작 조건: PRD `DEC-008` 승인. 현재 `test` 브랜치에서 진행, worktree·commit·push 없음. +- 완료 증거: 기존 HTTP 역할/언어 매트릭스 RED→GREEN, 두 기능 회귀 성공, DB 비밀값 불변과 owner 전용 donationId/숨김 회귀 통과. +- 범위 밖: DB/record/쿼리 수정, 추가 응답 필드, 숨김·랭킹·인증·시각 정책 변경. + +**Files:** +- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryService.kt` +- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/application/CreatorChannelDonationQueryService.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/home/application/CreatorChannelHomeQueryServiceTest.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/donation/application/CreatorChannelDonationQueryServiceTest.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt` +- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/donation/adapter/in/web/CreatorChannelDonationEndToEndTest.kt` + +**Interface:** 로컬 mapper에서 응답 domain 생성 인자 `isSecret = isMasked`를 사용한다. 입력 record의 실제 비밀값·기존 mapper signature 및 DTO signature는 유지한다. + +- [x] **RED:** 기존 두 HTTP E2E 및 service 테스트에서 내 채널/본인 비밀 후원의 flag 기대값을 false로, 타인 비밀은 true로 변경했다. 공개·타인 CREATOR·언어·내용·이미지·금액·시각·donationId assertion은 유지했다. +- [x] **RED 확인:** 두 E2E 메서드 실행은 컴파일 성공 후 `:test` 단계에서 600000ms timeout으로 assertion 결과를 얻지 못했다. 두 service의 역할별 메서드만 실행해 22개 중 8개에서 expected false / actual true 실패를 확인했다. HTTP 검증은 환경 지연 원인을 조사한 뒤 별도로 완료한다. +- [x] **GREEN:** 두 서비스의 반환 `isSecret` 값만 `isMasked`로 바꿨다. 테스트는 fixture/id/can으로 원본 공개·비밀 row를 선택하며 소유자의 응답 flag로 원본 비밀 여부를 추정하지 않는다. +- [x] **GREEN 확인:** 두 service 46개, 관련 패키지 196개 및 최종 HTTP E2E 55개가 통과했다. 조회 후 별도 트랜잭션 clear/재조회로 DB 비밀값 보존을 확인했다. +- [x] **REFACTOR:** 추가 추상화 없이 기능별 회귀·lint 성공과 독립 검토 승인을 기록했다. repository record의 실제 비밀값 assertion은 변경하지 않았다. + +**Goal `P2-GATE`:** 두 기능의 새 flag 계약 및 직접 영향 회귀를 판정한다. +- 시작 조건: `P2-T1` 구현 및 검증 완료. +- 완료 증거: 아래 명령 성공과 독립 검토. 전체 테스트는 사용자가 `./gradlew test`로 실행한다. +- TDD 예외 사유: 검증 전용 Goal이며 제품 코드를 추가하지 않는다. +- 대체 검증: 실제 SpringBootTest/H2/MockMvc 요청 결과와 DB 재조회 assertion, 코드 리뷰 대조. + +```bash +./gradlew --no-daemon -Pkotlin.compiler.execution.strategy=in-process test --tests 'kr.co.vividnext.sodalive.v2.creator.channel.home.*' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.*' --tests 'kr.co.vividnext.sodalive.v2.creator.channel.donation.*' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.donation.*' ktlintCheck +``` + +실행 순서: `P2-T1` → `P2-GATE`. 이번 변경에서는 전체 테스트를 자동 실행하지 않는다. + +- [x] `P2-GATE`: 관련 기능 회귀, 실제 HTTP/MockMvc 응답, DB 불변, ktlint 및 독립 검토 통과. + ## Progress +### 응답 isSecret 의미 변경 커밋 전 재검증: 2026-11-05 + +- 사용자 `/commit-with-validation` 요청에 따라 기존 구현 변경을 커밋한다. 구현 단계의 커밋 미실행 기록은 당시 이력으로 보존한다. +- 실행 명령: `./gradlew --no-daemon -Pkotlin.compiler.execution.strategy=in-process test --tests '*CreatorChannelHomeQueryServiceTest' --tests '*CreatorChannelDonationQueryServiceTest' --tests '*CreatorChannelHomeEndToEndTest' --tests '*CreatorChannelDonationEndToEndTest' ktlintCheck --console=plain`. +- 결과: exit 0, `BUILD SUCCESSFUL`, 2분 27초. 네 클래스 101개 테스트, 실패·오류·skip 0. `ktlintCheck`는 `UP-TO-DATE`로 통과했다. +- `git diff --check` 통과. 전체 테스트는 실행하지 않았으며 사용자 실행 명령은 `./gradlew test`다. + +### P2-GATE 완료 증거 + +- 관련 service 두 클래스 46개 및 `ktlintCheck` 성공. 이어 네 home/donation 패키지를 위 명령으로 검증했다 (`--tests` 패턴 앞에 `*` 사용, `--info --console=plain` 추가). 15개 클래스·196개 테스트, 실패·오류·skip 0, exit 0, 1분 32초. 기존 숨김·owner donationId·정산·랭킹 회귀 포함. +- 독립 정적 리뷰 APPROVE: production 변경은 두 반환 할당뿐이며, 저장값과 익명 내용·기존 권한/필드 구조 보존을 확인했다. +- 부모 최종 실행: `./gradlew --no-daemon -Pkotlin.compiler.execution.strategy=in-process test --tests '*CreatorChannelHomeEndToEndTest' --tests '*CreatorChannelDonationEndToEndTest' ktlintCheck bootJar --console=plain`. exit 0, 1분 45초. home 18 + donation 37 = 55개, 실패·오류·skip 0. `bootJar` 성공, lint 직전 성공 결과 재사용. +- Surface: RANDOM_PORT/TestRestTemplate/실제 JWT 요청과 MockMvc 역할·언어 매트릭스를 구분해 검증했다. 동일 비밀행의 owner/donor GET은 원문과 false, outsider GET은 익명과 true다. 기존 owner-only donationId, 숨김 후 최신 목록 보충 및 DB 원본 불변 assertion 통과. +- 초기 HTTP timeout은 재현되지 않았다. 진단 실행의 donation/home Spring 시작은 각각 약 25.7초/11.7초였으며, 소유 Test Executor 스레드 덤프는 repository 초기화 중 RUNNABLE 상태였고 이후 정상 종료했다. 과거 timeout 원인은 미확정이며 설정을 변경하지 않았다. +- 범위 밖 관찰: H2의 background scheduler에서 `FOR UPDATE SKIP LOCKED` 문법 오류 로그가 있었으나 관련 테스트 실패는 없었다. 원인을 이번 변경이나 timeout으로 단정하거나 수정하지 않았다. +- Kotlin LSP 미설치/도구 부재로 LSP 통과 주장은 하지 않는다. 관련 Kotlin 컴파일과 lint로 검증했다. 전체 테스트·운영 데이터/CDN 재검증·커밋·푸시는 수행하지 않았다. 전체 테스트 사용자 명령: `./gradlew test`. + +### P2-T1 RED 및 최소 변경 + +- HTTP RED 명령: `./gradlew --no-daemon -Pkotlin.compiler.execution.strategy=in-process test --tests '*CreatorChannelHomeEndToEndTest.shouldMaskOnlyOtherMembersSecretDonationsThroughHttp*' --tests '*CreatorChannelDonationEndToEndTest.shouldMaskOnlyOtherMembersSecretDonationsThroughHttp*'`. 컴파일 성공 후 `:test`에서 10분 timeout; RED assertion 증거로 간주하지 않는다. +- 대체 RED 명령: `./gradlew --no-daemon -Pkotlin.compiler.execution.strategy=in-process test --tests '*CreatorChannelHomeQueryServiceTest.shouldMaskSecretDonationOnlyForOtherViewers*' --tests '*CreatorChannelDonationQueryServiceTest.shouldMaskSecretDonationOnlyForOtherViewers*'`. 55초, 22개 중 8개 expected false / actual true 실패. 본인 및 채널 주인의 저장된 비밀 후원에서 기대와 다름을 확인했다. +- 그 뒤 production은 두 서비스의 `isSecret = isSecret`을 `isSecret = isMasked`로 바꾼 2줄만 수정했다. repository/record/DB 및 기존 donationId 판정은 변경하지 않았다. +- 전체 테스트는 실행하지 않는다. 기존 기능별 HTTP/서비스 테스트와 직접 영향 package 회귀만 실행하며, 전체 검증은 사용자 명령 `./gradlew test`로 분리한다. + ### 커밋 전 재검증: 2026-11-04 - 사용자 `/commit-with-validation` 요청으로 기존 변경의 커밋을 진행한다. 이전 구현 단계의 커밋 미실행 기록은 당시 상태로 보존한다. diff --git a/docs/20261104_크리에이터채널_비밀후원_익명조회/prd.md b/docs/20261104_크리에이터채널_비밀후원_익명조회/prd.md index 6925bf49..b2ee7b4c 100644 --- a/docs/20261104_크리에이터채널_비밀후원_익명조회/prd.md +++ b/docs/20261104_크리에이터채널_비밀후원_익명조회/prd.md @@ -4,7 +4,7 @@ | 항목 | 내용 | |---|---| -| 문서 상태 | 구현 및 기능 범위 Gate 완료, 전체 테스트 suite 검증은 timeout으로 미완료 | +| 문서 상태 | 응답 isSecret UI 표시 의미 변경 및 관련 기능 검증 완료 (`P2-GATE`) | | 작성일 | 2026-11-04 | | 최종 수정일 | 2026-11-04 | | 대상 제품 | 크리에이터 채널 비밀 후원 익명 조회 | @@ -15,7 +15,7 @@ ## 1. Overview -크리에이터 채널 홈과 후원 탭에서 비밀 후원 행은 그대로 보이되, 크리에이터와 후원자 본인 외의 조회자에게 후원자의 식별 정보와 메시지를 숨긴다. 조회 응답은 비밀 여부를 모든 사용자에게 표시해 클라이언트가 비밀 후원을 식별할 수 있게 한다. 마스킹 대상 비밀 후원 이미지 URL은 기존 `default-profile`과 같은 방식으로 `cloud.aws.cloud-front.host`에 `/profile/anonymous-profile.png`를 붙여 구성한다. 구현 및 범위 한정 검증을 마쳤고, 전체 테스트 suite는 실행 제한 시간 내 완료하지 못했다. +크리에이터 채널 홈과 후원 탭에서 비밀 후원 행은 그대로 보이되, 크리에이터와 후원자 본인 외의 조회자에게 후원자의 식별 정보와 메시지를 숨긴다. 응답 `isSecret`은 현재 조회자에게 익명 UI로 표시해야 하는지를 나타낸다. 마스킹 대상 비밀 후원 이미지 URL은 기존 `default-profile`과 같은 방식으로 `cloud.aws.cloud-front.host`에 `/profile/anonymous-profile.png`를 붙여 구성한다. 최초 구현 검증 이력은 보존하고, 후속 의미 변경은 `P2-T1`에서 별도로 검증한다. ## 2. Problem Statement @@ -28,7 +28,7 @@ - 홈의 `channelDonations`와 후원 탭 `donations`에 현재 KST 월의 비밀 후원을 포함한다. - 비밀 후원에 한해 크리에이터 본인 또는 해당 후원자 본인에게 원래 닉네임, 메시지, 프로필 이미지를 보여 준다. 공개 후원은 기존처럼 모든 인증 조회자에게 원본을 보여 준다. - 비밀 후원의 나머지 조회자에게 확정된 익명 표시 문구와 `"$cloudFrontHost/profile/anonymous-profile.png"` 이미지를 보여 준다. 공개 후원과 크리에이터·후원자 본인의 프로필 이미지 및 기존 default-profile fallback은 변경하지 않는다. -- 두 응답의 모든 후원 항목에 저장된 비밀 여부를 나타내는 `isSecret`을 제공한다. +- 두 응답의 `isSecret`은 현재 조회자에게 익명 후원 UI로 표시해야 하는지를 나타낸다. DB의 실제 비밀 여부는 변경하지 않는다. ### 3.2 UX 목표 @@ -54,14 +54,14 @@ - 기존 인증 및 크리에이터 조회 권한을 그대로 적용한다. - 기존 차단 확인과 크리에이터 역할 검증을 변경하지 않는다. -- 비밀 후원 목록 행에서 새 `memberId` 또는 donation 식별자를 응답에 노출하지 않는다. +- 이번 후속 변경은 새 `memberId`나 식별 필드를 추가하지 않으며, 현재 구현의 채널 주인 전용 `donationId` 반환 정책을 유지한다. ## 6. 핵심 사용자 흐름 1. 인증 사용자가 크리에이터 채널 홈 또는 후원 탭을 조회한다. 2. 서버는 기존 권한·차단·크리에이터 검증을 수행하고 현재 KST 월 후원 목록을 가져온다. 3. 비밀 후원은 크리에이터와 해당 후원자만 원본 필드를 받고, 그 외 조회자는 익명화된 값을 받는다. 공개 후원은 기존대로 인증 조회자에게 원본 필드를 제공한다. -4. 모든 항목은 `isSecret`으로 원래 비밀 상태를 확인할 수 있고 기존 정렬·건수·pagination을 따른다. +4. 클라이언트는 응답 `isSecret` 하나로 익명 후원 UI를 선택한다. 기존 정렬·건수·pagination은 유지한다. ## 7. 기능 요구사항 @@ -73,7 +73,7 @@ | `SDA-002` | 확정 | 후원 탭 `donations`와 `donationCount`에 현재 KST 월의 공개·비밀 후원을 포함한다. | count가 공개/비밀 전체와 일치하고 기존 페이지 경계를 유지 | §8.1~§8.2, `P1-T2` | | `SDA-003` | 확정 | 비밀 후원은 본인 또는 크리에이터에게 원본 필드를 반환하고, 공개 후원은 기존처럼 모든 인증 조회자에게 원본을 반환한다. | 비밀의 경우 `viewerId == creatorId` 또는 `viewerId == donor.memberId`; 공개 행은 인증 viewer 모두 nickname/message/profile 원본 | §8.3, `P1-T1`, `P1-T2` | | `SDA-004` | 확정 | 비밀 후원의 나머지 조회자에게 해당 행 필드만 익명화한다. | 닉네임 `익명`, 메시지 `크리에이터에게만 보여요`, 프로필 URL은 `"$cloudFrontHost/profile/anonymous-profile.png"` | §8.3, `P1-T1`, `P1-T2`, `EXT-001` | -| `SDA-005` | 확정 | 각 후원 항목에 저장된 비밀 여부를 `isSecret`으로 반환한다. | 두 목록, 모든 조회자, 공개·비밀 각각 Boolean이 persisted 값을 반영 | §8.1~§8.2, `P1-T1`, `P1-T2` | +| `SDA-005` | 정정 확정 | 응답 `isSecret`은 현재 조회자에게 익명 처리된 항목일 때만 true다. | 공개·본인 후원·내 채널 후원은 false, 다른 채널의 타인 비밀 후원은 true. DB 원본은 보존 | §8.1~§8.2, `P2-T1` | | `SDA-006` | 확정 | 익명 닉네임과 메시지 문구를 요청 언어에 맞춰 반환한다. | `Lang.KO/EN/JA`와 기존 `SodaMessageSource` 사용, 두 key 모두 언어별 값, KO 문구 정확 일치 | §8.3, `P1-T1`, `P1-T2` | | `SDA-007` | 완료 | 익명 프로필 이미지 파일명은 `anonymous-profile.png`; URL은 `"$cloudFrontHost/profile/anonymous-profile.png"`로 구성한다. | 설정된 CDN에서 HTTP 200, `image/png`, 19,331 bytes를 확인했다. | `EXT-001`, `P1-GATE` | @@ -84,12 +84,20 @@ - 기존 인증, block 검사, creator 검증과 응답 envelope를 유지한다. - 현재 KST 월 시작은 inclusive, 다음 KST 월 시작은 exclusive이며 저장·비교 시간 변환 규칙은 기존 구현을 유지한다. - 최신순은 `createdAt DESC, donation.id DESC`다. -- 비밀 여부는 마스킹 여부와 별개의 persisted 상태다. `isSecret`은 숨김 처리한 행에서도 `true`다. +- 응답 `isSecret = 저장된 isSecret && viewerId != creatorId && viewerId != donorId`다. 공개 후원·본인이 한 비밀 후원·내 채널의 비밀 후원은 false, 다른 채널에서 타인이 한 비밀 후원만 true다. +- 응답값은 실제 비밀 설정을 설명하지 않고 익명 UI 표시 여부만 나타낸다. DB와 repository record의 `isSecret`은 저장값 그대로 유지한다. `isMyDonation` 등 추가 필드는 만들지 않는다. - `can`과 `createdAtUtc`는 실제 값 그대로 반환한다. -- 새 donor `memberId`나 donation ID는 목록 DTO에 추가하지 않는다. +- 새 donor `memberId`나 추가 식별 필드를 만들지 않는다. 후속 기능으로 도입된 채널 주인 전용 `donationId` 노출 및 후원 숨김 정책은 이번 변경에서 그대로 유지한다. ### 8.2 Endpoint 계약 +| 조회 대상 | 응답 `isSecret` | 표시 정보 | +|---|---|---| +| 공개 후원 | false | 원본 | +| 내가 한 비밀 후원 | false | 원본 | +| 내 채널의 비밀 후원 | false | 원본 | +| 다른 채널에서 타인이 한 비밀 후원 | true | 익명 처리 | + | Method 및 경로 | 응답 필드 | 목록 및 page 규칙 | |---|---|---| | `GET /api/v2/creator-channels/{creatorId}/home` | `data.channelDonations[]`에 `nickname`, `profileImageUrl`, `can`, `message`, `createdAtUtc`, `isSecret` | 현재 월 전체 대상 중 최신 최대 8건 | @@ -136,7 +144,8 @@ EN/JA 문구는 사용자가 명시 승인한 문구가 아니며 작성자의 - [x] 홈 `channelDonations`는 전체 현재 월 공개·비밀 후보 중 최신 8건을 유지하며 새 home count 필드를 추가하지 않는다 (`SDA-001`). - [x] 후원 탭 공개/비밀 모두 건수·페이지·hasNext에 포함된다 (`SDA-002`). - [x] 비밀 후원은 크리에이터와 해당 후원자 본인만 원본을 보고 다른 사용자는 익명 문구와 이미지 자산을 본다. 공개 후원은 모두 원본을 본다 (`SDA-003`, `SDA-004`). -- [x] `isSecret`은 모든 응답 항목에 Boolean으로 직렬화되고 원래 상태를 나타낸다 (`SDA-005`). +- [x] 최초 구현에서 저장된 비밀 상태를 Boolean으로 직렬화했다 (과거 기준, `DEC-008`로 의미 변경). +- [x] 현재 조회자에게 익명 처리된 항목만 응답 `isSecret=true`이며 원본 반환 항목은 false다. 조회 후에도 DB의 비밀 설정은 동일하다 (`SDA-005`, `P2-T1`). - [x] `can`, `createdAtUtc`, 기존 인증·차단·크리에이터 오류 및 랭킹 동작이 유지된다. - [x] URL 계약과 설정된 CDN 접근을 검증했다. HTTP 200, `image/png`, 19,331 bytes (`SDA-007`). - [x] HTTP MockMvc E2E로 홈과 후원 탭의 익명/본인/크리에이터 응답을 검증했다. 별도 live application server 요청은 수행하지 않았다. @@ -150,6 +159,7 @@ EN/JA 문구는 사용자가 명시 승인한 문구가 아니며 작성자의 | `SDA-001`, `SDA-003`, `SDA-005`, `SDA-006` | `P1-T1` | 홈 repository/service/controller 및 `CreatorChannelHomeEndToEndTest` | | `SDA-002`~`SDA-006` | `P1-T2` | donation repository/service/controller 및 `CreatorChannelDonationEndToEndTest` | | `SDA-007` | `P1-T1`, `P1-T2`, `P1-GATE` | 두 Task는 확정 URL 계약과 기존 이미지 회귀를 검증하고, Gate는 운영 CDN 접근 가능 여부를 검증 | +| `SDA-005` 정정 | `P2-T1`, `P2-GATE` | 홈·후원 service/HTTP E2E의 역할별 UI flag, DB 원본 및 기존 donationId/숨김 회귀 | ## 12. 과거 문서와의 관계 @@ -167,6 +177,8 @@ EN/JA 문구는 사용자가 명시 승인한 문구가 아니며 작성자의 | 2026-11-04 | `DEC-006` | 확정 | 사용자가 제공한 익명 이미지 파일명은 `anonymous-profile.png`; 기존 `defaultProfileImageUrl()`과 동일하게 `cloud.aws.cloud-front.host`를 사용해 `"$cloudFrontHost/profile/anonymous-profile.png"`로 직접 구성한다. | 파일명 및 default-profile 구성 방식 사용자 확정. 운영 CDN 접근성은 별도 미검증 | `SDA-007`, `EXT-001`, §8.3 | | 2026-11-04 | `DEC-007` | 확정 | 익명 이미지는 마스킹 대상 비밀 후원 행에만 적용하고 공개/본인 원본 이미지와 기존 default-profile fallback은 보존한다. | 기존 표시 정책 유지 | `SDA-003`, `SDA-004`, §8.3 | +| 2026-11-04 | `DEC-008` | 정정 확정 | `DEC-002`의 저장값 전달 계약을 대체한다. 응답 isSecret은 익명 UI 표시 여부이며 공개·본인·내 채널은 false, 타 채널 타인 비밀 후원만 true다. DB 저장값, 원본/익명 내용, 기존 donationId와 숨김 정책은 유지한다. | UI에서 실제 비밀 여부 구분이 필요 없다는 사용자 확정 및 수정 요청 | `SDA-005`, §8.1, `P2-T1` | + ## 14. 변경 관리 요구사항 변경은 Decision Log, 이 PRD, 이 문서 안 API 계약, [plan-task.md](plan-task.md) 순서로 반영한다. 구현 시작 전에는 외부 의존 경계를 다시 확인하고, 진행 체크박스 및 검증 결과를 실제 증거에 따라 누적한다.