Files

18 KiB

PRD: 크리에이터 채널 커뮤니티 탭 API

1. Overview

크리에이터 채널의 커뮤니티 탭에서 조회자가 볼 수 있는 커뮤니티 게시글 전체 개수와 게시글 목록을 페이징 조회하는 API를 제공한다.


2. Problem

  • 크리에이터 채널 홈 API는 커뮤니티 게시글 일부를 홈 화면 요약용으로 조회하지만, 커뮤니티 탭은 전체 개수와 페이징 목록이 필요하다.
  • 기존 홈 API의 커뮤니티 조회 로직이 home 도메인 repository 안에 포함되어 있어, 커뮤니티 탭 API에서 그대로 재사용하려면 홈 도메인에 의존하게 된다.
  • 커뮤니티 게시글 조회 로직은 홈 화면과 커뮤니티 탭에서 모두 쓰일 수 있으므로, 하나의 커뮤니티 조회 도메인으로 분리되어야 한다.
  • 조회자의 성인 콘텐츠 노출 정책이 false이면 19금 커뮤니티 게시글은 전체 개수와 목록에서 모두 제외되어야 한다.
  • legacy /creator-community 목록 조회는 구매 내역 조건과 성인 필터 조건이 섞일 수 있으므로, isAdult=false 조회에서 구매한 19금 게시글이 개수나 목록에 포함되지 않도록 새 v2 조회 정책에서 명확히 보장해야 한다.
  • legacy 커뮤니티 목록은 유료 게시글을 구매하지 않은 조회자에게도 게시글 이미지를 노출했지만, 커뮤니티 탭 API는 유료 미구매 게시글의 이미지도 오디오와 동일하게 null로 내려줘야 한다.

3. Goals

  • 크리에이터 채널 커뮤니티 탭 조회 API를 제공한다.
  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/community로 한다.
  • 클라이언트에서 호출하는 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.community 하위 조립 계층에 둔다.
  • 커뮤니티 게시글 목록, 전체 개수, 구매 여부, 좋아요 수, 댓글 수, 성인 콘텐츠 노출, 유료 이미지/오디오 접근 정책은 API 패키지 밖 커뮤니티 도메인 패키지에 둔다.
  • 크리에이터 채널 홈 API와 커뮤니티 탭 API는 동일한 커뮤니티 조회 도메인을 사용한다.
  • 응답에는 조회 가능한 커뮤니티 게시글 전체 개수, 게시글 목록, page, size, hasNext를 포함한다.
  • 게시글 목록 item에는 게시글 id, 크리에이터 id, 크리에이터 닉네임, 크리에이터 프로필 이미지 URL, 작성 시간 UTC, 게시글 본문, 이미지 URL, 오디오 URL, 가격, 댓글 쓰기 가능 여부, 구매 여부, 좋아요 개수, 댓글 개수, pin 여부를 포함한다.
  • 유료 게시글의 이미지와 오디오 콘텐츠는 조회자가 구매했거나 게시글 작성자인 경우에만 내려준다.
  • 유료 게시글을 구매하지 않은 조회자에게는 이미지 URL과 오디오 콘텐츠 URL을 null로 내려준다.
  • 이미지가 없는 게시글은 imageUrlnull로 내려준다.
  • 조회자의 성인 콘텐츠 노출 정책이 false이면 19금 게시글은 전체 개수와 목록에서 제외한다.
  • 페이징 요청값은 기존 오디오/시리즈 탭 API와 같은 보정 규칙을 따른다.

4. Non-Goals

  • 커뮤니티 게시글 작성, 수정, 삭제 API는 포함하지 않는다.
  • 커뮤니티 게시글 구매 API는 포함하지 않는다.
  • 커뮤니티 댓글 작성, 수정, 삭제, 목록 조회 API는 포함하지 않는다.
  • 커뮤니티 좋아요 생성/취소 API는 포함하지 않는다.
  • legacy /creator-community API의 공개 endpoint 변경은 포함하지 않는다.
  • 크리에이터 채널 홈 API의 공개 응답 스키마 변경은 포함하지 않는다.
  • 홈 API의 커뮤니티 노출 개수나 홈 화면 구성 정책 변경은 포함하지 않는다.
  • DB schema, 운영 DDL, 마이그레이션은 포함하지 않는다.
  • 앱 표시용 상대 시간 문구는 서버에서 새로 조합하지 않는다.

5. Target Users

  • 회원: 크리에이터 채널 커뮤니티 탭에서 크리에이터의 커뮤니티 게시글을 탐색하는 사용자
  • 앱 클라이언트: 커뮤니티 탭 구성에 필요한 전체 개수와 게시글 목록을 단일 API 응답으로 표시하려는 클라이언트
  • 서버 개발자: 홈 API와 커뮤니티 탭 API에서 커뮤니티 조회 정책을 중복 없이 재사용하려는 개발자

