Files
sodalive-backend-spring-boot/docs/20260622_크리에이터_채널_후원_탭_API/prd.md

17 KiB

PRD: 크리에이터 채널 후원 탭 API

1. Overview

크리에이터 채널의 후원 탭에서 전체 채널 후원 개수, 후원 순위 Top 8, 채널 후원 목록을 페이징 조회하는 API를 제공한다.


2. Problem

  • 크리에이터 채널 홈 API는 후원 섹션에 최신 채널 후원 일부만 제공한다.
  • 후원 탭은 홈 요약보다 더 많은 채널 후원 목록을 추가 로딩해야 하고, 전체 채널 후원 개수와 후원 순위 Top 8을 함께 표시해야 한다.
  • 레거시 채널 후원 목록 API는 /explorer/profile/channel-donation에 있고, V2 크리에이터 채널 탭 API의 패키지 분리 구조와 맞지 않는다.
  • 후원 순위는 기존 레거시 CreatorDonationRankingQueryRepository.getMemberDonationRanking 결과와 동일해야 하므로 새 집계 기준을 임의로 만들면 안 된다.
  • 레거시 프로필의 후원 순위는 크리에이터 설정에 따라 비공개, 주간 공개, 전체 공개가 가능하므로 후원 탭 API도 같은 공개 범위와 기간 정책을 따라야 한다.
  • 신규 API는 기존 V2 크리에이터 채널 탭과 동일하게 공개 API 조립 계층과 도메인 조회 계층을 분리해야 한다.

3. Goals

  • 크리에이터 채널 후원 탭 조회 API를 제공한다.
  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/donations로 한다.
  • 클라이언트에서 호출하는 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.donation 하위 조립 계층에 둔다.
  • 후원 개수, 후원 순위, 후원 목록, 페이징 보정, 비공개 후원 노출 조건 같은 조회 책임은 API 패키지 밖의 kr.co.vividnext.sodalive.v2.creator.channel.donation 도메인 조회 계층에 둔다.
  • 응답에는 조회 가능한 전체 채널 후원 개수, 후원 순위 Top 8, 채널 후원 목록, page, size, hasNext를 포함한다.
  • 채널 후원 목록 item의 내용은 크리에이터 채널 홈 API의 channelDonations 섹션과 동일한 필드 의미를 사용한다.
  • 후원 순위 Top 8 item은 기존 MemberDonationRankingResponse와 동일한 결과 리스트 구조를 사용한다.
  • 페이징 요청값은 page 기본값 0, size 기본값 20, size 허용 범위 20..50으로 보정한다.
  • V2 패키지에 있는 기존 크리에이터 채널 탭 패턴과 홈 후원 섹션 조회 로직 중 재사용 가능한 것을 확인하고 재사용한다.

4. Non-Goals

  • 채널 후원 생성 API는 포함하지 않는다.
  • 채널 후원 수정, 삭제, 환불 API는 포함하지 않는다.
  • 후원 순위 산식, 포함 CanUsage, 정렬 기준 변경은 포함하지 않는다.
  • 크리에이터의 후원 순위 노출 설정 변경 API는 포함하지 않는다.
  • 레거시 /explorer/profile/channel-donation endpoint나 응답 스키마 변경은 포함하지 않는다.
  • 크리에이터 채널 홈 API의 공개 응답 스키마 변경은 포함하지 않는다.
  • DB schema, 운영 DDL, 마이그레이션은 포함하지 않는다.
  • 후원 메시지 기본 문구 조합 정책을 새로 만들지 않는다.

5. Target Users

  • 회원: 크리에이터 채널 후원 탭에서 다른 팬들의 채널 후원 내역과 후원 순위를 확인하는 사용자
  • 크리에이터: 자신의 채널 후원 내역과 후원 순위를 확인하는 사용자
  • 앱 클라이언트: 후원 탭 구성에 필요한 개수, 랭킹, 목록, 추가 로딩 상태를 단일 API 응답으로 표시하려는 클라이언트
  • 서버 개발자: 레거시 후원 저장 구조와 랭킹 쿼리를 보존하면서 V2 조회 계층을 분리하려는 개발자

6. User Stories

  • 사용자는 크리에이터 채널 후원 탭에 들어가면 전체 채널 후원 개수를 확인하고 싶다.
  • 사용자는 해당 크리에이터의 후원 순위 Top 8을 확인하고 싶다.
  • 사용자는 크리에이터가 후원 순위를 공개하지 않은 채널에서는 후원 순위 없이 채널 후원 목록만 확인한다.
  • 크리에이터는 후원 순위를 공개하지 않은 경우에도 본인 채널에서 자신의 후원 순위를 확인하고 싶다.
  • 사용자는 채널 후원 목록을 최신순으로 추가 로딩하고 싶다.
  • 사용자는 후원자 닉네임, 프로필 이미지, 후원 캔 수, 메시지, 후원 시간을 목록 item에서 바로 확인하고 싶다.
  • 앱 클라이언트는 page, size, hasNext를 이용해 추가 로딩 상태를 안정적으로 제어하고 싶다.
  • 서버 개발자는 API DTO가 도메인 조회 계층으로 새어 들어가지 않는 패키지 의존 방향을 유지하고 싶다.

