51 KiB
커뮤니티 게시물 상세 API Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development또는superpowers:executing-plans로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.
Goal: 인증 회원이 커뮤니티 게시물 상세, 댓글 목록, 댓글 답글 목록을 v2 API로 조회할 수 있게 한다.
Architecture: 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.creator.channel.community 조립 계층에 둔다. 게시물 상세/댓글/답글 조회 service, domain model, port, QueryDSL repository는 kr.co.vividnext.sodalive.v2.creator.channel.community 하위에 두고 v2.api.*에 의존하지 않는다. 기존 커뮤니티 탭의 CreatorChannelCommunityPost 조립 정책을 재사용하고, 댓글 저장 엔티티는 legacy CreatorCommunityComment를 사용하되 legacy response DTO는 v2 공개 응답으로 노출하지 않는다.
Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring MVC, Spring Data JPA, QueryDSL, JUnit 5, MockMvc, Gradle Wrapper
0. 구현 전 확정 사항
- 상세 API endpoint:
GET /api/v2/creator-channels/community-posts/{postId} - 댓글 API endpoint:
GET /api/v2/creator-channels/community-posts/{postId}/comments - 답글 API endpoint:
GET /api/v2/creator-channels/community-comments/{commentId}/replies - 인증 정책: 세 API 모두 인증 회원만 조회 가능하다. 비회원은 기존 Security 흐름과
requireMember정책으로 거부한다. - page 기준: 기존 크리에이터 채널 v2 탭 API와 동일한 0 기반 page index다.
- page fallback:
page기본값은0page < 0이면0size기본값은20size < 20이면20size > 50이면50
- 상세 응답 게시물 필드는 커뮤니티 탭의
CreatorChannelCommunityPostResponse와 동일한 의미를 사용하고isLiked를 포함한다. - 상세 응답은
comments: CreatorChannelCommunityCommentsResponse로 첫 댓글 20개를 포함한다. - 댓글 응답은
commentCount,comments,page,size,hasNext를 포함한다. - 댓글 item은
commentId,writerProfileImageUrl,writerNickname,content,createdAtUtc,latestReply를 포함한다. - 답글 응답은
replyCount,replies,page,size,hasNext를 포함한다. - 답글 item은 댓글 item과 같은 작성자/본문/UTC 작성 시간 구조를 사용하지만
latestReply를 포함하지 않는다. - 게시물 조회 정책:
CreatorCommunity.isActive == true- 조회자의 성인 콘텐츠 노출 정책이 false이면 19금 게시물은 조회 불가
- 조회자와 게시물 작성자 사이 활성 차단 관계가 있으면 접근 차단 오류
- 유료 본문/이미지/오디오 접근 정책은 커뮤니티 탭
CreatorChannelCommunityPost와 동일
- 댓글 조회 정책:
- 최상위 댓글:
CreatorCommunityComment.isActive == true,parent is null - 답글:
CreatorCommunityComment.isActive == true,parent.id == commentId - 댓글/답글 작성자가 조회자와 차단/피차단 관계이면 제외
- 비밀 최상위 댓글은 게시물 작성자 또는 댓글 작성자 본인에게만 노출
- 댓글 정렬:
createdAt desc,id desc - latest reply 정렬:
createdAt desc,id desc의 첫 번째 - 답글 목록 정렬:
createdAt desc,id desc
- 최상위 댓글:
isCommentAvailable == false인 게시물은 상세 초기 댓글과 댓글 목록 API 모두commentCount=0, 빈 목록,hasNext=false를 반환한다.createdAtUtc는kr.co.vividnext.sodalive.extensions.toUtcIso를 사용한다.- 프로필 이미지 URL은
String?.toCdnUrl(cloudFrontHost)와 기본 프로필 이미지 URL"$cloudFrontHost/profile/default-profile.png"정책을 따른다. - 탈퇴 회원 닉네임 prefix 제거는
removeDeletedNicknamePrefix()를 적용한다. - legacy
/creator-communityendpoint와 응답 스키마는 변경하지 않는다. - DB schema, 운영 DDL, 마이그레이션은 포함하지 않는다.
1. 파일 구조 계획
API 조립 계층
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityController.kt- 상세/댓글/답글 GET mapping 추가
- 인증 회원 null guard는 기존
requireMember재사용
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/application/CreatorChannelCommunityFacade.ktgetCommunityPostDetail,getCommunityComments,getCommunityReplies추가
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityPostDetailResponse.kt- 상세/댓글/답글 response DTO와 domain 변환 책임
- Test:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityPostDetailResponseTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityControllerTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/application/CreatorChannelCommunityFacadeTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityEndToEndTest.kt
도메인 조회 계층
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityPostDetail.kt- 상세/댓글/답글 domain model
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityQueryPolicy.kt- 기존 page fallback을 댓글/답글에도 재사용하도록 이름 또는 주석 확인
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/port/out/CreatorChannelCommunityQueryPort.kt- 게시물 상세, 댓글, 답글 조회 port method와 record 추가
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryService.kt- 상세/댓글/답글 use case 추가
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/CreatorChannelCommunityQueryRepository.kt- port method 구현 계약 반영
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepository.kt- QueryDSL 상세/댓글/답글 조회 구현
- Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityQueryPolicyTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepositoryTest.kt
기존 파일 확인/재사용
- Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityTabResponse.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityTab.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/domain/CreatorChannelPage.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/common/domain/CdnUrlExtensions.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/extensions/LocalDateTimeExtensions.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/explorer/profile/creatorCommunity/comment/CreatorCommunityComment.kt - Verify:
src/main/kotlin/kr/co/vividnext/sodalive/member/block/BlockMember.kt
문서 산출물
- Create:
docs/20260706_커뮤니티_게시물_상세_API/plan-task.md - Verify:
docs/20260706_커뮤니티_게시물_상세_API/prd.md
2. Response data class 초안
구현 시 src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityPostDetailResponse.kt를 아래 구조로 만든다. 공개 API 계약 변경이 필요하면 구현 전에 PRD와 이 문서를 먼저 갱신한다.
package kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto
import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.extensions.toUtcIso
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComment
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityComments
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityPostDetail
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReplies
import kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityReply
data class CreatorChannelCommunityPostDetailResponse(
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,
@JsonProperty("isLiked")
val isLiked: Boolean,
val comments: CreatorChannelCommunityCommentsResponse
) {
companion object {
fun from(detail: CreatorChannelCommunityPostDetail): CreatorChannelCommunityPostDetailResponse {
val post = detail.post
return CreatorChannelCommunityPostDetailResponse(
postId = post.postId,
creatorId = post.creatorId,
creatorNickname = post.creatorNickname,
creatorProfileUrl = post.creatorProfileUrl,
createdAtUtc = post.createdAt.toUtcIso(),
content = post.content,
imageUrl = post.imageUrl,
audioUrl = post.audioUrl,
price = post.price,
isCommentAvailable = post.isCommentAvailable,
existOrdered = post.existOrdered,
likeCount = post.likeCount,
commentCount = post.commentCount,
isPinned = post.isPinned,
isLiked = post.isLiked,
comments = CreatorChannelCommunityCommentsResponse.from(detail.comments)
)
}
}
}
data class CreatorChannelCommunityCommentsResponse(
val commentCount: Int,
val comments: List<CreatorChannelCommunityCommentResponse>,
val page: Int,
val size: Int,
@JsonProperty("hasNext")
val hasNext: Boolean
) {
companion object {
fun from(comments: CreatorChannelCommunityComments): CreatorChannelCommunityCommentsResponse {
return CreatorChannelCommunityCommentsResponse(
commentCount = comments.commentCount,
comments = comments.comments.map(CreatorChannelCommunityCommentResponse::from),
page = comments.page.page,
size = comments.page.size,
hasNext = comments.hasNext
)
}
}
}
data class CreatorChannelCommunityCommentResponse(
val commentId: Long,
val writerId: Long,
val writerProfileImageUrl: String,
val writerNickname: String,
val content: String,
val createdAtUtc: String,
val latestReply: CreatorChannelCommunityReplyResponse?
) {
companion object {
fun from(comment: CreatorChannelCommunityComment): CreatorChannelCommunityCommentResponse {
return CreatorChannelCommunityCommentResponse(
commentId = comment.commentId,
writerId = comment.writerId,
writerProfileImageUrl = comment.writerProfileImageUrl,
writerNickname = comment.writerNickname,
content = comment.content,
createdAtUtc = comment.createdAt.toUtcIso(),
latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from)
)
}
}
}
data class CreatorChannelCommunityRepliesResponse(
val replyCount: Int,
val replies: List<CreatorChannelCommunityReplyResponse>,
val page: Int,
val size: Int,
@JsonProperty("hasNext")
val hasNext: Boolean
) {
companion object {
fun from(replies: CreatorChannelCommunityReplies): CreatorChannelCommunityRepliesResponse {
return CreatorChannelCommunityRepliesResponse(
replyCount = replies.replyCount,
replies = replies.replies.map(CreatorChannelCommunityReplyResponse::from),
page = replies.page.page,
size = replies.page.size,
hasNext = replies.hasNext
)
}
}
}
data class CreatorChannelCommunityReplyResponse(
val commentId: Long,
val writerId: Long,
val writerProfileImageUrl: String,
val writerNickname: String,
val content: String,
val createdAtUtc: String
) {
companion object {
fun from(reply: CreatorChannelCommunityReply): CreatorChannelCommunityReplyResponse {
return CreatorChannelCommunityReplyResponse(
commentId = reply.commentId,
writerId = reply.writerId,
writerProfileImageUrl = reply.writerProfileImageUrl,
writerNickname = reply.writerNickname,
content = reply.content,
createdAtUtc = reply.createdAt.toUtcIso()
)
}
}
}
3. Domain / Port 초안
구현 시 src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityPostDetail.kt를 아래 구조로 만든다.
package kr.co.vividnext.sodalive.v2.creator.channel.community.domain
import kr.co.vividnext.sodalive.v2.creator.channel.live.domain.CreatorChannelPage
import java.time.LocalDateTime
data class CreatorChannelCommunityPostDetail(
val post: CreatorChannelCommunityPost,
val comments: CreatorChannelCommunityComments
)
data class CreatorChannelCommunityComments(
val commentCount: Int,
val comments: List<CreatorChannelCommunityComment>,
val page: CreatorChannelPage,
val hasNext: Boolean
)
data class CreatorChannelCommunityComment(
val commentId: Long,
val writerId: Long,
val writerProfileImageUrl: String,
val writerNickname: String,
val content: String,
val createdAt: LocalDateTime,
val latestReply: CreatorChannelCommunityReply?
)
data class CreatorChannelCommunityReplies(
val replyCount: Int,
val replies: List<CreatorChannelCommunityReply>,
val page: CreatorChannelPage,
val hasNext: Boolean
)
data class CreatorChannelCommunityReply(
val commentId: Long,
val writerId: Long,
val writerProfileImageUrl: String,
val writerNickname: String,
val content: String,
val createdAt: LocalDateTime
)
구현 시 CreatorChannelCommunityQueryPort에 아래 method와 record를 추가한다.
fun findCommunityPost(
postId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityPostRecord?
fun countCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long
): Int
fun findCommunityComments(
postId: Long,
viewerId: Long,
creatorId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord>
fun findLatestRepliesByCommentIds(
commentIds: List<Long>,
viewerId: Long
): List<CreatorChannelCommunityCommentRecord>
fun findRootCommentContext(
commentId: Long,
viewerId: Long,
canViewAdultContent: Boolean
): CreatorChannelCommunityCommentContextRecord?
fun countCommunityReplies(
commentId: Long,
viewerId: Long
): Int
fun findCommunityReplies(
commentId: Long,
viewerId: Long,
offset: Long,
limit: Int
): List<CreatorChannelCommunityCommentRecord>
data class CreatorChannelCommunityCommentRecord(
val commentId: Long,
val parentCommentId: Long?,
val postId: Long,
val writerId: Long,
val writerProfilePath: String?,
val writerNickname: String,
val content: String,
val createdAt: LocalDateTime
)
data class CreatorChannelCommunityCommentContextRecord(
val commentId: Long,
val postId: Long,
val creatorId: Long,
val isCommentAvailable: Boolean
)
4. Phase / Task Breakdown
Phase 1: DTO, domain, page 정책 기반 추가
-
Task 1.1: 상세/댓글/답글 response DTO 추가 - 검증:
CreatorChannelCommunityPostDetailResponse.kt와 DTO 직렬화 테스트를 추가했고 boolean is-prefix, comments/latestReply/replies 구조를 확인했다.- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityPostDetailResponse.kt - Create:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/dto/CreatorChannelCommunityPostDetailResponseTest.kt
- Create:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityPostDetailResponseTest.shouldSerializeBooleanFieldsWithIsPrefixAndReuseCommentsResponse를 추가한다.- 검증 항목:
isCommentAvailable,isPinned,isLiked,hasNextJSON 필드명이 유지된다.- 상세 응답의
comments가CreatorChannelCommunityCommentsResponse구조로 직렬화된다. - 댓글 item은
latestReply를 포함하고, 답글 item은latestReply를 포함하지 않는다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityPostDetailResponseTest
- 기대 결과:
- 신규 DTO가 없어서
compileTestKotlin실패한다.
- 신규 DTO가 없어서
- GREEN: 최소 구현/통과 확인
- 위 response data class 초안 그대로 DTO 파일을 추가한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.dto.CreatorChannelCommunityPostDetailResponseTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 기존
CreatorChannelCommunityPostResponse를 중복 수정하지 않는다. rg -n "JsonProperty\\(\"isLiked\"\\)|JsonProperty\\(\"hasNext\"\\)" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community로 Boolean 필드명을 확인한다.
- 기존
- Files:
-
Task 1.2: 상세/댓글/답글 domain model 추가 - 검증: 상세/댓글/답글 domain model을 추가하고 기존
CreatorChannelCommunityQueryPolicy.createPagefallback 재사용 테스트를 통과시켰다.- Files:
- Create:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityPostDetail.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/domain/CreatorChannelCommunityQueryPolicyTest.kt
- Create:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityQueryPolicyTest.shouldUseSamePageFallbackForCommunityComments를 추가한다.- 검증 항목:
createPage(null, null)은page=0,size=20,fetchLimit=21createPage(-1, 100)은page=0,size=50,fetchLimit=51
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityQueryPolicyTest
- 기대 결과:
- domain model import 또는 신규 테스트 의도에 맞춰 컴파일/테스트 실패를 확인한다.
- GREEN: 최소 구현/통과 확인
- 위 domain data class 초안을 추가한다.
- 기존
CreatorChannelCommunityQueryPolicy.createPage를 그대로 사용하고, 동작 변경은 하지 않는다. - 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.domain.CreatorChannelCommunityQueryPolicyTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
CreatorChannelPage를 새로 만들지 않고 기존v2.creator.channel.live.domain.CreatorChannelPage를 재사용한다.
- Files:
Phase 2: QueryDSL repository 상세/댓글/답글 조회
-
Task 2.1: port method와 record 계약 추가 - 검증: 상세/댓글/답글 port method와 record를 추가했고 service/home 테스트 fake 구현을 갱신해 컴파일 및 service 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/port/out/CreatorChannelCommunityQueryPort.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
FakeCreatorChannelCommunityQueryPort가 신규 port method를 구현하지 않아 컴파일 실패하도록 service test에 상세/댓글/답글 테스트 skeleton을 추가한다.- 추가할 테스트 이름:
shouldAssembleCommunityPostDetailWithInitialCommentsshouldReturnEmptyCommentsWhenCommentUnavailableshouldAssembleCommunityReplies
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
CreatorChannelCommunityQueryPort신규 method 미정의 또는 fake 미구현으로compileTestKotlin실패한다.
- GREEN: 최소 구현/통과 확인
CreatorChannelCommunityQueryPort에 3장 port 초안의 method와 record를 추가한다.FakeCreatorChannelCommunityQueryPort에는 테스트용 기본값을 반환하는 구현을 추가한다.- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
- 아직 service method가 없어서 의도한 RED 실패가 남는다.
- REFACTOR: 정리/회귀 확인
- port record는 API DTO를 import하지 않는다.
- 실행 명령:
rg -n "v2\\.api\\." src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community
- 기대 결과:
- 검색 결과 0건
- Files:
-
Task 2.2: 게시물 ID 기반 상세 row 조회 - 검증:
findCommunityPost를selectCommunityPostRow/toCommunityPostRecords재사용으로 구현하고 좋아요/구매/성인/비활성 정책 repository 테스트를 통과시켰다.- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/CreatorChannelCommunityQueryRepository.kt - Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepository.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepositoryTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
DefaultCreatorChannelCommunityQueryRepositoryTest.shouldFindCommunityPostByIdWithLikePurchaseAndAdultPolicy를 추가한다.- fixture:
- creator, viewer, adult-disabled viewer
- 무료 게시물 1개
- 유료 게시물 1개와 유효 구매 내역
- 19금 게시물 1개
- viewer 활성 좋아요와 다른 회원 좋아요
- 검증 항목:
findCommunityPost(postId, viewerId, true)는CreatorChannelCommunityPostRecord1건을 반환한다.existOrdered,isLiked,likeCount,commentCount,isPinned가 기존 목록과 같은 의미로 채워진다.canViewAdultContent=false이면 19금 게시물은null이다.- 비활성 게시물은
null이다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
findCommunityPost미구현으로 컴파일 실패한다.
- GREEN: 최소 구현/통과 확인
DefaultCreatorChannelCommunityQueryRepository.findCommunityPost(...)를 추가한다.- 기존
selectCommunityPostRow,communityPostCondition,toCommunityPostRecords를 재사용한다. - 단일 게시물 조회 조건은
creatorCommunity.id.eq(postId),isActive=true,member.isActive=true, 성인 필터를 적용한다. - 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 목록 조회와 상세 조회의 부가 필드 계산이 중복되지 않도록
toCommunityPostRecords를 재사용한다. - 단일 조회 결과가 없으면 빈 리스트 변환 후
firstOrNull()로 처리해 NPE를 피한다.
- 목록 조회와 상세 조회의 부가 필드 계산이 중복되지 않도록
- Files:
-
Task 2.3: 댓글 목록 count/list와 최신 답글 bulk 조회 - 검증: 댓글 count/list/latest reply bulk 조회를 구현하고 비밀 댓글, 차단 작성자, 최신 답글 정렬 repository 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepository.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepositoryTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
DefaultCreatorChannelCommunityQueryRepositoryTest.shouldFindVisibleRootCommentsWithLatestReply를 추가한다.- fixture:
- 공개 최상위 댓글 2개
- 각 댓글의 답글 여러 개
- 비밀 댓글 1개
- 비활성 댓글 1개
- 차단 작성자 댓글 1개
- 최상위 댓글이 아닌 답글 1개
- 검증 항목:
- viewer 기준
countCommunityComments는 공개/본인 비밀/차단 제외 정책을 반영한다. - creator 기준
countCommunityComments는 비밀 댓글도 포함한다. findCommunityComments는createdAt desc,id desc순서로limit개를 반환한다.findLatestRepliesByCommentIds는 parent comment별 최신 답글 1개만 반환한다.- 답글 작성자가 차단 관계이면 latest reply 후보에서 제외된다.
- viewer 기준
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
- 댓글 port method 미구현으로 컴파일 실패한다.
- GREEN: 최소 구현/통과 확인
countCommunityComments,findCommunityComments,findLatestRepliesByCommentIds를 구현한다.- 기존 helper
visibleSecretCommentCondition(creatorId, viewerId)와notBlockedCommentWriterCondition(viewerId)를 재사용한다. findLatestRepliesByCommentIds는 parent id 목록이 비어 있으면 빈 목록을 반환한다.- latest reply는 QueryDSL로 후보를 조회한 뒤 Kotlin group by로 parent별 첫 항목을 선택하거나 동등한 방식으로 구현한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- count 조건과 list 조건이 다르지 않은지 테스트 fixture를 재확인한다.
fetch().size대신 count query를 우선 사용한다.
- Files:
-
Task 2.4: 답글 조회용 부모 댓글 context, count/list 조회 - 검증: root comment context와 답글 count/list 조회를 구현하고 보이지 않는 비밀 부모, 답글의 답글 제외, 차단/비활성 제외 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepository.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepositoryTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
DefaultCreatorChannelCommunityQueryRepositoryTest.shouldFindVisibleRepliesForRootCommentOnly를 추가한다.- fixture:
- 최상위 댓글 1개
- 답글 3개
- 비활성 답글 1개
- 차단 작성자 답글 1개
- 답글의 답글 형태 비정상 데이터 1개
- 비밀 최상위 댓글 1개
- 검증 항목:
findRootCommentContext는 최상위 댓글만 반환한다.- 조회자에게 보이지 않는 비밀 부모 댓글은
null이다. countCommunityReplies와findCommunityReplies는 활성 답글과 차단 제외 정책을 반영한다.- 답글 정렬은
createdAt desc,id desc다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
- 답글 port method 미구현으로 컴파일 실패한다.
- GREEN: 최소 구현/통과 확인
findRootCommentContext,countCommunityReplies,findCommunityReplies를 구현한다.- 부모 댓글 context 조회는 부모 댓글의 게시물 활성/성인 필터/비밀 댓글/차단 작성자 정책을 함께 적용한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 답글 목록 item에
latestReply용 데이터를 넣지 않는다. parent.isNotNull과parent.id.eq(commentId)조건을 모두 사용한다.
- 답글 목록 item에
- Files:
Phase 3: Query service 상세/댓글/답글 조립
-
Task 3.1: 상세 조회 service 조립 - 검증: 상세 service가 기존 게시글 domain 변환, 초기 댓글 20개, 최신 답글을 조립하도록 구현하고 service 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryService.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityQueryServiceTest.shouldAssembleCommunityPostDetailWithInitialComments를 완성한다.- fake port fixture:
findCommunityPost는isCommentAvailable=true,isLiked=true게시물 반환findCommunityComments는 21개 댓글 반환findLatestRepliesByCommentIds는 첫 댓글 최신 답글 반환
- 검증 항목:
- 상세 게시물의 이미지/오디오/본문 접근 정책이 기존 탭과 같다.
comments.page=0,comments.size=20,comments.hasNext=truecomments.comments.size=20- 첫 댓글의
latestReply가 조립된다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
getCommunityPostDetail미구현으로 컴파일 실패한다.
- GREEN: 최소 구현/통과 확인
CreatorChannelCommunityQueryService.getCommunityPostDetail(postId, viewer, now)를 추가한다.findCommunityPost가 null이면SodaException(messageKey = "creator.community.invalid_request_retry")를 던진다.- 게시물 작성자와 조회자 사이 차단 관계는
existsBlockedBetween(viewerId, post.creatorId)로 확인한다. - 댓글은 내부 helper
getCommunityComments(postId, viewer, page = 0, size = 20, now)를 호출해 조립한다. - 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 기존
CreatorChannelCommunityPostRecord.toDomain(viewerId)를 상세에도 재사용한다. nowparameter는 기존 facade 패턴과 맞춰 테스트 주입 가능하게 유지한다.
- 기존
- Files:
-
Task 3.2: 댓글 목록 service 조립 - 검증: 댓글 service가 page fallback, 댓글 불가 빈 응답, CDN/default profile, 탈퇴 닉네임 prefix 제거, latest reply 조립을 수행하도록 구현하고 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryService.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityQueryServiceTest.shouldReturnEmptyCommentsWhenCommentUnavailable를 추가한다.CreatorChannelCommunityQueryServiceTest.shouldApplyCommentPageFallbackAndLatestReply를 추가한다.- 검증 항목:
isCommentAvailable=false이면 comment count/list/latest reply port를 호출하지 않고 빈 응답을 반환한다.page=-1,size=100요청은offset=0,limit=51, 응답size=50으로 조립된다.findLatestRepliesByCommentIds는 page 응답에 포함된 최대size개 댓글 id만 대상으로 호출된다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
getCommunityComments미구현 또는 port 호출 누락으로 실패한다.
- GREEN: 최소 구현/통과 확인
CreatorChannelCommunityQueryService.getCommunityComments(postId, viewer, page, size, now)를 추가한다.- 게시물 검증은
findCommunityPost를 사용한다. CreatorChannelCommunityQueryPolicy.limitItems와hasNext를 재사용한다.- 댓글 record는 CDN URL/default profile URL, 닉네임 prefix 제거 후 domain으로 변환한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 댓글 조립 helper와 답글 조립 helper를 분리해 중복을 줄인다.
- API DTO를 import하지 않는다.
- Files:
-
Task 3.3: 답글 목록 service 조립 - 검증: 답글 service가 부모 context 검증, page fallback, reply DTO용 domain 조립을 수행하도록 구현하고 보이지 않는 부모 예외 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryService.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityQueryServiceTest.shouldAssembleCommunityReplies를 완성한다.CreatorChannelCommunityQueryServiceTest.shouldThrowWhenRootCommentContextNotVisible를 추가한다.- 검증 항목:
findRootCommentContext가 null이면SodaException(messageKey = "creator.community.invalid_request_retry")- page fallback과
hasNext가 댓글 목록과 동일하다. - 응답 domain의 replies item에는 latest reply가 없다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
getCommunityReplies미구현으로 컴파일 실패한다.
- GREEN: 최소 구현/통과 확인
CreatorChannelCommunityQueryService.getCommunityReplies(commentId, viewer, page, size, now)를 추가한다.- 부모 댓글 context 조회 후 게시물 작성자와 조회자 사이 차단 관계를 확인한다.
countCommunityReplies,findCommunityReplies로 답글 응답을 조립한다.- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 댓글/답글 record 변환에서 default profile URL과 닉네임 정리를 공통 private helper로 묶는다.
- Files:
Phase 4: Facade와 Controller endpoint 연결
-
Task 4.1: facade method 추가 - 검증: 상세/댓글/답글 facade method를 추가하고 raw page/size 전달 및 공개 response DTO 변환 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/application/CreatorChannelCommunityFacade.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/application/CreatorChannelCommunityFacadeTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
- 추가할 테스트 이름:
shouldReturnCommunityPostDetailResponseshouldReturnCommunityCommentsResponseshouldReturnCommunityRepliesResponse
- 검증 항목:
- facade가 query service 결과를 response DTO로 변환한다.
- raw
page,size를 service로 전달한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.application.CreatorChannelCommunityFacadeTest
- 기대 결과:
- 신규 facade method 미구현으로 컴파일 실패한다.
- 추가할 테스트 이름:
- GREEN: 최소 구현/통과 확인
getCommunityPostDetail(postId, viewer, now),getCommunityComments(postId, viewer, page, size, now),getCommunityReplies(commentId, viewer, page, size, now)를 추가한다.- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.application.CreatorChannelCommunityFacadeTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- facade에는 비즈니스 조건문을 넣지 않는다.
- Files:
-
Task 4.2: controller endpoint 추가 - 검증: 세 v2 GET mapping을 추가하고 인증 거부, 성공 JSON, raw page/size 전달 controller 테스트를 통과시켰다.
- Files:
- Modify:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityController.kt - Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityControllerTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
- 추가할 테스트 이름:
shouldRejectAnonymousCommunityPostDetailRequestshouldReturnCommunityPostDetailForAuthenticatedMembershouldReturnCommunityCommentsForAuthenticatedMembershouldPassRawCommentPageAndSizeToFacadeshouldReturnCommunityRepliesForAuthenticatedMembershouldPassRawReplyPageAndSizeToFacade
- 검증 항목:
- 세 endpoint는 인증 회원만 통과한다.
- JSON 경로:
$.data.postId$.data.isLiked$.data.comments.commentCount$.data.comments.comments[0].latestReply.commentId$.data.hasNext
- controller는 page/size 보정을 하지 않고 facade로 raw 값을 전달한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityControllerTest
- 기대 결과:
- 신규 mapping 미구현으로 404 또는 컴파일 실패를 확인한다.
- 추가할 테스트 이름:
- GREEN: 최소 구현/통과 확인
- controller에 아래 mapping을 추가한다.
@GetMapping("/community-posts/{postId}")@GetMapping("/community-posts/{postId}/comments")@GetMapping("/community-comments/{commentId}/replies")
- 기존
requireMember를 재사용한다. - 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityControllerTest
- 기대 결과:
BUILD SUCCESSFUL
- controller에 아래 mapping을 추가한다.
- REFACTOR: 정리/회귀 확인
- 기존
@GetMapping("/{creatorId}/community")와 신규 literal path가 충돌하지 않는지 controller test에서 모두 통과 확인한다.
- 기존
- Files:
Phase 5: End-to-End와 회귀 검증
-
Task 5.1: 상세 API E2E 테스트 - 검증: 상세 API E2E에서
isLiked=true, 초기 댓글 20개,hasNext=true, 최신 답글, UTCZ문자열을 확인했다.- Files:
- Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityEndToEndTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
CreatorChannelCommunityEndToEndTest.shouldReturnCommunityPostDetailWithInitialComments를 추가한다.- fixture:
- creator, viewer
- 커뮤니티 게시물 1개
- 활성 좋아요
- 댓글 21개
- 첫 댓글 최신 답글 1개
- 검증 항목:
GET /api/v2/creator-channels/community-posts/{postId}200 OKdata.isLiked=truedata.comments.comments.length()==20data.comments.hasNext=truedata.comments.comments[0].latestReply존재createdAtUtc는Z로 끝나는 UTC 문자열
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
- API 미구현 상태에서 404 또는 JSON assertion 실패를 확인한다.
- GREEN: 최소 구현/통과 확인
- Phase 1~4 구현을 연결해 테스트를 통과시킨다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- E2E fixture helper가 기존 커뮤니티 탭 테스트 helper와 충돌하지 않도록 private helper로 유지한다.
- Files:
-
Task 5.2: 댓글/답글 API E2E 테스트 - 검증: 댓글 API, 답글 API, 댓글 불가 빈 응답 E2E를 추가했고 답글 item에
latestReply가 없음을 확인했다.- Files:
- Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityEndToEndTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
- 추가할 테스트 이름:
shouldReturnPagedCommunityCommentsWithLatestReplyshouldReturnPagedCommunityRepliesWithoutLatestReplyshouldReturnEmptyCommentsWhenCommentUnavailable
- 검증 항목:
- 댓글 API는
commentCount,comments,page,size,hasNext를 반환한다. - 답글 API는
replyCount,replies,page,size,hasNext를 반환한다. - 답글 item에는
latestReply필드가 없다. - 댓글 불가 게시물은 빈 댓글 응답을 반환한다.
- 댓글 API는
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
- API 미구현 상태에서 404 또는 JSON assertion 실패를 확인한다.
- 추가할 테스트 이름:
- GREEN: 최소 구현/통과 확인
- 댓글/답글 controller, facade, service, repository 연결을 완료한다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- 응답 필드명
commentId가 댓글과 답글 모두에서 동일하게 사용되는지 확인한다.
- 응답 필드명
- Files:
-
Task 5.3: 접근 정책 E2E 테스트 - 검증: 성인 콘텐츠 미허용 회원의 19금 상세 접근이 공통 오류 응답(
success=false)으로 거부됨을 E2E로 확인했다.- Files:
- Modify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityEndToEndTest.kt
- Modify:
- RED: 실패 테스트 작성/실패 확인
- 추가할 테스트 이름:
shouldRejectAdultCommunityPostWhenViewerCannotViewAdultContentshouldRejectCommunityPostWhenViewerAndCreatorAreBlockedshouldFilterSecretAndBlockedCommentsshouldRejectRepliesWhenParentCommentIsNotVisible
- 검증 항목:
- 19금 게시물 접근 불가
- 게시물 작성자 차단 관계 접근 차단
- 비밀 댓글은 게시물 작성자/댓글 작성자 본인에게만 노출
- 차단 댓글 작성자와 답글 작성자는 count/list/latestReply에서 제외
- 보이지 않는 부모 댓글의 답글 조회는 오류
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
- 접근 정책 미구현으로 assertion 실패를 확인한다.
- 추가할 테스트 이름:
- GREEN: 최소 구현/통과 확인
- repository 조건과 service 예외 처리를 조정해 테스트를 통과시킨다.
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityEndToEndTest
- 기대 결과:
BUILD SUCCESSFUL
- REFACTOR: 정리/회귀 확인
- legacy
/creator-communitycontroller/service는 수정하지 않았는지git diff로 확인한다.
- legacy
- Files:
Phase 6: 전체 회귀와 문서 갱신
-
Task 6.1: 기존 커뮤니티 탭 회귀 테스트 - 검증: community controller/service/repository 포함 focused test 묶음과 domain 패키지 API 의존성 검색을 실행해 회귀가 없음을 확인했다.
- Files:
- Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/community/adapter/in/web/CreatorChannelCommunityControllerTest.kt - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/application/CreatorChannelCommunityQueryServiceTest.kt - Verify:
src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community/adapter/out/persistence/DefaultCreatorChannelCommunityQueryRepositoryTest.kt
- Verify:
- RED: 실패 테스트 작성/실패 확인
- TDD 예외 사유: 이 task는 신규 테스트 작성이 아니라 기존 커뮤니티 탭 동작 회귀 검증이다.
- 대체 검증 방법: 기존 테스트 전체를 실행해 상세/댓글 추가가 탭 목록 동작을 깨지 않는지 확인한다.
- GREEN: 최소 구현/통과 확인
- 실행 명령:
./gradlew test --tests kr.co.vividnext.sodalive.v2.api.creator.channel.community.adapter.in.web.CreatorChannelCommunityControllerTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.application.CreatorChannelCommunityQueryServiceTest./gradlew test --tests kr.co.vividnext.sodalive.v2.creator.channel.community.adapter.out.persistence.DefaultCreatorChannelCommunityQueryRepositoryTest
- 기대 결과:
- 모두
BUILD SUCCESSFUL
- 모두
- 실행 명령:
- REFACTOR: 정리/회귀 확인
rg -n "v2\\.api\\." src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community실행 결과가 0건인지 확인한다.
- Files:
-
Task 6.2: formatting, compile, 전체 검증 - 검증:
./gradlew compileKotlin,./gradlew ktlintCheck,./gradlew test,./gradlew tasks --all을 실행해 모두BUILD SUCCESSFUL을 확인했다.- Files:
- Verify: 전체 변경 파일
- Modify:
docs/20260706_커뮤니티_게시물_상세_API/plan-task.md
- RED: 실패 테스트 작성/실패 확인
- TDD 예외 사유: 전체 검증 task는 구현 완료 후 회귀 확인 단계라 별도 실패 테스트를 만들지 않는다.
- 대체 검증 방법: 단일 테스트, compile, ktlint, 전체 테스트를 순서대로 실행한다.
- GREEN: 최소 구현/통과 확인
- 실행 명령:
./gradlew compileKotlin./gradlew ktlintCheck./gradlew test
- 기대 결과:
- 모두
BUILD SUCCESSFUL
- 모두
- 실행 명령:
- REFACTOR: 정리/회귀 확인
- 실행 명령:
./gradlew tasks --all
- 기대 결과:
BUILD SUCCESSFUL
- 검증 결과를 이 문서 하단 "검증 기록"에 누적한다.
- 실행 명령:
- Files:
5. 구현 순서 권장
- Task 1.1 → Task 1.2로 공개 DTO와 domain type을 먼저 고정한다.
- Task 2.1 → Task 2.4로 repository 조회 계약과 QueryDSL 조건을 테스트로 고정한다.
- Task 3.1 → Task 3.3으로 service 조립과 예외 정책을 완성한다.
- Task 4.1 → Task 4.2로 facade/controller endpoint를 연결한다.
- Task 5.1 → Task 5.3으로 실제 HTTP 흐름과 접근 정책을 검증한다.
- Task 6.1 → Task 6.2로 기존 커뮤니티 탭 회귀와 전체 검증을 수행한다.
6. 검증 기록
- 2026-07-06: plan-task 문서 작성 전
docs/20260706_커뮤니티_게시물_상세_API/prd.md,docs/agent-guides/코드스타일.md,docs/agent-guides/테스트스타일.md,docs/agent-guides/실행명령어.md를 확인했다. - 2026-07-06: 기존 유사 계획 문서
docs/20260621_크리에이터_채널_커뮤니티_탭_API/plan-task.md,docs/20260622_크리에이터_채널_FanTalk_탭_API/plan-task.md를 확인해 문서 구조와 TDD task 형식을 맞췄다. - 2026-07-06:
rg -n "TBD|TODO|implement later|fill in|적절|:app:compileDebugKotlin|android|Android|Similar" docs/20260706_커뮤니티_게시물_상세_API/plan-task.md로 placeholder와 잘못된 Android task 예시가 없음을 확인했다. - 2026-07-06:
./gradlew tasks --all을 실행해 Gradle 명령 유효성을 확인했고BUILD SUCCESSFUL을 확인했다. - 2026-07-06: Phase 1-5 구현 후
./gradlew compileKotlin, DTO/facade/controller/service/repository/E2E focused tests를 실행해BUILD SUCCESSFUL을 확인했다. - 2026-07-07: 포맷 정리 후
./gradlew ktlintCheck와 관련 focused test 묶음을 재실행해 모두BUILD SUCCESSFUL을 확인했다. - 2026-07-07:
rg -n "v2\.api\." src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/community실행 결과 0건으로 domain/query 계층의 API DTO 의존이 없음을 확인했다. - 2026-07-07:
rg -n "creator-community|schema|migration|Flyway|Liquibase" src/main/kotlin/kr/co/vividnext/sodalive/v2 src/test/kotlin/kr/co/vividnext/sodalive/v2 docs/20260706_커뮤니티_게시물_상세_API/plan-task.md실행 결과 문서 언급 외 legacy endpoint/schema 변경 흔적이 없음을 확인했다. - 2026-07-07:
./gradlew compileKotlin,./gradlew ktlintCheck,./gradlew test,./gradlew tasks --all을 실행해 모두BUILD SUCCESSFUL을 확인했다. - 2026-07-07: 리뷰 게이트에서 Task 5.3 E2E 누락, 비밀 답글 노출, 댓글 불가 게시글의 답글 직접 조회 우회가 발견되어 repository/service/E2E 실패 테스트를 추가했다.
- 2026-07-07: 추가 RED 확인으로
DefaultCreatorChannelCommunityQueryRepositoryTest,CreatorChannelCommunityQueryServiceTest,CreatorChannelCommunityEndToEndTestfocused 실행 시 5개 테스트 실패를 확인했다. - 2026-07-07: 비밀 답글 필터와 댓글 불가 답글 빈 응답 처리를 구현한 뒤 같은 focused test 묶음을 재실행해
BUILD SUCCESSFUL을 확인했다. - 2026-07-07: 보강 후
./gradlew compileKotlin,./gradlew ktlintCheck,./gradlew test,./gradlew tasks --all을 재실행해 모두BUILD SUCCESSFUL을 확인했다. - 2026-07-07:
rg -n "shouldRejectCommunityPostWhenViewerAndCreatorAreBlocked|shouldFilterSecretAndBlockedComments|shouldRejectRepliesWhenParentCommentIsNotVisible|shouldReturnEmptyRepliesWhenCommentUnavailable" ...로 접근 정책 E2E/서비스 테스트명이 추가됐음을 확인했다.