6. User Stories

  • 사용자는 크리에이터 채널 커뮤니티 탭에 들어가면 자신이 조회 가능한 게시글 전체 개수를 확인하고 싶다.
  • 사용자는 커뮤니티 게시글을 최신순으로 추가 로딩하고 싶다.
  • 성인 콘텐츠 노출이 꺼진 사용자는 19금 게시글이 개수와 목록에 포함되지 않기를 원한다.
  • 사용자는 이미지가 없는 게시글도 정상적으로 목록에서 확인하고 싶다.
  • 사용자는 구매한 유료 게시글의 오디오 콘텐츠를 재생할 수 있어야 한다.
  • 구매하지 않은 사용자는 유료 게시글의 이미지 URL과 오디오 콘텐츠 URL을 받지 않아야 한다.
  • 앱 클라이언트는 크리에이터 프로필 이미지, 댓글 작성 가능 여부, 구매 여부, 좋아요 개수, 댓글 개수, pin 여부를 게시글 item에서 바로 확인하고 싶다.
  • 서버 개발자는 홈 API와 커뮤니티 탭 API가 동일한 커뮤니티 조회 도메인을 사용한다는 것을 패키지 의존 방향으로 확인하고 싶다.

7. Core Features

Feature A. 크리에이터 채널 커뮤니티 탭 조회 API

Requirements

  • 신규 API는 크리에이터 채널 전용 v2 API로 작성한다.
  • API endpoint는 GET /api/v2/creator-channels/{creatorId}/community로 한다.
  • 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 범위에 게시글이 없으면 communityPosts는 빈 배열, hasNextfalse로 내려주되 communityPostCount는 전체 개수를 유지한다.

Feature B. 응답 스키마

Requirements

  • 응답 DTO는 구현 전에 명시하고 공개 API 계약으로 관리한다.
  • 응답 최상위 DTO 이름은 CreatorChannelCommunityTabResponse로 한다.
  • 응답에는 다음 값을 포함한다.
    • communityPostCount: 조회자가 조회 가능한 커뮤니티 게시글 전체 개수
    • communityPosts: 커뮤니티 게시글 목록
    • page: 현재 응답의 page index
    • size: 현재 응답의 page size
    • hasNext: 다음 page 존재 여부
  • communityPostCount는 목록 조회와 같은 공개 여부, 작성자, 성인 콘텐츠 노출, 차단 정책을 적용해 계산한다.
  • communityPostCount에는 현재 page에 포함되지 않은 게시글도 포함한다.
  • communityPostCount는 pinned 게시글과 일반 게시글을 모두 포함한 전체 개수다.
  • page, size는 fallback 보정 이후 실제 적용된 값을 내려준다.
  • hasNext는 같은 조건에서 다음 page에 노출할 게시글이 있으면 true로 내려준다.
  • 응답 스키마 예시는 다음과 같다.
data class CreatorChannelCommunityTabResponse(
    val communityPostCount: Int,
    val communityPosts: List<CreatorChannelCommunityPostResponse>,
    val page: Int,
    val size: Int,
    @JsonProperty("hasNext")
    val hasNext: Boolean
)

data class CreatorChannelCommunityPostResponse(
    val postId: Long,
    val creatorId: Long,
    val creatorNickname: String,
    val creatorProfileUrl: String,
    val createdAtUtc: String,
    val content: String,
    val imageUrl: String?,
    val audioUrl: String?,
    val price: Int,
    @JsonProperty("isCommentAvailable")
    val isCommentAvailable: Boolean,
    val existOrdered: Boolean,
    val likeCount: Int,
    val commentCount: Int,
    @JsonProperty("isPinned")
    val isPinned: Boolean
)

Edge Cases

  • 조회 가능한 커뮤니티 게시글이 없으면 communityPostCount0, communityPosts는 빈 배열, hasNextfalse로 내려준다.
  • 이미지가 없는 게시글은 imageUrlnull로 내려준다.
  • 유료 게시글을 구매하지 않았고 게시글 작성자도 아닌 조회자에게는 이미지가 있는 게시글이어도 imageUrlnull로 내려준다.
  • 오디오가 없는 게시글은 audioUrlnull로 내려준다.
  • isCommentAvailable == false인 게시글의 commentCount는 기존 커뮤니티 목록 정책과 동일하게 0으로 내려준다.
  • Boolean 응답 필드는 Jackson 직렬화 시 commentAvailable, pinned로 바뀌지 않고 isCommentAvailable, isPinned로 내려가야 한다.

Feature C. 커뮤니티 게시글 목록과 개수

