Files

16 KiB

크리에이터 채널 홈 커뮤니티 응답 확장 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 크리에이터 채널 홈의 noticescommunities 게시글에 실제 고정 여부와 댓글 가능 여부를 non-null Boolean으로 제공한다.

Architecture: 기존 CreatorChannelCommunityPost가 보유한 두 값을 공유 CreatorChannelCommunityPostResponse.from에서 그대로 매핑한다. 조회 계층과 데이터 모델은 건드리지 않고 공개 응답 DTO와 직접 영향받는 직렬화·E2E 테스트만 수정한다.

Tech Stack: Kotlin, Java 17, Spring Boot 2.7.14, Jackson, JUnit 5, MockMvc, Gradle Wrapper

문서 상태

문서 항목 내용
상태 구현 중
작성일 2026-08-10
요구사항 기준 docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md
API 기준 PRD 7. API 계약
현재 Phase Phase 1: 홈 커뮤니티 상태 응답 확장
현재 활성 Goal 없음
다음 Goal P1-GATE

현재 상태

Phase 상태 완료 Task 활성/다음 Goal 차단 또는 남은 조건
1 진행 중 1/1 P1-GATE P1-GATE 검증 대기
  • 동시에 하나의 미완료 goal만 운용한다.
  • 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다.
  • goal 실행 시 token budget을 설정하지 않는다.

범위

포함

  • GET /api/v2/creator-channels/{creatorId}/homenotices[*], communities[*]isPinned 추가
  • 같은 게시글 응답에 isCommentAvailable 추가
  • 도메인 값의 DTO 매핑과 정확한 is* JSON 필드명 검증
  • 기존 홈 API E2E와 Kotlin 포맷 회귀 검증

제외

  • 조회 조건, 정렬, 최대 노출 개수와 권한 정책 변경
  • 커뮤니티 탭·상세·댓글 API 변경
  • DB, entity, domain model, repository 수정
  • 전용 DTO 분리, 신규 dependency와 관련 없는 리팩터링

Global Constraints

  • Kotlin + Java 17, Spring Boot 2.7.14와 기존 Jackson 직렬화 방식을 유지한다.
  • 기존 공유 CreatorChannelCommunityPostResponsefrom 변환만 확장한다.
  • 공개 필드명은 isPinned, isCommentAvailable로 고정하고 두 값은 nullable로 만들지 않는다.
  • CreatorChannelCommunityPost.isPinned, CreatorChannelCommunityPost.isCommentAvailable 값을 재계산하지 않는다.
  • 구현은 RED → RED 확인 → GREEN → GREEN 확인 → REFACTOR 순서로 진행한다.
  • focused test에서 시작해 직접 영향받는 홈 E2E와 ktlintCheck까지만 확장한다.
  • 전체 ./gradlew test는 공통 경계 영향이나 targeted test로 판단할 수 없는 실패가 있을 때만 실행한다.

Phase 1: 홈 커뮤니티 상태 응답 확장

Phase 결과: 홈 API의 공지와 일반 커뮤니티 게시글이 고정 여부와 댓글 가능 여부를 정확한 Boolean 필드로 반환한다.

선행조건: 승인된 PRD의 CCHC-001~004, DEC-001~003 확인.

Phase 완료 조건: P1-T1P1-GATE 완료, 체크박스와 실제 검증 결과를 Progress에 누적.

Task 1.1 공유 게시글 응답에 상태 필드 추가

Goal 실행 P1-T1: 공유 홈 게시글 응답이 도메인의 isPinned, isCommentAvailable을 정확한 JSON 필드로 직렬화하게 한다.

  • 시작 조건: PRD 상태가 구현 기준 확정이고 현재 활성 Goal이 없다.
  • 완료 증거: 아래 5단계 완료, controller focused test와 홈 E2E 통과, 변경 파일과 결과를 Progress에 기록.
  • 범위 밖: 조회 service/repository, domain model, 다른 커뮤니티 API 수정.

Files:

  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt:92
  • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt:57
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt:173
  • Modify: docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md

Interfaces:

  • Consumes: CreatorChannelCommunityPost.isPinned: Boolean, CreatorChannelCommunityPost.isCommentAvailable: Boolean

  • Produces: CreatorChannelCommunityPostResponse.isPinned: Boolean, CreatorChannelCommunityPostResponse.isCommentAvailable: Boolean

  • JSON: data.notices[*].isPinned, data.notices[*].isCommentAvailable, data.communities[*].isPinned, data.communities[*].isCommentAvailable

  • RED: CreatorChannelHomeControllerTest.shouldReturnCreatorChannelHomeForAuthenticatedMember의 일반 커뮤니티 fixture를 아래처럼 공지와 반대 상태로 만들고, 네 JSON 경로 assertion을 추가한다. 같은 assertion을 CreatorChannelHomeEndToEndTest.shouldAssembleCreatorChannelHomeSectionsThroughSingleHttpRequest에도 추가하되 E2E fixture의 댓글 가능 값은 기존 true를 사용한다.

