Files
sodalive-backend-spring-boot/docs/20260612_크리에이터_채널_홈_API/prd.md

315 lines
18 KiB
Markdown

# PRD: 크리에이터 채널 홈 API
## 1. Overview
크리에이터 채널 신규 페이지의 홈 탭에 필요한 크리에이터 정보, 라이브/오디오/후원/공지/스케줄/시리즈/커뮤니티/팬 Talk/소개/활동/SNS 데이터를 한 번에 조회하는 v2 API를 제공한다.
---
## 2. Problem
- 신규 크리에이터 채널 화면은 기존 `ExplorerService.getCreatorProfile`, `ExplorerService.getCreatorDetail`, 커뮤니티, 후원, 오디오, 라이브, 시리즈 도메인 데이터가 섞여 있어 홈 탭용 API 계약을 먼저 확정해야 한다.
- Figma 홈 화면에는 여러 섹션이 한 스크롤에 배치되어 있으므로 클라이언트가 섹션별 기존 API를 여러 번 호출하면 초기 진입 속도와 계약 관리가 불리하다.
- 공지와 커뮤니티는 같은 커뮤니티 게시글 데이터를 사용하지만 `isFixed` 여부에 따라 홈에서 다른 섹션으로 분리되어야 한다.
- 예약 라이브와 예약 업로드 오디오 콘텐츠를 하나의 스케줄 섹션으로 합쳐 노출해야 하므로 타입과 이동 대상 id를 명확히 내려줘야 한다.
- 활동 지수와 SNS는 기존 구버전 크리에이터 채널 상세(`ExplorerService.getCreatorDetail`)와 의미가 어긋나지 않아야 한다.
---
## 3. Goals
- 크리에이터 채널 홈 탭 첫 화면을 구성하는 단일 조회 API를 제공한다.
- API는 인증 회원만 조회할 수 있도록 제공한다.
- API는 인증 회원 기준 성인 콘텐츠 노출 정책, 차단 관계, 구매 여부 등 기존 도메인 정책을 가능한 한 유지한다.
- 응답 시간은 앱 표시 포맷에 의존하지 않도록 UTC 기준 문자열로 내려준다.
- 공지, 커뮤니티 게시글은 홈 노출에 필요한 게시글 요약 필드를 제공한다.
- 채널 후원은 최신순 8개를 내려준다.
- 오디오 콘텐츠는 최근 업로드 기준 최대 9개를 내려주고, 예약 업로드 전 콘텐츠는 일반 오디오 목록에는 포함하지 않는다.
- 시리즈는 최대 8개를 내려주고, 해당 시리즈에 속한 콘텐츠의 최신 공개일 기준으로 정렬한다.
- 팬 Talk는 가장 최근에 남긴 팬 Talk 1개와 전체 팬 Talk 개수를 함께 내려준다.
- 활동 지수와 SNS는 `ExplorerService.getCreatorDetail`의 계산/필드 의미를 기준으로 확장한다.
---
## 4. Non-Goals
- 이번 범위는 크리에이터 채널 `홈` API만 포함한다.
- Figma 상단 탭의 `라이브`, `오디오`, `시리즈`, `커뮤니티`, `팬Talk`, `후원` 탭별 전체보기/페이징 API는 다음 범위에서 추가한다.
- `화보` 섹션과 화보 활동 지표는 이번 범위에서 제외한다.
- 기존 구버전 크리에이터 채널 API의 공개 스키마는 변경하지 않는다.
- 커뮤니티 글 작성, 팬 Talk 작성, 채널 후원 등록, 팔로우/알림/DM/AI 채팅 실행 API는 포함하지 않는다.
- 관리자 화면 신규 개발은 포함하지 않는다.
- 앱 표시용 다국어 문구, 날짜 포맷, 숫자 단위 축약 표시는 서버에서 처리하지 않는다.
---
## 5. Target Users
- 회원: 크리에이터 채널 홈에서 최신 활동과 대표 콘텐츠를 탐색하는 사용자
- 앱 클라이언트: 홈 탭 진입 시 한 API 응답으로 섹션을 구성하려는 클라이언트
- 크리에이터: 자신의 채널 홈에 공지, 콘텐츠, 후원, 활동 정보가 적절히 노출되기를 원하는 사용자
---
## 6. User Stories
- 사용자는 크리에이터 채널에 진입하면 닉네임, 팔로워 수, AI 채팅 가능 여부, DM 가능 여부를 바로 확인하고 싶다.
- 사용자는 크리에이터가 현재 진행 중인 라이브가 있으면 홈에서 바로 보고 싶다.
- 사용자는 크리에이터가 최근 올린 오디오 콘텐츠와 시리즈를 홈에서 빠르게 탐색하고 싶다.
- 사용자는 고정된 커뮤니티 글은 공지로, 일반 커뮤니티 글은 커뮤니티 섹션으로 구분해 보고 싶다.
- 사용자는 예정된 라이브와 예약 업로드 오디오 콘텐츠를 시간순으로 보고 싶다.
- 사용자는 최근 채널 후원, 팬 Talk, 소개, 활동 지표, SNS 링크를 한 화면에서 확인하고 싶다.
---
## 7. Core Features
### Feature A. 크리에이터 채널 홈 조회 API
#### Requirements
- 신규 API는 메인 페이지 홈 API와 분리된 크리에이터 채널 전용 v2 API로 작성한다.
- 신규 코드 위치는 `kr.co.vividnext.sodalive.v2.creator.channel` 하위 경계를 기본 후보로 한다.
- API endpoint는 `GET /api/v2/creator-channels/{creatorId}/home`을 기본안으로 한다.
- 인증 회원만 조회할 수 있어야 한다.
- 비회원이 조회하면 기존 인증 필요 API와 동일하게 `common.error.bad_credentials` 계열 오류를 반환한다.
- 조회 대상 회원이 존재하지 않으면 기존 정책과 동일하게 `member.validation.user_not_found` 계열 오류를 반환한다.
- 조회 대상 회원이 크리에이터가 아니면 기존 정책과 동일하게 `member.validation.creator_not_found` 계열 오류를 반환한다.
- 조회자와 크리에이터 사이에 차단 관계가 있으면 구버전 채널 접근 정책과 동일하게 접근 차단 오류를 반환한다.
- 섹션별 데이터가 부족하면 가능한 만큼만 내려주고 전체 API는 성공 처리한다.
- 섹션 데이터가 없으면 빈 배열 또는 `null`로 내려주되, 응답 스키마는 유지한다.
#### Edge Cases
- 조회자 본인이 크리에이터인 경우에도 같은 응답 스키마를 사용한다.
### Feature B. 크리에이터 기본 정보
#### Requirements
- 크리에이터 기본 정보에는 다음 값을 포함한다.
- `creatorId`
- `nickname`
- `profileImageUrl`
- `followerCount`
- `isAiChatAvailable`
- `isDmAvailable`
- `isFollow`
- `isNotify`
- `followerCount`는 활성 팔로우 수 기준으로 계산한다.
- `isAiChatAvailable`은 해당 `Member`와 연결된 활성 `ChatCharacter`가 있는지로 판단한다. 구현 후보는 `ChatCharacterRepository.existsByCreatorMemberId(creatorId)`를 기준으로 한다.
- `isDmAvailable``member.memberKind != MemberKind.AI_CHARACTER`이면 `true`, `AI_CHARACTER`이면 `false`로 판단한다.
- `isFollow`, `isNotify`는 인증 회원의 기존 `CreatorFollowing` 상태를 기준으로 내려준다.
#### Edge Cases
- 프로필 이미지가 없으면 기존 기본 프로필 이미지 URL을 내려준다.
- AI 캐릭터 크리에이터는 AI 채팅 가능 여부가 `true`일 수 있지만 DM 가능 여부는 `false`일 수 있다.
### Feature C. 현재 진행 중인 라이브
#### Requirements
- 크리에이터가 현재 진행 중인 라이브를 내려준다.
- 현재 진행 중인 라이브는 기존 라이브 도메인의 `LiveRoomStatus.NOW` 의미와 동일하게 판단한다.
- 응답에는 라이브 ID, 제목, 커버 이미지, 시작 시각 UTC, 유료 여부 또는 가격, 성인 여부, 예약 여부가 아닌 현재 라이브 여부를 포함한다.
- 조회자의 성인 콘텐츠 노출 정책과 차단 정책을 반영한다.
#### Edge Cases
- 현재 진행 중인 라이브가 없으면 `currentLive``null`로 내려준다.
### Feature D. 신규 오디오 콘텐츠
#### Requirements
- Figma 홈 상단에 노출되는 신규 오디오 콘텐츠 영역에 사용할 최신 공개 오디오 콘텐츠를 내려준다.
- 예약 공개 전 콘텐츠는 신규 오디오 콘텐츠로 노출하지 않는다.
- 응답 필드는 홈 오디오 콘텐츠 카드와 동일하게 콘텐츠 ID, 제목, duration, 커버 이미지, 가격, 포인트 사용 가능 여부, 성인 여부를 포함한다.
- 정렬은 공개 시각 최신순이다.
#### Edge Cases
- 공개된 오디오 콘텐츠가 없으면 `latestAudioContent``null`로 내려준다.
### Feature E. 채널 후원
#### Requirements
- 채널 후원은 최신순 최대 8개를 내려준다.
- 기존 `ChannelDonationService.getChannelDonationList` 응답 필드 의미를 유지한다.
- 조회 범위는 기존 채널 후원 목록과 동일하게 이번 달 기준으로 한다.
- 응답에는 후원 ID, 회원 ID, 닉네임, 프로필 이미지, 후원 can, 비밀 후원 여부, 메시지, 생성 시각 UTC를 포함한다.
- 비밀 후원 표시 정책은 기존 채널 후원 목록 정책을 따른다.
#### Edge Cases
- 후원이 없으면 빈 배열을 내려준다.
### Feature F. 공지
#### Requirements
- 커뮤니티 게시글 중 `isFixed == true`인 글을 홈의 공지 섹션으로 처리한다.
- 응답에는 게시글 ID, 크리에이터 ID, 크리에이터 닉네임, 크리에이터 프로필 이미지, 이미지 URL, 오디오 URL, 본문, 가격, 작성 시각 UTC, 구매 여부, 좋아요 수, 댓글 수를 포함한다.
- 홈 응답에 포함하는 게시글 요약 필드는 기존 크리에이터 커뮤니티 전체보기 게시글 리스트와 같은 의미를 유지한다.
- 정렬은 고정 시각 최신순을 우선하고, 고정 시각이 없으면 작성 시각 최신순으로 한다.
- 공지 최대 노출 개수는 기존 고정 글 제한 정책에 맞춰 최대 3개로 한다.
#### Edge Cases
- 고정 게시글이 없으면 빈 배열을 내려준다.
- 성인 커뮤니티 글은 조회자의 성인 콘텐츠 노출 정책을 따른다.
### Feature G. 스케줄
#### Requirements
- 예약 라이브와 예약 업로드 오디오 콘텐츠를 하나의 스케줄 배열로 내려준다.
- 스케줄은 오늘 날짜와 가장 근접한 예약 항목 최대 3개를 내려준다.
- 예약 라이브는 `LiveRoomStatus.RESERVATION` 의미와 동일하게, `LiveRoom.beginDateTime > now`이고 활성 상태인 라이브를 대상으로 한다.
- 예약 업로드 오디오 콘텐츠는 `AudioContent.releaseDate > now`인 활성 또는 예약 상태 콘텐츠를 대상으로 한다.
- 응답에는 예약 날짜/시간 UTC, 제목, 타입, 대상 ID를 포함한다.
- 타입 값은 기존 추천 페이지의 `RecommendedActivityType` 코드 체계를 사용한다.
- 구현 시 `RecommendedActivityType``CreatorActivityType`으로 이름을 변경하고 공용 패키지로 이동한다.
- 추천 페이지와 크리에이터 채널 홈은 이동된 공용 `CreatorActivityType`을 함께 사용한다.
- 크리에이터 채널 홈 스케줄에서는 `LIVE`, `AUDIO`만 사용한다.
- 오디오 콘텐츠가 `다시보기` 카테고리여도 스케줄 타입은 `LIVE_REPLAY`가 아니라 `AUDIO`로 내려준다.
- 대상 ID는 타입이 `LIVE`이면 라이브 ID, `AUDIO`이면 오디오 콘텐츠 ID를 의미한다.
- 정렬은 예약 날짜/시간 오름차순이다. 같은 예약 시간이면 라이브를 오디오보다 먼저 표시한다.
- 성인 예약 라이브/오디오는 조회자의 성인 노출 정책이 false이면 노출하지 않는다.
- 성인 노출 정책은 DB 조회 조건에 먼저 반영하고, 라이브/오디오 스케줄 후보를 service에서 합친 뒤에도 최종 응답 전 한 번 더 보정한다.
- service 최종 보정에 필요한 성인 여부는 내부 스케줄 후보 record/domain model에만 포함하고, 공개 스케줄 응답 필드에는 포함하지 않는다.
#### Edge Cases
- 예약 데이터가 없으면 빈 배열을 내려준다.
### Feature H. 오디오 콘텐츠 목록
#### Requirements
- 최근 업로드된 오디오 콘텐츠를 최대 9개 내려준다.
- 신규 오디오 콘텐츠 영역과 오디오 목록 영역의 첫 번째 항목이 겹치지 않도록, 오디오 목록에서는 Feature D의 `latestAudioContent`로 내려간 가장 최신 콘텐츠를 제외한다.
- 예약 업로드 전 콘텐츠는 포함하지 않는다.
- 응답에는 다음 값을 포함한다.
- 오디오 콘텐츠 ID
- 제목
- duration
- 이미지
- 가격
- 포인트 사용 가능 여부
- 처음 올린 콘텐츠인지 여부
- 시리즈에 속해 있는 경우 시리즈 이름
- 시리즈에 속해 있는 경우 오리지널 시리즈 여부
- 기존 오디오 콘텐츠 목록의 `isPointAvailable`, `isScheduledToOpen`, 구매/대여 상태 의미를 유지한다.
- `처음 올린 콘텐츠인지 여부`는 해당 크리에이터의 공개 오디오 콘텐츠 중 공개 순서가 첫 번째인지로 판단한다.
- 공개 순서는 공개 시각이 가장 빠른 콘텐츠 1개를 첫 콘텐츠로 판단한다. 동일한 공개 시각이 있으면 `id` 오름차순을 2차 기준으로 한다.
- `오리지널 시리즈 여부`는 콘텐츠가 속한 `Series.isOriginal == true`이면 `true`로 판단한다.
#### Edge Cases
- 시리즈에 속하지 않은 콘텐츠는 시리즈 관련 필드를 `null`로 내려준다.
- 오디오 콘텐츠가 없으면 빈 배열을 내려준다.
### Feature I. 시리즈
#### Requirements
- 시리즈는 최대 8개를 내려준다.
- 정렬은 각 시리즈에 속한 공개 콘텐츠의 최신 공개 시각 내림차순이다.
- 응답에는 기존 시리즈 카드 구성에 필요한 시리즈 ID, 제목, 커버 이미지, 연재 요일, 완결 여부, 콘텐츠 개수, 신규/인기 표시 정보를 포함한다.
- 성인 콘텐츠 노출 정책과 조회자 콘텐츠 타입 선호 정책은 기존 `ContentSeriesService.getSeriesList` 정책을 따른다.
#### Edge Cases
- 공개 콘텐츠가 없는 시리즈를 노출할지 여부는 기존 시리즈 목록 정책을 따른다.
- 시리즈가 없으면 빈 배열을 내려준다.
### Feature J. 커뮤니티
#### Requirements
- 커뮤니티 섹션은 `isFixed == false`인 커뮤니티 게시글만 대상으로 한다.
- 최대 3개를 최신순으로 내려준다.
- 응답에는 게시글 ID, 크리에이터 ID, 크리에이터 닉네임, 크리에이터 프로필 이미지, 이미지 URL, 오디오 URL, 본문, 가격, 작성 시각 UTC, 구매 여부, 좋아요 수, 댓글 수를 포함한다.
- 홈 응답에 포함하는 게시글 요약 필드는 기존 크리에이터 커뮤니티 전체보기 게시글 리스트와 같은 의미를 유지한다.
#### Edge Cases
- 고정 공지는 커뮤니티 섹션에 중복 노출하지 않는다.
- 커뮤니티 게시글이 없으면 빈 배열을 내려준다.
### Feature K. 팬 Talk
#### Requirements
- 팬 Talk는 가장 최근에 남긴 최상위 팬 Talk 1개를 내려준다.
- 전체 팬 Talk 개수를 함께 내려준다.
- 기존 `CreatorCheers`에서 `parent == null`, `isActive == true`인 항목을 팬 Talk 대상으로 본다.
- 최근 팬 Talk 응답에는 팬 Talk ID, 작성자 ID, 작성자 닉네임, 작성자 프로필 이미지, 내용, 언어 코드, 작성 시각 UTC를 포함한다.
- 답글 목록은 홈 팬 Talk 요약에서는 내려주지 않는다.
#### Edge Cases
- 팬 Talk가 없으면 `latestFanTalk``null`, `totalCount``0`으로 내려준다.
- 조회자와 차단 관계가 있는 작성자의 팬 Talk는 기존 팬 Talk 목록 정책과 동일하게 제외한다.
### Feature L. 소개
#### Requirements
- 소개는 `member.introduce` 값을 내려준다.
- 값이 비어 있으면 빈 문자열을 내려준다.
### Feature M. 활동
#### Requirements
- 활동 섹션은 `ExplorerService.getCreatorDetail`의 활동 지표 의미를 기준으로 한다.
- 응답에는 다음 값을 포함한다.
- 데뷔일 UTC
- D-Day 표시 계산에 필요한 경과 일수 또는 `dDay`
- 라이브 진행 횟수
- 라이브 누적 진행 시간
- 라이브 누적 참여자
- 업로드한 오디오 콘텐츠 개수
- 시리즈 개수
- 데뷔일은 첫 라이브 시작 시각과 첫 공개 오디오 콘텐츠 공개 시각 중 빠른 값으로 계산한다.
- 라이브 진행 횟수, 라이브 누적 진행 시간, 라이브 누적 참여자는 기존 `ExplorerQueryRepository.getLiveCount`, `getLiveTime`, `getLiveContributorCount`의 의미를 기준으로 한다.
- 업로드한 오디오 콘텐츠 개수는 공개된 오디오 콘텐츠만 포함하고 예약 업로드는 반영하지 않는다.
- 시리즈 개수는 크리에이터의 활성 시리즈 개수를 기준으로 한다.
#### Edge Cases
- 데뷔일 후보가 없으면 데뷔일은 `null`, `dDay`는 빈 문자열로 내려준다.
- 라이브 진행 시간이 없는 경우 `0`으로 내려준다.
### Feature N. SNS
#### Requirements
- SNS는 `ExplorerService.getCreatorDetail`의 SNS 필드 의미를 기준으로 한다.
- 응답에는 다음 값을 포함한다.
- `instagramUrl`
- `fancimmUrl`
- `xUrl`
- `youtubeUrl`
- `kakaoOpenChatUrl`
- 값이 없으면 빈 문자열 또는 `null` 중 기존 응답 관례를 따른다. 현재 구버전 상세는 빈 문자열을 사용한다.
---
## 8. UX / UI Expectations
- Figma node `296:14890` 기준 홈 화면 섹션 순서는 다음을 따른다.
- 크리에이터 기본 정보
- 홈 탭
- 현재 진행 중인 라이브
- 신규 오디오 콘텐츠
- 채널 후원
- 공지
- 스케줄
- 오디오 콘텐츠
- 시리즈
- 커뮤니티
- 팬 Talk
- 소개
- 활동
- SNS
- Figma에 상단 탭으로 `홈/라이브/오디오/시리즈/화보/커뮤니티/팬Talk/후원`이 보이지만, 이번 API는 홈 탭만 지원한다.
- Figma 활동 섹션에는 `화보` 항목이 보이지만 이번 범위에서는 제외한다.
- 서버는 앱 표시 문구를 조합하지 않고, 앱이 섹션 노출 여부와 텍스트 포맷을 결정할 수 있는 원천 데이터를 내려준다.
---
## 9. Technical Constraints
- 빌드 도구는 Gradle Wrapper(`./gradlew`), 런타임은 Kotlin + Java 17, Spring Boot 2.7.14를 따른다.
- 기존 v2 공개 API처럼 Controller는 `ApiResponse.ok(...)` 형태를 사용한다.
- 신규 API/서비스/DTO는 메인 페이지 홈 패키지(`kr.co.vividnext.sodalive.v2.api.home`)와 섞지 않고 크리에이터 채널 전용 패키지 경계에 둔다.
- 구버전 `explorer`, `content`, `live`, `series` 구현 코드는 응답 의미와 도메인 정책을 맞추기 위한 근거로 참조한다. 신규 크리에이터 채널 홈 API의 application/DTO 경계는 별도로 둔다.
- 시간 응답은 UTC 기준 ISO-8601 문자열을 기본으로 한다.
- 공개 API 스키마는 구현 전 plan-task에서 DTO 필드명과 nullable 정책을 확정한 뒤 변경한다.
- 신규 쿼리는 차단 관계, 비활성 회원, 성인 콘텐츠 노출, 예약 공개 여부를 명시적으로 테스트해야 한다.
---
## 10. Metrics
- 크리에이터 채널 홈 API 응답 성공률
- 크리에이터 채널 홈 API 평균/95퍼센타일 응답 시간
- 섹션별 빈 응답 비율
- 채널 홈 진입 후 라이브/오디오/시리즈/커뮤니티/팬 Talk/후원 탭 이동률
- AI 채팅 버튼, DM 버튼 노출 대비 클릭률
---
## 11. Open Questions
- 없음.