Requirements

  • 조회 대상은 지정한 creatorId가 작성한 커뮤니티 게시글로 제한한다.
  • 활성 게시글만 조회한다.
  • 조회자의 성인 콘텐츠 노출 정책이 false이면 19금 게시글은 목록에서 제외한다.
  • 조회자의 성인 콘텐츠 노출 정책이 false이면 19금 게시글은 communityPostCount에서도 제외한다.
  • 성인 콘텐츠 필터는 구매 여부보다 우선 적용한다.
  • 조회자가 19금 게시글을 구매했더라도 성인 콘텐츠 노출 정책이 false이면 해당 게시글은 목록과 전체 개수에 포함하지 않는다.
  • 목록은 pinned 게시글을 먼저 노출하고, 그 다음 일반 게시글을 노출한다.
  • pinned 게시글 사이의 정렬은 fixedAt desc, id desc를 따른다.
  • 일반 게시글 사이의 정렬은 createdAt desc, id desc를 따른다.
  • 목록은 page, size 기준으로 페이징 조회한다.
  • 다음 page 존재 여부는 size + 1개를 조회하거나 동등한 방식으로 판단하되, 응답 목록에는 최대 size개만 내려준다.
  • createdAtUtc는 게시글 생성 시간을 UTC 기준 ISO-8601 문자열로 내려준다.
  • creatorProfileUrl은 크리에이터 프로필 이미지 path가 있으면 기존 CDN URL 조합 정책으로 내려주고, 없으면 기본 프로필 이미지 URL을 내려준다.
  • existOrdered는 조회자가 게시글 작성자이면 true, 조회자가 유효 구매 내역을 가지고 있으면 true, 그 외에는 false로 내려준다.
  • imageUrl은 커뮤니티 게시글 이미지 path가 있고 조회자가 해당 게시글의 유료 미디어에 접근할 수 있을 때만 기존 CDN URL 조합 정책으로 내려준다.
  • likeCount는 활성 좋아요 수를 기준으로 계산한다.
  • commentCount는 조회자가 볼 수 있는 활성 최상위 댓글 수를 기준으로 계산한다.
  • 댓글 수 계산에는 기존 커뮤니티 댓글의 차단 관계와 비밀 댓글 노출 정책을 적용한다.

Edge Cases

  • pinned 게시글과 일반 게시글이 섞여 있어도 전체 목록은 하나의 페이징 결과로 내려준다.
  • pinned 게시글 개수가 page size를 초과하면 첫 page는 pinned 게시글만 포함될 수 있다.
  • 게시글 작성자가 조회자인 경우에도 성인 콘텐츠 노출 정책이 false이면 19금 게시글은 제외한다.
  • 좋아요나 댓글이 없는 게시글은 likeCount, commentCount0으로 내려준다.

Feature D. 유료 이미지와 오디오 콘텐츠 접근 정책

Requirements

  • 커뮤니티 게시글에 이미지 path가 없으면 imageUrlnull이다.
  • 커뮤니티 게시글에 오디오 path가 없으면 audioUrlnull이다.
  • 무료 게시글에 이미지 path가 있으면 CDN URL을 내려준다.
  • 무료 게시글에 오디오 path가 있으면 signed URL을 내려준다.
  • 유료 게시글에 이미지 path가 있고 조회자가 해당 게시글을 구매했으면 CDN URL을 내려준다.
  • 유료 게시글에 오디오 path가 있고 조회자가 해당 게시글을 구매했으면 signed URL을 내려준다.
  • 유료 게시글에 이미지 path가 있고 조회자가 게시글 작성자이면 CDN URL을 내려준다.
  • 유료 게시글에 오디오 path가 있고 조회자가 게시글 작성자이면 signed URL을 내려준다.
  • 유료 게시글에 이미지 path가 있지만 조회자가 구매하지 않았고 게시글 작성자도 아니면 imageUrlnull이다.
  • 유료 게시글에 오디오 path가 있지만 조회자가 구매하지 않았고 게시글 작성자도 아니면 audioUrlnull이다.
  • 이 이미지 제한 정책은 legacy /creator-community 목록의 기존 이미지 노출 동작과 다르며, 커뮤니티 탭 API에서는 오디오 접근 정책과 동일하게 적용한다.
  • 이미지 URL은 signed URL로 만들지 않고 기존 CDN URL 조합 정책만 사용한다.
  • 오디오 signed URL 생성은 기존 AudioContentCloudFront.generateSignedURL 방식을 재사용한다.
  • 오디오 signed URL 만료 시간은 legacy 커뮤니티 목록 정책과 동일하게 30분을 기본으로 한다.
  • 유료 게시글 본문은 기존 크리에이터 채널 홈 API의 유료 커뮤니티 본문 마스킹 정책을 따른다.
  • 유료 게시글 이미지/오디오 접근 여부는 CanUsage.PAID_COMMUNITY_POST의 유효 구매 내역을 기준으로 판단한다.
  • 환불된 구매 내역은 접근 가능 구매로 보지 않는다.

