173 lines
15 KiB
Markdown
173 lines
15 KiB
Markdown
# 크리에이터 채널 비밀 후원 익명 조회 PRD
|
|
|
|
## 문서 정보
|
|
|
|
| 항목 | 내용 |
|
|
|---|---|
|
|
| 문서 상태 | 구현 및 기능 범위 Gate 완료, 전체 테스트 suite 검증은 timeout으로 미완료 |
|
|
| 작성일 | 2026-11-04 |
|
|
| 최종 수정일 | 2026-11-04 |
|
|
| 대상 제품 | 크리에이터 채널 비밀 후원 익명 조회 |
|
|
| 작성자·결정권자 | 제품 요청 기준 |
|
|
| 관련 API Contract | 이 문서 §8 |
|
|
| 관련 구현 계획 | [plan-task.md](plan-task.md) |
|
|
| 관련 review | 없음 |
|
|
|
|
## 1. Overview
|
|
|
|
크리에이터 채널 홈과 후원 탭에서 비밀 후원 행은 그대로 보이되, 크리에이터와 후원자 본인 외의 조회자에게 후원자의 식별 정보와 메시지를 숨긴다. 조회 응답은 비밀 여부를 모든 사용자에게 표시해 클라이언트가 비밀 후원을 식별할 수 있게 한다. 마스킹 대상 비밀 후원 이미지 URL은 기존 `default-profile`과 같은 방식으로 `cloud.aws.cloud-front.host`에 `/profile/anonymous-profile.png`를 붙여 구성한다. 구현 및 범위 한정 검증을 마쳤고, 전체 테스트 suite는 실행 제한 시간 내 완료하지 못했다.
|
|
|
|
## 2. Problem Statement
|
|
|
|
현재 두 조회 경로는 비밀 후원을 일반 조회자에게 결과에서 제거한다. 그 결과 후원 건수와 목록의 의미가 조회자에 따라 달라지고, 사용자는 비밀 후원이 있었는지 확인할 수 없다. 해결 후에는 건수와 행은 유지하면서 권한에 맞게 표시 필드만 가려야 한다.
|
|
|
|
## 3. Goals
|
|
|
|
### 3.1 제품 목표
|
|
|
|
- 홈의 `channelDonations`와 후원 탭 `donations`에 현재 KST 월의 비밀 후원을 포함한다.
|
|
- 비밀 후원에 한해 크리에이터 본인 또는 해당 후원자 본인에게 원래 닉네임, 메시지, 프로필 이미지를 보여 준다. 공개 후원은 기존처럼 모든 인증 조회자에게 원본을 보여 준다.
|
|
- 비밀 후원의 나머지 조회자에게 확정된 익명 표시 문구와 `"$cloudFrontHost/profile/anonymous-profile.png"` 이미지를 보여 준다. 공개 후원과 크리에이터·후원자 본인의 프로필 이미지 및 기존 default-profile fallback은 변경하지 않는다.
|
|
- 두 응답의 모든 후원 항목에 저장된 비밀 여부를 나타내는 `isSecret`을 제공한다.
|
|
|
|
### 3.2 UX 목표
|
|
|
|
- 익명화된 후원도 목록의 건수와 정렬을 유지해 숨겨진 행으로 오해되지 않게 한다.
|
|
- 메시지가 null 또는 빈 문자열이어도 비공개 문구를 표시한다.
|
|
|
|
## 4. Non-Goals
|
|
|
|
- 후원 작성, 수정, 취소, DB 스키마 또는 DDL 변경.
|
|
- 공개 후원의 표시 변경.
|
|
- 랭킹의 노출·집계·익명성 변경. 기존 랭킹 식별 정보와 비밀 후원 집계는 유지한다.
|
|
- 플랫폼 전체에서 후원자를 알아볼 수 없게 하는 익명성 보장. 이 변경은 두 목록의 특정 필드만 가린다.
|
|
- 인증, 차단, 크리에이터 검증, API route, pagination 정책 변경.
|
|
- 기존 `20260622` 문서 수정. 본 문서는 목록의 비밀 후원 노출, 건수 및 `isSecret` 표시 범위에 한해 후속 기준이며 그 외 역사적 결정은 대체하지 않는다.
|
|
|
|
## 5. Target Users and Permissions
|
|
|
|
| 사용자 | 조회 결과 |
|
|
|---|---|
|
|
| 채널 크리에이터 (`viewerId == creatorId`) | 공개 및 비밀 후원 모두 원본 필드 |
|
|
| 후원자 본인 (`viewerId == donor.memberId`) | 자신의 공개 및 비밀 후원 원본 필드 |
|
|
| 그 외 인증 조회자 | 공개 후원은 원본 필드, 비밀 후원은 행을 유지하고 닉네임·메시지·프로필 이미지를 익명 표시 |
|
|
|
|
- 기존 인증 및 크리에이터 조회 권한을 그대로 적용한다.
|
|
- 기존 차단 확인과 크리에이터 역할 검증을 변경하지 않는다.
|
|
- 비밀 후원 목록 행에서 새 `memberId` 또는 donation 식별자를 응답에 노출하지 않는다.
|
|
|
|
## 6. 핵심 사용자 흐름
|
|
|
|
1. 인증 사용자가 크리에이터 채널 홈 또는 후원 탭을 조회한다.
|
|
2. 서버는 기존 권한·차단·크리에이터 검증을 수행하고 현재 KST 월 후원 목록을 가져온다.
|
|
3. 비밀 후원은 크리에이터와 해당 후원자만 원본 필드를 받고, 그 외 조회자는 익명화된 값을 받는다. 공개 후원은 기존대로 인증 조회자에게 원본 필드를 제공한다.
|
|
4. 모든 항목은 `isSecret`으로 원래 비밀 상태를 확인할 수 있고 기존 정렬·건수·pagination을 따른다.
|
|
|
|
## 7. 기능 요구사항
|
|
|
|
요구사항과 파일명·URL 구성 계약은 확정됐고 구현됐다. 설정된 CDN에서 익명 이미지 URL의 실제 접근도 확인됐다.
|
|
|
|
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
|
|---|---|---|---|---|
|
|
| `SDA-001` | 확정 | 홈 `channelDonations`에 현재 KST 월의 공개·비밀 후원을 포함한다. | 현재 월 경계 포함, 이전·다음 월 제외, 전체 후보에서 최신 최대 8건, `createdAt`/id 내림차순. 홈 count 필드는 추가하지 않는다. | §8.1~§8.2, `P1-T1` |
|
|
| `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-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` |
|
|
|
|
## 8. API 계약
|
|
|
|
### 8.1 공통 동작
|
|
|
|
- 기존 인증, block 검사, creator 검증과 응답 envelope를 유지한다.
|
|
- 현재 KST 월 시작은 inclusive, 다음 KST 월 시작은 exclusive이며 저장·비교 시간 변환 규칙은 기존 구현을 유지한다.
|
|
- 최신순은 `createdAt DESC, donation.id DESC`다.
|
|
- 비밀 여부는 마스킹 여부와 별개의 persisted 상태다. `isSecret`은 숨김 처리한 행에서도 `true`다.
|
|
- `can`과 `createdAtUtc`는 실제 값 그대로 반환한다.
|
|
- 새 donor `memberId`나 donation ID는 목록 DTO에 추가하지 않는다.
|
|
|
|
### 8.2 Endpoint 계약
|
|
|
|
| Method 및 경로 | 응답 필드 | 목록 및 page 규칙 |
|
|
|---|---|---|
|
|
| `GET /api/v2/creator-channels/{creatorId}/home` | `data.channelDonations[]`에 `nickname`, `profileImageUrl`, `can`, `message`, `createdAtUtc`, `isSecret` | 현재 월 전체 대상 중 최신 최대 8건 |
|
|
| `GET /api/v2/creator-channels/{creatorId}/donations` | `data.donations[]`에 같은 후원 필드와 `isSecret`, `data.donationCount` | 기본 page `0`, 기본 size `20`, size `20..50`, hasNext 기존 규칙 |
|
|
|
|
### 8.3 익명화 및 현지화
|
|
|
|
마스킹 대상 비밀 후원의 `profileImageUrl`은 `cloud.aws.cloud-front.host`를 사용하는 기존 `defaultProfileImageUrl()`과 동일한 직접 문자열 조합으로 `"$cloudFrontHost/profile/anonymous-profile.png"`를 만든다. 이는 임의 사용자 제공 경로에 `toCdnUrl`을 적용하는 계약이 아니다. 기존 구현 근거는 `CreatorChannelHomeQueryService.kt:258`과 `CreatorChannelDonationQueryService.kt:114`의 `defaultProfileImageUrl(): String = "$cloudFrontHost/profile/default-profile.png"`이다. `https://cdn.test`를 host로 쓰는 테스트 기대 URL은 `https://cdn.test/profile/anonymous-profile.png`다.
|
|
|
|
이 익명 URL은 **마스킹 대상 비밀 후원 행에만** 사용한다. 공개 후원과 크리에이터·해당 후원자 본인의 원본 프로필 이미지 및 기존 `default-profile.png` fallback은 그대로 유지한다. 설정된 CDN에서 해당 경로의 이미지 접근을 확인했다. 이 검증은 업로드 주체나 별도 운영 배포 상태를 확인한 것은 아니다.
|
|
|
|
기존 `SodaMessageSource`에서 다음 key를 `Lang.KO`, `Lang.EN`, `Lang.JA`별로 제공한다.
|
|
|
|
| Key | KO | EN 기술 초안 | JA 기술 초안 |
|
|
|---|---|---|---|
|
|
| `explorer.channel_donation.anonymous.nickname` | `익명` | `Anonymous` | `匿名` |
|
|
| `explorer.channel_donation.anonymous.message` | `크리에이터에게만 보여요` | `Only visible to the creator` | `クリエイターにのみ表示されます` |
|
|
|
|
EN/JA 문구는 사용자가 명시 승인한 문구가 아니며 작성자의 기술 초안이다. 구현 review에서 제품 문구 확인 대상으로 고정하고 `SodaMessageSource` 외부 번역 서비스를 도입하지 않는다. null 또는 빈 원본 메시지에도 익명 조회자는 위 문구를 받는다.
|
|
|
|
### 8.4 랭킹 제한
|
|
|
|
랭킹 query·응답·가시성·합계를 변경하지 않는다. 랭킹의 `userId`, 닉네임, 프로필은 기존 규칙대로 노출될 수 있고 집계에는 비밀 후원 기여분이 포함된다. 따라서 이 기능만으로 전체 서비스 차원의 후원자 익명성이 보장되지 않는다.
|
|
|
|
## 9. 외부 의존
|
|
|
|
| ID | 우선순위 | 제공 필요 계약 | 담당 주체 | 구현 영향 | 재개 조건 |
|
|
|---|---:|---|---|---|---|
|
|
| `EXT-001` | 완료 | 파일명 `anonymous-profile.png`; URL 계약 `"$cloudFrontHost/profile/anonymous-profile.png"` (`cloud.aws.cloud-front.host` 사용) | 사용자/제품 자산 제공자 | 익명 조회자의 이미지 필드. 설정된 CDN에서 접근 확인 | HTTP 200, `image/png`, 19,331 bytes 확인. 업로드 작업은 수행하지 않음 |
|
|
|
|
테스트 host `https://cdn.test`의 정확한 기대 URL은 `https://cdn.test/profile/anonymous-profile.png`다. 신규 config, fallback, 공통 helper 또는 임의 사용자 경로용 `toCdnUrl`은 추가하지 않는다. 설정된 CDN 접근 검증은 통과했다. 저장소 필터를 없애 raw secret 필드를 반환하는 코드를 먼저 배포해서는 안 된다.
|
|
|
|
## 10. 보안과 데이터 취급
|
|
|
|
- 익명화는 응답 DTO 이전 서버 서비스 변환에서 수행한다. 익명 뷰어에게 raw 비밀 필드를 보내지 않는다.
|
|
- 저장소가 비밀 행을 조회하도록 바뀌어도 원본 값이 API 출력·로그·오류에 노출되지 않게 서비스 경계를 검증한다.
|
|
- 이 변경으로 donor ID를 새로 반환하지 않는다.
|
|
- 랭킹의 기존 식별 정보가 목록 마스킹을 우회해 보일 수 있음을 문서화하며 전역 익명성을 약속하지 않는다.
|
|
|
|
## 11. 성공 기준과 추적
|
|
|
|
### 11.1 수용 기준
|
|
|
|
- [x] 홈 `channelDonations`는 전체 현재 월 공개·비밀 후보 중 최신 8건을 유지하며 새 home count 필드를 추가하지 않는다 (`SDA-001`).
|
|
- [x] 후원 탭 공개/비밀 모두 건수·페이지·hasNext에 포함된다 (`SDA-002`).
|
|
- [x] 비밀 후원은 크리에이터와 해당 후원자 본인만 원본을 보고 다른 사용자는 익명 문구와 이미지 자산을 본다. 공개 후원은 모두 원본을 본다 (`SDA-003`, `SDA-004`).
|
|
- [x] `isSecret`은 모든 응답 항목에 Boolean으로 직렬화되고 원래 상태를 나타낸다 (`SDA-005`).
|
|
- [x] `can`, `createdAtUtc`, 기존 인증·차단·크리에이터 오류 및 랭킹 동작이 유지된다.
|
|
- [x] URL 계약과 설정된 CDN 접근을 검증했다. HTTP 200, `image/png`, 19,331 bytes (`SDA-007`).
|
|
- [x] HTTP MockMvc E2E로 홈과 후원 탭의 익명/본인/크리에이터 응답을 검증했다. 별도 live application server 요청은 수행하지 않았다.
|
|
|
|
범위 한정 feature gate는 package tests, `ktlintCheck`, `bootJar`, CDN 접근 검증으로 완료했다. 전체 프로젝트 test suite는 제한 시간 timeout으로 완료되지 않았으며 전체 suite 통과를 뜻하지 않는다.
|
|
|
|
### 11.2 추적표
|
|
|
|
| 요구사항 | 구현 계획 | 자동 검증 |
|
|
|---|---|---|
|
|
| `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 접근 가능 여부를 검증 |
|
|
|
|
## 12. 과거 문서와의 관계
|
|
|
|
[`docs/20260622_크리에이터_채널_후원_탭_API/prd.md`](../20260622_크리에이터_채널_후원_탭_API/prd.md)의 문서는 작성 당시의 역사적 기록으로 남긴다. 본 PRD는 홈 및 후원 탭에서 비밀 후원을 제거하던 기존 visibility 동작, 그에 따른 목록 건수, 응답 `isSecret` 부재에 대해서만 후속 기준을 정한다. 랭킹을 비롯한 본 문서에 명시하지 않은 기존 결정은 바꾸지 않는다.
|
|
|
|
## 13. Decision Log
|
|
|
|
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 |
|
|
|---|---|---|---|---|---|
|
|
| 2026-11-04 | `DEC-001` | 확정 | 두 목록은 전체 현재 월 비밀 후원을 포함하고 표시 필드만 권한별 마스킹한다. | 요청에서 확정된 제품 동작 | `SDA-001`~`SDA-006`, §8 |
|
|
| 2026-11-04 | `DEC-002` | 확정 | `isSecret`은 persisted 비밀 상태를 보여 주며 모든 사용자에게 제공한다. | 요청에서 확정된 API 동작 | `SDA-005` |
|
|
| 2026-11-04 | `DEC-003` | 확정 | 랭킹은 변경하지 않고 전역 익명성을 보장하지 않는다. | 요청에서 확정된 범위 | §8.4 |
|
|
| 2026-11-04 | `DEC-004` | 외부 의존 (과거 기록, `DEC-006`으로 정정) | 익명 프로필 이미지는 자산 경로를 받기 전까지 운영 구현 및 완료를 차단한다. | 실제 이미지 제공 필요 | `EXT-001`, `P1-GATE` |
|
|
| 2026-11-04 | `DEC-005` | 기술 초안 | EN/JA 문구는 작성자 초안으로 구현 review 때 제품 확인한다. | 한국어만 정확 문구 승인됨 | `SDA-006` |
|
|
| 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 |
|
|
|
|
## 14. 변경 관리
|
|
|
|
요구사항 변경은 Decision Log, 이 PRD, 이 문서 안 API 계약, [plan-task.md](plan-task.md) 순서로 반영한다. 구현 시작 전에는 외부 의존 경계를 다시 확인하고, 진행 체크박스 및 검증 결과를 실제 증거에 따라 누적한다.
|