185 lines
17 KiB
Markdown
185 lines
17 KiB
Markdown
# 크리에이터 채널 비밀 후원 익명 조회 PRD
|
|
|
|
## 문서 정보
|
|
|
|
| 항목 | 내용 |
|
|
|---|---|
|
|
| 문서 상태 | 응답 isSecret UI 표시 의미 변경 및 관련 기능 검증 완료 (`P2-GATE`) |
|
|
| 작성일 | 2026-11-04 |
|
|
| 최종 수정일 | 2026-11-04 |
|
|
| 대상 제품 | 크리에이터 채널 비밀 후원 익명 조회 |
|
|
| 작성자·결정권자 | 제품 요청 기준 |
|
|
| 관련 API Contract | 이 문서 §8 |
|
|
| 관련 구현 계획 | [plan-task.md](plan-task.md) |
|
|
| 관련 review | 없음 |
|
|
|
|
## 1. Overview
|
|
|
|
크리에이터 채널 홈과 후원 탭에서 비밀 후원 행은 그대로 보이되, 크리에이터와 후원자 본인 외의 조회자에게 후원자의 식별 정보와 메시지를 숨긴다. 응답 `isSecret`은 현재 조회자에게 익명 UI로 표시해야 하는지를 나타낸다. 마스킹 대상 비밀 후원 이미지 URL은 기존 `default-profile`과 같은 방식으로 `cloud.aws.cloud-front.host`에 `/profile/anonymous-profile.png`를 붙여 구성한다. 최초 구현 검증 이력은 보존하고, 후속 의미 변경은 `P2-T1`에서 별도로 검증한다.
|
|
|
|
## 2. Problem Statement
|
|
|
|
현재 두 조회 경로는 비밀 후원을 일반 조회자에게 결과에서 제거한다. 그 결과 후원 건수와 목록의 의미가 조회자에 따라 달라지고, 사용자는 비밀 후원이 있었는지 확인할 수 없다. 해결 후에는 건수와 행은 유지하면서 권한에 맞게 표시 필드만 가려야 한다.
|
|
|
|
## 3. Goals
|
|
|
|
### 3.1 제품 목표
|
|
|
|
- 홈의 `channelDonations`와 후원 탭 `donations`에 현재 KST 월의 비밀 후원을 포함한다.
|
|
- 비밀 후원에 한해 크리에이터 본인 또는 해당 후원자 본인에게 원래 닉네임, 메시지, 프로필 이미지를 보여 준다. 공개 후원은 기존처럼 모든 인증 조회자에게 원본을 보여 준다.
|
|
- 비밀 후원의 나머지 조회자에게 확정된 익명 표시 문구와 `"$cloudFrontHost/profile/anonymous-profile.png"` 이미지를 보여 준다. 공개 후원과 크리에이터·후원자 본인의 프로필 이미지 및 기존 default-profile fallback은 변경하지 않는다.
|
|
- 두 응답의 `isSecret`은 현재 조회자에게 익명 후원 UI로 표시해야 하는지를 나타낸다. DB의 실제 비밀 여부는 변경하지 않는다.
|
|
|
|
### 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`나 식별 필드를 추가하지 않으며, 현재 구현의 채널 주인 전용 `donationId` 반환 정책을 유지한다.
|
|
|
|
## 6. 핵심 사용자 흐름
|
|
|
|
1. 인증 사용자가 크리에이터 채널 홈 또는 후원 탭을 조회한다.
|
|
2. 서버는 기존 권한·차단·크리에이터 검증을 수행하고 현재 KST 월 후원 목록을 가져온다.
|
|
3. 비밀 후원은 크리에이터와 해당 후원자만 원본 필드를 받고, 그 외 조회자는 익명화된 값을 받는다. 공개 후원은 기존대로 인증 조회자에게 원본 필드를 제공한다.
|
|
4. 클라이언트는 응답 `isSecret` 하나로 익명 후원 UI를 선택한다. 기존 정렬·건수·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`은 현재 조회자에게 익명 처리된 항목일 때만 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` |
|
|
|
|
## 8. API 계약
|
|
|
|
### 8.1 공통 동작
|
|
|
|
- 기존 인증, block 검사, creator 검증과 응답 envelope를 유지한다.
|
|
- 현재 KST 월 시작은 inclusive, 다음 KST 월 시작은 exclusive이며 저장·비교 시간 변환 규칙은 기존 구현을 유지한다.
|
|
- 최신순은 `createdAt DESC, donation.id DESC`다.
|
|
- 응답 `isSecret = 저장된 isSecret && viewerId != creatorId && viewerId != donorId`다. 공개 후원·본인이 한 비밀 후원·내 채널의 비밀 후원은 false, 다른 채널에서 타인이 한 비밀 후원만 true다.
|
|
- 응답값은 실제 비밀 설정을 설명하지 않고 익명 UI 표시 여부만 나타낸다. DB와 repository record의 `isSecret`은 저장값 그대로 유지한다. `isMyDonation` 등 추가 필드는 만들지 않는다.
|
|
- `can`과 `createdAtUtc`는 실제 값 그대로 반환한다.
|
|
- 새 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건 |
|
|
| `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] 최초 구현에서 저장된 비밀 상태를 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 요청은 수행하지 않았다.
|
|
|
|
범위 한정 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 접근 가능 여부를 검증 |
|
|
| `SDA-005` 정정 | `P2-T1`, `P2-GATE` | 홈·후원 service/HTTP E2E의 역할별 UI flag, DB 원본 및 기존 donationId/숨김 회귀 |
|
|
|
|
## 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 |
|
|
|
|
| 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) 순서로 반영한다. 구현 시작 전에는 외부 의존 경계를 다시 확인하고, 진행 체크박스 및 검증 결과를 실제 증거에 따라 누적한다.
|