Edge Cases

  • 조회자가 구매했더라도 성인 콘텐츠 노출 정책이 false인 19금 게시글은 목록에 포함되지 않으므로 이미지 URL과 오디오 signed URL도 내려주지 않는다.
  • 구매 내역이 중복으로 있어도 응답 item은 게시글 1개로 중복 없이 내려준다.
  • 이미지 path가 blank이면 imageUrlnull로 내려준다.
  • 오디오 signed URL 생성 대상 path가 blank이면 audioUrlnull로 내려준다.

Feature E. 커뮤니티 조회 도메인 분리

Requirements

  • 커뮤니티 탭 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.community 하위에 둔다.
  • 커뮤니티 게시글 조회 service, 순수 정책, domain model, port, repository는 kr.co.vividnext.sodalive.v2.creator.channel.community 하위에 둔다.
  • 도메인 조회 계층은 API response DTO를 import하지 않는다.
  • 도메인 조회 계층은 API facade나 controller를 import하지 않는다.
  • 의존 방향은 항상 v2.api.creator.channel.community -> v2.creator.channel.community이다.
  • 크리에이터 채널 홈 API는 홈 도메인 내부에 커뮤니티 조회 쿼리를 직접 보유하지 않고, 분리된 커뮤니티 조회 도메인을 사용한다.
  • 홈 API의 공개 응답 필드명과 필드 의미는 변경하지 않는다.
  • 홈 API의 커뮤니티 요약 조회 limit와 notice 조회 정책은 기존 동작을 유지한다.
  • legacy kr.co.vividnext.sodalive.explorer.profile.creatorCommunity 쓰기/상세/댓글/좋아요/구매 기능은 이번 분리 대상에 포함하지 않는다.

Edge Cases

  • 홈 API와 커뮤니티 탭 API가 같은 domain model을 사용하더라도 각 API response DTO는 각 API 패키지에서 따로 소유한다.
  • 커뮤니티 도메인 분리 과정에서 기존 홈 API controller mapping과 신규 커뮤니티 탭 controller mapping이 충돌하면 안 된다.
  • 도메인 분리 후 v2.creator.channel.community 하위에서 v2.api.* import 검색 결과가 0건이어야 한다.

8. Technical Constraints

  • 빌드 도구는 Gradle Wrapper(./gradlew)를 사용한다.
  • Kotlin + Spring Boot 2.7.14 기존 스타일을 따른다.
  • 신규 공개 API 스키마는 구현 전에 PRD와 구현 계획/TASK 문서에 명시한다.
  • 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.community 하위에 둔다.
  • API 조립 계층은 HTTP 계약과 공개 응답 변환만 담당한다.
  • 도메인 조회 코드는 kr.co.vividnext.sodalive.v2.creator.channel.community 하위에 둔다.
  • 도메인 패키지는 kr.co.vividnext.sodalive.v2.api.* 패키지에 의존하지 않는다.
  • 기존 크리에이터 채널 홈/라이브/오디오/시리즈 API의 인증, 예외, 성인 콘텐츠 노출, 차단 관계 정책을 재사용한다.
  • 성인 콘텐츠 노출 여부는 기존 v2 탭 API와 동일하게 MemberContentPreferenceServiceisAdultVisibleByPolicy를 기준으로 계산한다.
  • 페이징 응답은 기존 오디오/시리즈 탭 API와 같은 page, size, hasNext 패턴을 따른다.
  • 이미지 URL은 기존 String?.toCdnUrl(cloudFrontHost) 방식과 같은 CDN URL 조합 정책을 따른다.
  • 오디오 URL은 콘텐츠 CloudFront signed URL 생성 정책을 따른다.
  • createdAtUtc 변환은 기존에 재사용 가능한 toUtcIso 확장함수가 있으면 신규 private 확장함수를 만들지 않고 기존 확장함수를 사용한다.
  • 날짜 응답은 UTC 기준 ISO-8601 문자열로 내려준다.

9. Metrics

  • 커뮤니티 탭 API 성공/실패 건수
  • 커뮤니티 탭 API 응답 시간
  • 커뮤니티 탭 추가 로딩 요청 건수
  • 성인 콘텐츠 노출 정책이 false인 조회에서 19금 게시글이 개수와 목록에 포함되지 않는 테스트 통과 여부
  • 유료 게시글 이미지 CDN URL/null 처리와 오디오 signed URL/null 처리 테스트 통과 여부
  • 홈 API 커뮤니티 요약 조회 회귀 테스트 통과 여부
  • v2.creator.channel.community 도메인 패키지의 v2.api.* import 검색 결과 0건 여부

10. Open Questions

  • 없음. 구현 중 새 정책 결정이 필요하면 구현 전에 이 PRD와 plan-task.md를 먼저 갱신한다.