communities = listOf(
    post.copy(
        postId = 302L,
        content = "community",
        isCommentAvailable = false,
        isPinned = false
    )
)
.andExpect(jsonPath("$.data.notices[0].isPinned").value(true))
.andExpect(jsonPath("$.data.notices[0].isCommentAvailable").value(true))
.andExpect(jsonPath("$.data.communities[0].isPinned").value(false))
.andExpect(jsonPath("$.data.communities[0].isCommentAvailable").value(false))

E2E assertion은 실제 저장값과 조회 조건을 검증한다.

.andExpect(jsonPath("$.data.notices[0].isPinned").value(true))
.andExpect(jsonPath("$.data.notices[0].isCommentAvailable").value(true))
.andExpect(jsonPath("$.data.communities[0].isPinned").value(false))
.andExpect(jsonPath("$.data.communities[0].isCommentAvailable").value(true))
  • RED 확인: 다음 focused test를 실행해 $.data.notices[0].isPinned 등 신규 JSON 경로에 값이 없어 실패하는지 확인한다.
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' --no-daemon

Expected: 테스트가 신규 필드 미직렬화로 실패한다. compile 오류나 fixture 오류는 RED 증거로 인정하지 않는다.

  • GREEN: CreatorChannelCommunityPostResponse에 기존 커뮤니티 탭 DTO와 같은 @JsonProperty 관례로 필드를 추가하고 from에서 도메인 값을 직접 매핑한다.
data class CreatorChannelCommunityPostResponse(
    val postId: Long,
    val creatorId: Long,
    val creatorNickname: String,
    val creatorProfileUrl: String,
    val imageUrl: String?,
    val audioUrl: String?,
    val content: String,
    val price: Int,
    val dateUtc: String,
    val existOrdered: Boolean,
    @JsonProperty("isCommentAvailable")
    val isCommentAvailable: Boolean,
    val likeCount: Int,
    val commentCount: Int,
    @JsonProperty("isPinned")
    val isPinned: Boolean,
    @JsonProperty("isLiked")
    val isLiked: Boolean
) {
    companion object {
        fun from(post: CreatorChannelCommunityPost): CreatorChannelCommunityPostResponse {
            return CreatorChannelCommunityPostResponse(
                postId = post.postId,
                creatorId = post.creatorId,
                creatorNickname = post.creatorNickname,
                creatorProfileUrl = post.creatorProfileUrl,
                imageUrl = post.imageUrl,
                audioUrl = post.audioUrl,
                content = post.content,
                price = post.price,
                dateUtc = post.createdAt.toUtcIso(),
                existOrdered = post.existOrdered,
                isCommentAvailable = post.isCommentAvailable,
                likeCount = post.likeCount,
                commentCount = post.commentCount,
                isPinned = post.isPinned,
                isLiked = post.isLiked
            )
        }
    }
}
  • GREEN 확인: controller test와 홈 E2E를 함께 실행해 공유 DTO 매핑, JSON 이름, 실제 조회값이 모두 통과하는지 확인한다.
./gradlew test \
  --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' \
  --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.CreatorChannelHomeEndToEndTest' \
  --no-daemon

Expected: 두 테스트 클래스가 BUILD SUCCESSFUL로 종료된다.

  • REFACTOR: 새 abstraction을 만들지 않고 이번 Task가 추가한 fixture·필드·매핑만 정리한다. 다음 명령을 실행하고 결과를 P1-T1 Progress에 기록한다.
./gradlew ktlintCheck --no-daemon
git diff --check

Expected: 두 명령 모두 exit code 0.

  • Commit: 커밋 전후 규칙 검증과 함께 구현·테스트·Progress 변경만 커밋한다.
work/scripts/check-commit-message-rules.sh --message "feat(creator-channel): 홈 커뮤니티 응답 상태를 추가한다"
git add \
  src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt \
  src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt \
  src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt \
  docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md
git commit -m "feat(creator-channel): 홈 커뮤니티 응답 상태를 추가한다"
work/scripts/check-commit-message-rules.sh HEAD

Phase 1 Gate

Goal 실행 P1-GATE: PRD의 네 확정 요구사항과 작은 변경 범위를 최종 판정한다.

  • 시작 조건: P1-T1의 체크박스, focused test, E2E, 포맷과 Progress 기록 완료.
  • 완료 증거: 아래 자동 검증 통과, PRD 추적성 확인, Gate 결과를 Progress에 기록.
  • 범위 밖: Gate 통과를 위한 test 삭제·skip·완화와 관련 없는 코드 수정.
./gradlew test \
  --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' \
  --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.CreatorChannelHomeEndToEndTest' \
  --no-daemon
./gradlew ktlintCheck --no-daemon
./gradlew tasks --all --no-daemon
git diff --check