7. Core Features

Feature A. 크리에이터 채널 후원 탭 조회 API

Requirements

  • 신규 API는 크리에이터 채널 전용 V2 API로 작성한다.
  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/donations로 한다.
  • creatorId는 path variable로 받는다.
  • 채널 후원 추가 로딩을 위해 page, size query parameter를 받는다.
  • page는 0부터 시작하는 page index로 처리한다.
  • page를 보내지 않으면 기본값 0을 사용한다.
  • size를 보내지 않으면 기본값 20을 사용한다.
  • page가 0보다 작으면 0으로 보정한다.
  • size가 20보다 작으면 20으로 보정한다.
  • size가 50보다 크면 50으로 보정한다.
  • API는 인증 회원만 조회할 수 있어야 한다.
  • 비회원이 조회하면 기존 인증 필요 API와 동일하게 common.error.bad_credentials 계열 오류를 반환한다.
  • 조회 대상 회원이 존재하지 않으면 기존 정책과 동일하게 member.validation.user_not_found 계열 오류를 반환한다.
  • 조회 대상 회원이 크리에이터가 아니면 기존 정책과 동일하게 member.validation.creator_not_found 계열 오류를 반환한다.
  • 조회자와 크리에이터 사이에 차단 관계가 있으면 기존 크리에이터 채널 접근 정책과 동일하게 접근 차단 오류를 반환한다.
  • 조회 가능한 채널 후원이 없어도 전체 API는 성공 처리한다.

Edge Cases

  • page가 0보다 작거나 size가 허용 범위를 벗어나도 400 오류를 반환하지 않고 실제 적용값으로 보정한다.
  • 요청한 page 범위에 채널 후원이 없으면 donations는 빈 배열, hasNextfalse로 내려주되 donationCount는 전체 개수를 유지한다.
  • 조회자 본인이 크리에이터인 경우에도 같은 응답 스키마를 사용한다.

Feature B. 응답 스키마

Requirements

  • 응답 DTO는 구현 전에 명시하고 공개 API 계약으로 관리한다.
  • 응답 최상위 DTO 이름은 CreatorChannelDonationTabResponse로 한다.
  • 응답에는 다음 값을 포함한다.
    • donationCount: 조회자가 조회 가능한 전체 채널 후원 개수
    • rankings: 후원 순위 Top 8 목록
    • donations: 채널 후원 목록
    • page: 현재 응답의 page index
    • size: 현재 응답의 page size
    • hasNext: 다음 page 존재 여부
  • donationCount는 현재 page에 포함되지 않은 채널 후원도 포함한다.
  • rankings는 최대 8개만 내려준다.
  • rankings item은 기존 MemberDonationRankingResponse와 동일하게 userId, nickname, profileImage, donationCan을 포함한다.
  • donations item은 크리에이터 채널 홈 API의 CreatorChannelDonationResponse와 동일하게 nickname, profileImageUrl, can, message, createdAtUtc를 포함한다.
  • page, size는 fallback 보정 이후 실제 적용된 값을 내려준다.
  • hasNext는 같은 조건에서 다음 page에 노출할 채널 후원이 있으면 true로 내려준다.
  • 응답 스키마 예시는 다음과 같다.
data class CreatorChannelDonationTabResponse(
    val donationCount: Int,
    val rankings: List<MemberDonationRankingResponse>,
    val donations: List<CreatorChannelDonationResponse>,
    val page: Int,
    val size: Int,
    @JsonProperty("hasNext")
    val hasNext: Boolean
)

data class MemberDonationRankingResponse(
    @JsonProperty("userId") val userId: Long,
    @JsonProperty("nickname") val nickname: String,
    @JsonProperty("profileImage") val profileImage: String,
    @JsonProperty("donationCan") val donationCan: Int
)

data class CreatorChannelDonationResponse(
    val nickname: String,
    val profileImageUrl: String,
    val can: Int,
    val message: String,
    val createdAtUtc: String
)

