From af347a71a26a0fa0bf8652e655e830a69b68c75f Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 10 Aug 2026 11:35:30 +0900 Subject: [PATCH 1/3] =?UTF-8?q?docs(creator-channel):=20=ED=99=88=20?= =?UTF-8?q?=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=99=95=EC=9E=A5=20PRD=EC=99=80=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B3=84=ED=9A=8D=20=EB=AC=B8=EC=84=9C=EB=A5=BC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 272 ++++++++++++++++++ .../prd.md | 144 ++++++++++ 2 files changed, 416 insertions(+) create mode 100644 docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md create mode 100644 docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md diff --git a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md new file mode 100644 index 00000000..a00684c4 --- /dev/null +++ b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md @@ -0,0 +1,272 @@ +# 크리에이터 채널 홈 커뮤니티 응답 확장 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:** 크리에이터 채널 홈의 `notices`와 `communities` 게시글에 실제 고정 여부와 댓글 가능 여부를 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-T1` | + +## 현재 상태 + +| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | +|---:|---|---:|---|---| +| 1 | 대기 | `0/1` | `P1-T1` | 없음 | + +- 동시에 하나의 미완료 goal만 운용한다. +- 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. +- goal 실행 시 token budget을 설정하지 않는다. + +## 범위 + +### 포함 + +- `GET /api/v2/creator-channels/{creatorId}/home`의 `notices[*]`, `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 직렬화 방식을 유지한다. +- 기존 공유 `CreatorChannelCommunityPostResponse`와 `from` 변환만 확장한다. +- 공개 필드명은 `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-T1`과 `P1-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`를 사용한다. + +```kotlin +communities = listOf( + post.copy( + postId = 302L, + content = "community", + isCommentAvailable = false, + isPinned = false + ) +) +``` + +```kotlin +.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은 실제 저장값과 조회 조건을 검증한다. + +```kotlin +.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 경로에 값이 없어 실패하는지 확인한다. + +```bash +./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`에서 도메인 값을 직접 매핑한다. + +```kotlin +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 이름, 실제 조회값이 모두 통과하는지 확인한다. + +```bash +./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에 기록한다. + +```bash +./gradlew ktlintCheck --no-daemon +git diff --check +``` + +**Expected:** 두 명령 모두 exit code `0`. + +- [ ] **Commit:** 커밋 전후 규칙 검증과 함께 구현·테스트·Progress 변경만 커밋한다. + +```bash +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·완화와 관련 없는 코드 수정. + +```bash +./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`가 실제 조회 조건과 일치한다. +- [ ] 댓글 가능 값이 `true`와 `false` 모두 그대로 직렬화된다. +- [ ] endpoint, 인증, 기존 홈 응답 필드와 조회 결과가 유지된다. (`CCHC-004`) +- [ ] 전체 회귀 테스트를 생략한 근거와 대신 실행한 focused·E2E 검증을 Progress에 기록한다. + +## 실행 순서와 의존성 + +| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 | +|---:|---|---|---|---| +| 1 | `P1-T1` | 승인된 PRD | 아니요 | RED 실패 원인이 신규 필드 누락인지 다시 확인 | +| 2 | `P1-GATE` | `P1-T1` 완료 | 아니요 | 실패를 소유한 Task에 회귀 수정 goal 추가 | + +```text +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별 실행 결과를 아래에 누적한다. + +## 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` | + +## 발견된 문제 + +없음. diff --git a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md new file mode 100644 index 00000000..a0cf917e --- /dev/null +++ b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md @@ -0,0 +1,144 @@ +# PRD: 크리에이터 채널 홈 커뮤니티 응답 확장 + +## 문서 정보 + +| 항목 | 내용 | +|---|---| +| 문서 상태 | 구현 기준 확정 | +| 작성일 | 2026-08-10 | +| 최종 수정일 | 2026-08-10 | +| 대상 제품 | 크리에이터 채널 홈 API | +| 작성자·결정권자 | 사용자 | +| 관련 API Contract | 별도 문서 없음, 이 문서의 `7. API 계약`을 기준으로 함 | +| 관련 구현 계획 | `docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md` | +| 관련 review | 없음 | + +## 1. Overview + +크리에이터 채널 홈 API의 `notices`와 `communities` 게시글 응답에 고정 여부와 댓글 가능 여부를 각각 `isPinned`, `isCommentAvailable`로 제공한다. 기존 조회 결과가 이미 보유한 값을 응답에 그대로 반영해 클라이언트가 별도 추론 없이 게시글 상태를 표시할 수 있게 한다. + +## 2. Problem Statement + +- 현재 `GET /api/v2/creator-channels/{creatorId}/home`의 게시글 도메인 모델에는 고정 여부와 댓글 가능 여부가 있지만 홈 응답 DTO에는 두 값이 없다. +- 클라이언트는 홈의 게시글이 고정 글인지, 댓글 작성이 가능한지 응답만으로 일관되게 판단할 수 없다. +- `notices`와 `communities`가 같은 `CreatorChannelCommunityPostResponse`를 사용하므로 두 배열의 계약을 함께 확장해야 한다. + +문제를 해결했다는 판단은 두 배열의 각 게시글에 실제 도메인 값과 일치하는 non-null Boolean 필드가 직렬화되는지로 한다. + +## 3. Goals + +- 홈 응답의 `notices[*]`와 `communities[*]`에 `isPinned`를 제공한다. +- 홈 응답의 `notices[*]`와 `communities[*]`에 `isCommentAvailable`을 제공한다. +- 기존 커뮤니티 조회 결과의 값을 변형하거나 재계산하지 않고 그대로 사용한다. +- 기존 홈 API의 endpoint, 인증 정책과 기존 응답 필드를 유지한다. + +## 4. Non-Goals + +- 커뮤니티 조회 조건, 고정 정렬, 최대 노출 개수는 변경하지 않는다. +- 댓글 작성·수정·삭제 정책과 API는 변경하지 않는다. +- 커뮤니티 탭 및 게시글 상세 API 계약은 변경하지 않는다. +- DB schema, entity, domain model과 repository query는 변경하지 않는다. +- `notices`와 `communities` 전용 응답 DTO를 새로 분리하지 않는다. +- 관련 없는 홈 응답 필드나 패키지 구조는 리팩터링하지 않는다. + +## 5. Target Users and Permissions + +- 대상 사용자: 인증 후 크리에이터 채널 홈을 조회하는 앱 사용자 +- 인증 및 권한: 기존 홈 API 정책을 그대로 사용한다. +- 차단, 성인 콘텐츠와 구매 여부 정책: 기존 홈 API 조회 결과를 그대로 사용한다. + +## 6. 기능 요구사항 + +| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 | +|---|---|---|---|---| +| `CCHC-001` | 확정 | `notices[*]`와 `communities[*]`에 `isPinned`를 제공한다. | JSON 필드명이 정확히 `isPinned`이고 값은 `CreatorChannelCommunityPost.isPinned`와 일치한다. | `7. API 계약`, `P1-T1` | +| `CCHC-002` | 확정 | `notices[*]`와 `communities[*]`에 `isCommentAvailable`을 제공한다. | JSON 필드명이 정확히 `isCommentAvailable`이고 값은 `CreatorChannelCommunityPost.isCommentAvailable`과 일치한다. | `7. API 계약`, `P1-T1` | +| `CCHC-003` | 확정 | 두 필드는 non-null Boolean으로 응답한다. | 게시글이 존재하면 두 필드가 누락되거나 `null`이 되지 않는다. | `P1-T1`, `P1-GATE` | +| `CCHC-004` | 확정 | 기존 홈 API 계약과 조회 정책을 보존한다. | endpoint, 인증 정책, 기존 응답 필드와 게시글 선택·정렬 결과가 변경되지 않는다. | `P1-GATE` | + +### 상태별 기대값 + +| 배열 | `isPinned` | `isCommentAvailable` | +|---|---:|---| +| `notices` | 해당 게시글의 실제 값. 현재 홈 조회 조건상 `true` | 해당 게시글의 댓글 허용 설정값 | +| `communities` | 해당 게시글의 실제 값. 현재 홈 조회 조건상 `false` | 해당 게시글의 댓글 허용 설정값 | + +- 댓글 수가 `0`이어도 `isCommentAvailable`을 별도로 계산하지 않는다. +- 빈 `notices` 또는 `communities`는 기존처럼 빈 배열로 응답한다. + +## 7. API 계약 + +### 7.1 Endpoint + +- Method: `GET` +- Path: `/api/v2/creator-channels/{creatorId}/home` +- 요청과 인증 정책: 변경 없음 + +### 7.2 응답 확장 + +`data.notices[*]`와 `data.communities[*]`에 다음 필드를 추가한다. + +| 필드 | 타입 | nullable | 의미 | +|---|---|---:|---| +| `isPinned` | Boolean | 아니요 | 해당 커뮤니티 게시글의 고정 여부 | +| `isCommentAvailable` | Boolean | 아니요 | 해당 커뮤니티 게시글의 댓글 작성 허용 여부 | + +예시: + +```json +{ + "data": { + "notices": [ + { + "postId": 301, + "isPinned": true, + "isCommentAvailable": true + } + ], + "communities": [ + { + "postId": 302, + "isPinned": false, + "isCommentAvailable": false + } + ] + } +} +``` + +예시는 추가 필드와 배열별 의미만 나타내며 기존 게시글 응답 필드는 그대로 유지한다. + +## 8. 기술적 제약 + +- Kotlin + Java 17, Spring Boot 2.7.14와 기존 Jackson 직렬화 방식을 유지한다. +- 기존 공유 `CreatorChannelCommunityPostResponse`와 `from` 변환을 재사용한다. +- `is*` JSON 이름이 변경되지 않도록 기존 Boolean 응답 필드의 `@JsonProperty` 관례를 따른다. +- 신규 dependency와 별도 abstraction을 추가하지 않는다. +- 구현은 응답 DTO와 직접 영향받는 테스트로 제한한다. + +## 9. 성공 기준 + +- [ ] `notices[*].isPinned`와 `notices[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) +- [ ] `communities[*].isPinned`와 `communities[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) +- [ ] 기존 홈 API 응답 및 조회 흐름 회귀 테스트가 통과한다. (`CCHC-004`) +- [ ] `ktlintCheck`가 통과한다. +- [ ] 전체 회귀 테스트를 생략하면 작은 응답 DTO 변경이라는 근거와 대신 실행한 focused·영향 범위 테스트를 검증 기록에 남긴다. + +## 10. Open Questions + +없음. + +## 11. 요구사항 추적표 + +| 요구사항 범위 | 계획 Phase | Goal | 자동 검증 | 수동 검증 | +|---|---:|---|---|---| +| `CCHC-001~003` | 1 | `P1-T1` | `CreatorChannelHomeControllerTest` | 홈 응답 JSON 필드명과 Boolean 값 확인 | +| `CCHC-004` | 1 | `P1-GATE` | `CreatorChannelHomeEndToEndTest`, `ktlintCheck` | 기존 필드 유지 여부 확인 | + +## 12. Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·Goal | +|---|---|---|---|---|---| +| 2026-08-10 | `DEC-001` | 확정 | `isPinned`, `isCommentAvailable`을 `notices`와 `communities` 모두에 추가한다. | 두 배열이 같은 게시글 응답 DTO를 사용하며 사용자가 인터뷰 선택지 A를 승인했다. | `CCHC-001~004`, `P1-T1`, `P1-GATE` | +| 2026-08-10 | `DEC-002` | 확정 | 공유 응답 DTO를 확장하고 전용 DTO는 분리하지 않는다. | 기존 도메인 값과 변환 경로를 재사용하는 최소 변경이다. | `CCHC-001~004`, `P1-T1` | +| 2026-08-10 | `DEC-003` | 확정 | PRD를 구현 기준으로 승인한다. | 사용자가 작성된 PRD를 검토하고 승인했다. | 문서 전체, `plan-task.md` | From d99104d945743663803b506417b4d2ad3ac4b0a2 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 10 Aug 2026 12:09:58 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat(creator-channel):=20=ED=99=88=20?= =?UTF-8?q?=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 29 +++++++++++++------ .../home/dto/CreatorChannelHomeResponse.kt | 6 ++++ .../home/CreatorChannelHomeEndToEndTest.kt | 4 +++ .../web/CreatorChannelHomeControllerTest.kt | 13 ++++++++- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md index a00684c4..17b56e20 100644 --- a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md +++ b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md @@ -12,19 +12,19 @@ | 문서 항목 | 내용 | |---|---| -| 상태 | 구현 대기 | +| 상태 | 구현 중 | | 작성일 | 2026-08-10 | | 요구사항 기준 | `docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md` | | API 기준 | PRD `7. API 계약` | | 현재 Phase | Phase 1: 홈 커뮤니티 상태 응답 확장 | | 현재 활성 Goal | 없음 | -| 다음 Goal | `P1-T1` | +| 다음 Goal | `P1-GATE` | ## 현재 상태 | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| -| 1 | 대기 | `0/1` | `P1-T1` | 없음 | +| 1 | 진행 중 | `1/1` | `P1-GATE` | `P1-GATE` 검증 대기 | - 동시에 하나의 미완료 goal만 운용한다. - 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. @@ -87,7 +87,7 @@ - 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`를 사용한다. +- [x] **RED:** `CreatorChannelHomeControllerTest.shouldReturnCreatorChannelHomeForAuthenticatedMember`의 일반 커뮤니티 fixture를 아래처럼 공지와 반대 상태로 만들고, 네 JSON 경로 assertion을 추가한다. 같은 assertion을 `CreatorChannelHomeEndToEndTest.shouldAssembleCreatorChannelHomeSectionsThroughSingleHttpRequest`에도 추가하되 E2E fixture의 댓글 가능 값은 기존 `true`를 사용한다. ```kotlin communities = listOf( @@ -116,7 +116,7 @@ E2E assertion은 실제 저장값과 조회 조건을 검증한다. .andExpect(jsonPath("$.data.communities[0].isCommentAvailable").value(true)) ``` -- [ ] **RED 확인:** 다음 focused test를 실행해 `$.data.notices[0].isPinned` 등 신규 JSON 경로에 값이 없어 실패하는지 확인한다. +- [x] **RED 확인:** 다음 focused test를 실행해 `$.data.notices[0].isPinned` 등 신규 JSON 경로에 값이 없어 실패하는지 확인한다. ```bash ./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.creator.channel.home.adapter.in.web.CreatorChannelHomeControllerTest' --no-daemon @@ -124,7 +124,7 @@ E2E assertion은 실제 저장값과 조회 조건을 검증한다. **Expected:** 테스트가 신규 필드 미직렬화로 실패한다. compile 오류나 fixture 오류는 RED 증거로 인정하지 않는다. -- [ ] **GREEN:** `CreatorChannelCommunityPostResponse`에 기존 커뮤니티 탭 DTO와 같은 `@JsonProperty` 관례로 필드를 추가하고 `from`에서 도메인 값을 직접 매핑한다. +- [x] **GREEN:** `CreatorChannelCommunityPostResponse`에 기존 커뮤니티 탭 DTO와 같은 `@JsonProperty` 관례로 필드를 추가하고 `from`에서 도메인 값을 직접 매핑한다. ```kotlin data class CreatorChannelCommunityPostResponse( @@ -171,7 +171,7 @@ data class CreatorChannelCommunityPostResponse( } ``` -- [ ] **GREEN 확인:** controller test와 홈 E2E를 함께 실행해 공유 DTO 매핑, JSON 이름, 실제 조회값이 모두 통과하는지 확인한다. +- [x] **GREEN 확인:** controller test와 홈 E2E를 함께 실행해 공유 DTO 매핑, JSON 이름, 실제 조회값이 모두 통과하는지 확인한다. ```bash ./gradlew test \ @@ -182,7 +182,7 @@ data class CreatorChannelCommunityPostResponse( **Expected:** 두 테스트 클래스가 `BUILD SUCCESSFUL`로 종료된다. -- [ ] **REFACTOR:** 새 abstraction을 만들지 않고 이번 Task가 추가한 fixture·필드·매핑만 정리한다. 다음 명령을 실행하고 결과를 `P1-T1` Progress에 기록한다. +- [x] **REFACTOR:** 새 abstraction을 만들지 않고 이번 Task가 추가한 fixture·필드·매핑만 정리한다. 다음 명령을 실행하고 결과를 `P1-T1` Progress에 기록한다. ```bash ./gradlew ktlintCheck --no-daemon @@ -191,7 +191,7 @@ git diff --check **Expected:** 두 명령 모두 exit code `0`. -- [ ] **Commit:** 커밋 전후 규칙 검증과 함께 구현·테스트·Progress 변경만 커밋한다. +- [x] **Commit:** 커밋 전후 규칙 검증과 함께 구현·테스트·Progress 변경만 커밋한다. ```bash work/scripts/check-commit-message-rules.sh --message "feat(creator-channel): 홈 커뮤니티 응답 상태를 추가한다" @@ -260,6 +260,17 @@ P1-T1 → P1-GATE 실행 전이다. 구현 시 기존 기록을 삭제하지 않고 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/문서 | diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt index 8859108a..6e14c92a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/dto/CreatorChannelHomeResponse.kt @@ -181,8 +181,12 @@ data class CreatorChannelCommunityPostResponse( 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 ) { @@ -199,8 +203,10 @@ data class CreatorChannelCommunityPostResponse( 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 ) } diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt index 9389351d..9970dff6 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/CreatorChannelHomeEndToEndTest.kt @@ -66,12 +66,16 @@ class CreatorChannelHomeEndToEndTest @Autowired constructor( .andExpect(jsonPath("$.data.latestAudioContent.audioContentId").value(fixture.latestAudioId)) .andExpect(jsonPath("$.data.channelDonations[0].can").value(500)) .andExpect(jsonPath("$.data.notices[0].postId").value(fixture.noticeId)) + .andExpect(jsonPath("$.data.notices[0].isPinned").value(true)) + .andExpect(jsonPath("$.data.notices[0].isCommentAvailable").value(true)) .andExpect(jsonPath("$.data.schedules[0].targetId").value(fixture.liveScheduleId)) .andExpect(jsonPath("$.data.schedules[1].targetId").value(fixture.audioScheduleId)) .andExpect(jsonPath("$.data.audioContents[0].audioContentId").value(fixture.listAudioId)) .andExpect(jsonPath("$.data.audioContents[1].audioContentId").value(fixture.firstAudioId)) .andExpect(jsonPath("$.data.series[0].seriesId").value(fixture.seriesId)) .andExpect(jsonPath("$.data.communities[0].postId").value(fixture.communityId)) + .andExpect(jsonPath("$.data.communities[0].isPinned").value(false)) + .andExpect(jsonPath("$.data.communities[0].isCommentAvailable").value(true)) .andExpect(jsonPath("$.data.fanTalk.latestFanTalk.fanTalkId").value(fixture.fanTalkId)) .andExpect(jsonPath("$.data.introduce").value("e2e introduce")) .andExpect(jsonPath("$.data.activity.audioContentCount").value(3)) diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt index 0a2785cb..a95f29f3 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/api/creator/channel/home/adapter/in/web/CreatorChannelHomeControllerTest.kt @@ -135,7 +135,11 @@ class CreatorChannelHomeControllerTest @Autowired constructor( .andExpect(jsonPath("$.data.notices[0].dateUtc").value("2026-06-12T04:00:00Z")) .andExpect(jsonPath("$.data.notices[0].imageUrl").doesNotExist()) .andExpect(jsonPath("$.data.notices[0].audioUrl").doesNotExist()) + .andExpect(jsonPath("$.data.notices[0].isPinned").value(true)) + .andExpect(jsonPath("$.data.notices[0].isCommentAvailable").value(true)) .andExpect(jsonPath("$.data.notices[0].isLiked").value(true)) + .andExpect(jsonPath("$.data.communities[0].isPinned").value(false)) + .andExpect(jsonPath("$.data.communities[0].isCommentAvailable").value(false)) .andExpect(jsonPath("$.data.communities[0].isLiked").value(true)) .andExpect(jsonPath("$.data.series[0].isNew").value(true)) .andExpect(jsonPath("$.data.series[0].isOriginal").value(true)) @@ -283,7 +287,14 @@ class CreatorChannelHomeControllerTest @Autowired constructor( isOriginal = true ) ), - communities = listOf(post.copy(postId = 302L, content = "community")), + communities = listOf( + post.copy( + postId = 302L, + content = "community", + isCommentAvailable = false, + isPinned = false + ) + ), fanTalk = CreatorChannelFanTalkSummary( totalCount = 1, latestFanTalk = CreatorChannelFanTalk( From b18e40d4fc771b25a23a1e922d9220bfc1a3915a Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 10 Aug 2026 14:15:18 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs(creator-channel):=20=ED=99=88=20?= =?UTF-8?q?=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=A5=BC=20=EA=B8=B0=EB=A1=9D=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 32 ++++++++++++------- .../prd.md | 10 +++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md index 17b56e20..c97ec81f 100644 --- a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md +++ b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/plan-task.md @@ -12,19 +12,20 @@ | 문서 항목 | 내용 | |---|---| -| 상태 | 구현 중 | +| 상태 | 구현 완료 | | 작성일 | 2026-08-10 | | 요구사항 기준 | `docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md` | | API 기준 | PRD `7. API 계약` | -| 현재 Phase | Phase 1: 홈 커뮤니티 상태 응답 확장 | +| 현재 Phase | Phase 1 완료: 홈 커뮤니티 상태 응답 확장 | | 현재 활성 Goal | 없음 | -| 다음 Goal | `P1-GATE` | +| 다음 Goal | 없음 | +| Gate | `P1-GATE` 완료 | ## 현재 상태 | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| -| 1 | 진행 중 | `1/1` | `P1-GATE` | `P1-GATE` 검증 대기 | +| 1 | 완료 | `1/1` | 없음 | 없음 | - 동시에 하나의 미완료 goal만 운용한다. - 완료된 Task와 검증 기록은 되돌리거나 삭제하지 않는다. @@ -224,11 +225,11 @@ git diff --check **Expected:** 모든 명령이 exit code `0`이고 다음 조건이 충족된다. -- [ ] `notices[*]`와 `communities[*]`에 두 non-null Boolean이 존재한다. (`CCHC-001~003`) -- [ ] `notices[*].isPinned == true`, `communities[*].isPinned == false`가 실제 조회 조건과 일치한다. -- [ ] 댓글 가능 값이 `true`와 `false` 모두 그대로 직렬화된다. -- [ ] endpoint, 인증, 기존 홈 응답 필드와 조회 결과가 유지된다. (`CCHC-004`) -- [ ] 전체 회귀 테스트를 생략한 근거와 대신 실행한 focused·E2E 검증을 Progress에 기록한다. +- [x] `notices[*]`와 `communities[*]`에 두 non-null Boolean이 존재한다. (`CCHC-001~003`) +- [x] `notices[*].isPinned == true`, `communities[*].isPinned == false`가 실제 조회 조건과 일치한다. +- [x] 댓글 가능 값이 `true`와 `false` 모두 그대로 직렬화된다. +- [x] endpoint, 인증, 기존 홈 응답 필드와 조회 결과가 유지된다. (`CCHC-004`) +- [x] 전체 회귀 테스트를 생략한 근거와 대신 실행한 focused·E2E 검증을 Progress에 기록한다. ## 실행 순서와 의존성 @@ -258,7 +259,7 @@ P1-T1 → P1-GATE ## Progress -실행 전이다. 구현 시 기존 기록을 삭제하지 않고 Goal별 실행 결과를 아래에 누적한다. +기존 기록을 삭제하지 않고 Goal별 실행 결과를 아래에 누적한다. ### P1-T1 @@ -269,7 +270,16 @@ P1-T1 → P1-GATE - 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가 없음을 자체 검토했다. +- 커밋 검증: 커밋 전 `work/scripts/check-commit-message-rules.sh --message "feat(creator-channel): 홈 커뮤니티 응답 상태를 추가한다"`가 모든 규칙을 `[PASS]`로 확인했다. 최종 diff는 계획된 네 파일만 포함하고 비밀값, 관련 없는 변경, agent footer가 없음을 자체 검토했다. 커밋 후 `work/scripts/check-commit-message-rules.sh HEAD`를 커밋 `d99104d945743663803b506417b4d2ad3ac4b0a2`에 실행해 모든 규칙이 `[PASS]`임을 확인했다. + +### P1-GATE + +- 자동 검증: `./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 40s`였고 테스트 산출물이 `UP-TO-DATE`였다. 실제 HTTP 경계를 새로 실행하기 위해 `./gradlew cleanTest --no-daemon`을 exit code `0`, `BUILD SUCCESSFUL in 16s`로 수행한 뒤 같은 Gate 테스트 명령을 다시 실행해 `:test` 실행과 exit code `0`, `BUILD SUCCESSFUL in 48s`를 확인했다. +- 정적·빌드 검증: `./gradlew ktlintCheck --no-daemon`은 exit code `0`, `BUILD SUCCESSFUL in 1m 22s`, `./gradlew tasks --all --no-daemon`은 exit code `0`, `BUILD SUCCESSFUL in 22s`, `git diff --check`는 출력 없이 exit code `0`이었다. +- 요구사항 추적: `CCHC-001~003`은 `notices[*]`, `communities[*]`의 non-null Boolean `isPinned`, `isCommentAvailable`과 도메인 값 직접 매핑으로 확인했다. 실제 저장·조회 E2E에서 공지는 `isPinned=true`, 일반 게시글은 `isPinned=false`였고 controller fixture와 E2E를 합쳐 `isCommentAvailable=true/false`가 모두 그대로 직렬화됨을 확인했다. `CCHC-004`는 기존 `GET /api/v2/creator-channels/{creatorId}/home`, 비회원 `401`, 인증 회원 `200`, 기존 홈 섹션·필드·조회 결과 assertion의 유지와 focused·E2E 통과로 확인했다. +- 실제 HTTP 표면: `CreatorChannelHomeEndToEndTest.shouldAssembleCreatorChannelHomeSectionsThroughSingleHttpRequest`가 인증된 MockMvc `GET`을 controller에서 service, repository, H2 database 조회까지 통과시켜 실제 API 응답의 공지와 일반 게시글 값을 검증했다. +- 전체 회귀 생략: 변경은 공유 홈 응답 DTO의 additive Boolean 두 필드와 직접 직렬화 경계에 한정되고 focused controller test와 실제 저장·조회 MockMvc E2E로 영향을 판정할 수 있었으며 모호한 실패가 없었다. 계획의 실행 조건에 따라 전체 `./gradlew test`는 실행하지 않았다. +- 변경 문서 상태: 구현·테스트는 커밋 `d99104d945743663803b506417b4d2ad3ac4b0a2`에 있고, Gate에서는 기존 `P1-T1` 기록과 이미 존재한 커밋 후 검증 문장을 보존한 채 `plan-task.md`와 `prd.md`의 완료 상태만 변경했다. Kotlin 코드, 테스트, Decision Log는 변경하지 않았다. ## Decision Log diff --git a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md index a0cf917e..d098f8e9 100644 --- a/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md +++ b/docs/20260810_크리에이터_채널_홈_커뮤니티_응답_확장/prd.md @@ -118,11 +118,11 @@ ## 9. 성공 기준 -- [ ] `notices[*].isPinned`와 `notices[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) -- [ ] `communities[*].isPinned`와 `communities[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) -- [ ] 기존 홈 API 응답 및 조회 흐름 회귀 테스트가 통과한다. (`CCHC-004`) -- [ ] `ktlintCheck`가 통과한다. -- [ ] 전체 회귀 테스트를 생략하면 작은 응답 DTO 변경이라는 근거와 대신 실행한 focused·영향 범위 테스트를 검증 기록에 남긴다. +- [x] `notices[*].isPinned`와 `notices[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) +- [x] `communities[*].isPinned`와 `communities[*].isCommentAvailable`이 실제 값으로 응답된다. (`CCHC-001~003`) +- [x] 기존 홈 API 응답 및 조회 흐름 회귀 테스트가 통과한다. (`CCHC-004`) +- [x] `ktlintCheck`가 통과한다. +- [x] 전체 회귀 테스트를 생략하면 작은 응답 DTO 변경이라는 근거와 대신 실행한 focused·영향 범위 테스트를 검증 기록에 남긴다. ## 10. Open Questions