Expected: 모든 명령이 exit code 0이고 다음 조건이 충족된다.

  • notices[*]communities[*]에 두 non-null Boolean이 존재한다. (CCHC-001~003)
  • notices[*].isPinned == true, communities[*].isPinned == false가 실제 조회 조건과 일치한다.
  • 댓글 가능 값이 truefalse 모두 그대로 직렬화된다.
  • endpoint, 인증, 기존 홈 응답 필드와 조회 결과가 유지된다. (CCHC-004)
  • 전체 회귀 테스트를 생략한 근거와 대신 실행한 focused·E2E 검증을 Progress에 기록한다.

실행 순서와 의존성

순서 Goal 선행조건 병행 가능 차단 시 다음 행동
1 P1-T1 승인된 PRD 아니요 RED 실패 원인이 신규 필드 누락인지 다시 확인
2 P1-GATE P1-T1 완료 아니요 실패를 소유한 Task에 회귀 수정 goal 추가
P1-T1 → P1-GATE

변경 금지 항목

  • 공개 필드명이나 기존 홈 응답 필드를 근거 없이 변경하지 않는다.
  • domain, service, repository에서 두 Boolean을 재계산하지 않는다.
  • 전용 DTO, helper, 신규 dependency를 추가하지 않는다.
  • 테스트를 삭제·skip·완화하거나 관련 없는 코드를 정리하지 않는다.
  • 기존 완료 기록과 Decision Log를 삭제하거나 덮어쓰지 않는다.

의사결정 및 중단 규칙

  • 구현 범위가 바뀌면 PRD Decision Log와 이 계획을 먼저 갱신한다.
  • 신규 JSON 필드 외 실패가 발생하면 원인을 분리하고 범위 밖 변경을 임의로 적용하지 않는다.
  • targeted test로 영향 범위를 판단할 수 없을 때만 전체 ./gradlew test --no-daemon을 실행한다.
  • 체크박스와 완료 증거, Progress 기록이 모두 충족된 뒤에만 goal을 완료 처리한다.

Progress

실행 전이다. 구현 시 기존 기록을 삭제하지 않고 Goal별 실행 결과를 아래에 누적한다.

P1-T1

  • RED 작성: controller fixture의 일반 게시글을 postId=302L, content="community", isCommentAvailable=false, isPinned=false로 구성하고 controller 및 E2E에 공지/일반 게시글 상태 assertion을 추가했다.
  • RED 확인: ./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' --no-daemon은 exit code 1로 실패했다. CreatorChannelHomeControllerTest.kt:138$.data.notices[0].isPinned assertion에서 PathNotFoundException이 발생해 신규 JSON 필드 미직렬화가 원인임을 확인했으며 compile 및 fixture 오류는 없었다.
  • GREEN 구현: CreatorChannelCommunityPostResponse에 non-null Boolean isCommentAvailable, isPinned을 명시적 @JsonProperty와 함께 추가하고 post.isCommentAvailable, post.isPinned을 직접 매핑했다.
  • GREEN 확인: ./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.CreatorChannelHomeEndToEndTest' --no-daemon은 exit code 0, BUILD SUCCESSFUL in 54s로 종료됐다. controller focused test와 직접 영향받는 홈 E2E가 함께 통과해 공지 true/true, 일반 게시글 false/false controller fixture 및 false/true 실제 E2E 저장값의 매핑과 JSON 이름을 확인했다.
  • REFACTOR 확인: 새 abstraction 없이 계획된 fixture, 응답 필드, 직접 매핑만 유지했다. ./gradlew ktlintCheck --no-daemon은 exit code 0, BUILD SUCCESSFUL in 25s였고 git diff --check도 exit code 0이었다.
  • 전체 회귀 생략: 이번 변경은 공유 홈 DTO의 additive 직렬화 필드 두 개와 직접 영향받는 테스트에 한정되고 controller focused test와 실제 저장·조회 E2E가 모두 통과했으므로 계획의 조건에 따라 전체 ./gradlew test는 실행하지 않았다.
  • 변경 파일: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt, src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt, src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt, docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md.
  • 커밋 검증: 커밋 전 work/scripts/check-commit-message-rules.sh --message "feat(creator-channel): 홈 커뮤니티 응답 상태를 추가한다"가 모든 규칙을 [PASS]로 확인했다. 최종 diff는 계획된 네 파일만 포함하고 비밀값, 관련 없는 변경, agent footer가 없음을 자체 검토했다.

Decision Log

날짜 ID 상태 결정 근거 영향 Goal/문서
2026-08-10 PLAN-DEC-001 확정 하나의 구현 Task와 하나의 Gate로 실행한다. 공유 DTO 한 곳의 additive 변경이며 별도 구현 경계가 없다. P1-T1, P1-GATE
2026-08-10 PLAN-DEC-002 확정 전체 회귀는 조건부로 두고 controller test, 홈 E2E, ktlintCheck를 기본 검증으로 사용한다. 변경이 홈 게시글 응답 직렬화 경계에 한정된다. P1-T1, P1-GATE

발견된 문제

없음.