Edge Cases

  • 조회 가능한 채널 후원이 없으면 donationCount0, donations는 빈 배열, hasNextfalse로 내려준다.
  • 노출 가능한 후원 순위가 없으면 rankings는 빈 배열로 내려준다.
  • 크리에이터가 후원 순위를 공개하지 않았고 조회자가 크리에이터 본인이 아니면 채널 후원 목록은 정상 조회하되 rankings만 빈 배열로 내려준다.
  • 작성자 프로필 이미지가 없으면 기존 V2 크리에이터 채널 API와 동일하게 기본 프로필 이미지 URL을 내려준다.
  • createdAtUtcChannelDonationMessage.createdAt을 UTC 기준 ISO-8601 문자열로 내려준다.
  • Boolean 응답 필드는 Jackson 직렬화 필드명을 명시한다.

Feature C. 전체 채널 후원 개수와 목록

Requirements

  • 조회 대상은 지정한 creatorId의 채널 후원 메시지로 제한한다.
  • 저장 엔티티는 기존 ChannelDonationMessage를 사용한다.
  • 채널 후원 목록은 크리에이터 채널 홈 API의 후원 섹션과 동일하게 현재 KST 월 범위의 후원 메시지를 대상으로 한다.
  • 현재 KST 월 범위는 now를 UTC로 받은 뒤 Asia/Seoul 기준 월 시작 이상, 다음 달 월 시작 미만으로 변환해 계산한다.
  • 전체 채널 후원 개수는 목록과 같은 creator, 월 범위, 비공개 후원 노출 조건을 적용해 계산한다.
  • 목록 정렬은 최신순을 기본으로 하며 createdAt desc, id desc를 따른다.
  • 다음 page 존재 여부는 size + 1개를 조회하거나 동등한 방식으로 판단하되, 응답 목록에는 최대 size개만 내려준다.
  • 후원자 닉네임은 ChannelDonationMessage.member.nickname을 사용하고 기존 삭제 회원 prefix 제거 정책을 적용한다.
  • 후원자 프로필 이미지는 ChannelDonationMessage.member.profileImage를 기존 CDN URL 조합 정책으로 변환한다.
  • 후원 캔 수는 ChannelDonationMessage.can을 사용한다.
  • 후원 메시지는 크리에이터 채널 홈 API와 동일하게 ChannelDonationMessage.additionalMessage가 없으면 빈 문자열로 내려준다.
  • 후원 시간은 ChannelDonationMessage.createdAt을 UTC 기준 ISO-8601 문자열로 변환한다.

Edge Cases

  • 조회자가 크리에이터 본인이면 해당 크리에이터의 비공개 후원까지 목록과 개수에 포함한다.
  • 조회자가 크리에이터 본인이 아니면 공개 후원과 조회자 본인의 비공개 후원만 목록과 개수에 포함한다.
  • 비회원 조회는 허용하지 않으므로 비회원 기준 비공개 후원 필터는 별도로 만들지 않는다.
  • 같은 회원이 여러 번 후원한 경우 목록에서는 각각 별도 item으로 내려준다.

Feature D. 후원 순위 Top 8

Requirements

  • 후원 순위는 기존 레거시 CreatorDonationRankingQueryRepository.getMemberDonationRanking 결과와 동일해야 한다.
  • API 응답에는 Top 8만 내려준다.
  • 호출 offset은 0, limit은 8을 사용한다.
  • 순위 산식과 포함 후원 유형은 레거시 쿼리 기준을 따른다.
    • CanUsage.DONATION
    • CanUsage.SPIN_ROULETTE
    • CanUsage.LIVE
    • CanUsage.CHANNEL_DONATION
  • 환불된 사용 내역은 제외한다.
  • 비활성 회원은 제외한다.
  • 정렬은 레거시 쿼리와 동일하게 donationCan desc, member.id desc를 따른다.
  • 기간은 크리에이터의 donationRankingPeriod 설정을 따른다.
  • donationRankingPeriod가 없으면 DonationRankingPeriod.CUMULATIVE를 사용한다.
  • DonationRankingPeriod.WEEKLY는 기존 레거시 서비스의 주간 범위 계산을 따른다.
  • DonationRankingPeriod.CUMULATIVE는 기존 레거시 서비스의 전체 누적 범위 계산을 따른다.
  • 후원 순위 노출 정책은 기존 프로필 정책과 동일하게 유지한다.
  • 조회자가 크리에이터 본인이거나 크리에이터의 isVisibleDonationRanktrue이면 rankings를 내려준다.
  • 조회자가 크리에이터 본인이 아니고 크리에이터의 isVisibleDonationRankfalse이면 rankings는 빈 배열로 내려준다.
  • 조회자가 크리에이터 본인이 아니고 크리에이터의 isVisibleDonationRankfalse인 경우에도 donationCount, donations, page, size, hasNext는 후원 목록 조건대로 정상 조회한다.
  • donationCan 노출 여부는 기존 프로필 정책과 동일하게 크리에이터 본인 조회 시 실제 값을 내려주고, 일반 회원 조회 시 0으로 내려준다.

Edge Cases

  • 순위 대상 회원이 8명보다 적으면 있는 만큼만 내려준다.
  • 같은 후원 캔 금액이면 레거시 쿼리와 동일하게 회원 ID 내림차순으로 정렬한다.
  • 순위 조회 결과가 없어도 후원 탭 API는 성공 처리한다.
  • 후원 순위 비공개로 rankings가 빈 배열인 경우와 실제 순위 결과가 없어 rankings가 빈 배열인 경우 모두 같은 응답 스키마를 사용한다.

Feature E. V2 재사용 범위와 계층 분리

Requirements

  • 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.donation 하위에 둔다.
  • 후원 탭 조회 service, 순수 정책, domain model, port, QueryDSL repository는 kr.co.vividnext.sodalive.v2.creator.channel.donation 하위에 둔다.
  • 도메인 조회 계층은 API response DTO를 import하지 않는다.
  • 도메인 조회 계층은 API facade나 controller를 import하지 않는다.
  • 의존 방향은 항상 v2.api.creator.channel.donation -> v2.creator.channel.donation이다.
  • 페이징 값 보정과 offset, fetchLimit 계산은 기존 CreatorChannelPage 패턴을 재사용한다.
  • page, size, hasNext, limitItems 정책은 기존 FanTalk/커뮤니티/시리즈 탭의 query policy 패턴을 재사용한다.
  • 인증 회원 확인, creator role 검증, 채널 차단 접근 오류는 기존 V2 크리에이터 채널 탭 API와 같은 흐름을 따른다.
  • 프로필 이미지 CDN URL 변환과 기본 프로필 이미지 URL은 기존 V2 크리에이터 채널 API 정책을 따른다.
  • UTC ISO 변환은 기존 toUtcIso 확장 함수 또는 같은 의미의 기존 V2 변환 방식을 재사용한다.
  • 홈 API의 findChannelDonations 조회 조건과 응답 필드는 참고하되, 홈 도메인 repository에 후원 탭 페이징 책임을 추가하지 않는다.
  • 후원 순위는 레거시 repository 또는 같은 쿼리 기준을 감싼 V2 port를 통해 재사용한다.
  • 레거시 채널 후원 목록 API의 기본 메시지 조합(buildMessage)은 이번 V2 후원 탭 목록 응답에 재사용하지 않는다.

Edge Cases

  • 신규 donation 도메인 패키지에서 v2.api.* import 검색 결과가 0건이어야 한다.
  • 홈 API의 channelDonations 공개 응답 의미는 변경하지 않는다.
  • legacy 후원 생성/목록 기능은 기존 패키지에 남겨두고 이번 조회 계층 분리 대상에 포함하지 않는다.

8. Technical Constraints

  • 빌드 도구는 Gradle Wrapper(./gradlew)를 사용한다.
  • 언어/런타임은 Kotlin + Java 17을 따른다.
  • 프레임워크는 Spring Boot 2.7.14를 따른다.
  • 기존 Kotlin/Spring 스타일과 ktlint 규칙을 따른다.
  • QueryDSL 조회는 기존 V2 크리에이터 채널 탭 repository 패턴을 따른다.
  • 공개 API 스키마는 구현 중 임의 변경하지 않고, 변경이 필요하면 PRD와 구현 계획/TASK 문서를 먼저 갱신한다.

9. Decisions

  • endpoint는 GET /api/v2/creator-channels/{creatorId}/donations로 확정한다.
  • page는 0 기반 page index로 처리한다.
  • page 기본값은 0, size 기본값은 20으로 한다.
  • page가 0 미만이면 0으로 보정한다.
  • size가 20 미만이면 20, 50 초과이면 50으로 보정한다.
  • 채널 후원 목록 item은 크리에이터 채널 홈 API의 CreatorChannelDonationResponse와 같은 필드 의미를 사용한다.
  • 후원 순위 Top 8 item은 기존 MemberDonationRankingResponse와 같은 필드 의미를 사용한다.
  • 후원 순위 산식은 CreatorDonationRankingQueryRepository.getMemberDonationRanking 기준을 변경하지 않는다.
  • 후원 순위 공개 여부는 isVisibleDonationRank, 기간은 donationRankingPeriod 기준으로 판단한다.
  • 채널 후원 목록과 개수의 기간은 홈 후원 섹션과 동일하게 현재 KST 월 범위로 한다.

10. Open Questions

  • 없음. 구현 중 공개 응답 필드 추가나 기간 정책 변경이 필요하면 이 PRD를 먼저 갱신한다.