docs(home): 메인 전체 탭 유료 오디오 요구사항을 문서화한다

This commit is contained in:
2026-07-10 10:16:27 +09:00
parent 2403e8703c
commit 561e181859
2 changed files with 73 additions and 10 deletions

View File

@@ -2,7 +2,7 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`로 task 단위 구현을 진행한다. 각 단계는 체크박스(`- [ ]`)로 진행 상태를 갱신한다.
**Goal:** `GET /api/v2/audio/contents`로 메인 콘텐츠 전체 탭의 오디오, 시리즈, 오리지널, 무료, 포인트 목록을 정렬/요일/페이징 조건에 맞춰 조회한다.
**Goal:** `GET /api/v2/audio/contents`로 메인 콘텐츠 전체 탭의 유료 오디오, 시리즈, 오리지널, 무료, 포인트 목록을 정렬/요일/페이징 조건에 맞춰 조회한다.
**Architecture:** 공개 API controller/facade/response DTO는 `kr.co.vividnext.sodalive.v2.api.content.all` 조립 계층에 둔다. 전체 탭 조회 service, 요청 보정 정책, domain model, port, QueryDSL repository는 `kr.co.vividnext.sodalive.v2.content.all` 하위에 두고 `v2.api.*`에 의존하지 않는다. 기존 `ContentSort`, `SeriesPublishedDaysOfWeek`, 콘텐츠 추천/채널 오디오/채널 시리즈 조회 패턴을 재사용하되 공개 응답 DTO는 전체 탭 전용으로 최소 필드만 둔다.
@@ -25,6 +25,9 @@
- `dayOfWeek`가 invalid이면 요일 조건을 적용하지 않고 `dayOfWeek = null`로 fallback한다.
- `type != SERIES`이면 `dayOfWeek`는 조회 조건에 적용하지 않고 응답에서 `null`로 내려준다.
- `type=ORIGINAL`에는 `dayOfWeek`를 적용하지 않는다.
- `type=AUDIO``price > 0`인 유료 공개 오디오만 조회한다.
- `type=FREE``price == 0`인 무료 공개 오디오만 조회하며 `type=AUDIO` 결과와 겹치지 않는다.
- `type=POINT``isPointAvailable == true` 조건을 유지하고 `type=AUDIO`의 유료 조건을 상속하지 않는다.
- 전체 응답은 `totalCount`, `audios`, `series`, `sort`, `dayOfWeek`, `page`, `size`, `hasNext`를 포함한다.
- `AUDIO`, `FREE`, `POINT``audios`만 채우고 `series`는 빈 배열로 내려준다.
- `SERIES`, `ORIGINAL``series`만 채우고 `audios`는 빈 배열로 내려준다.
@@ -243,6 +246,7 @@ interface MainContentAllQueryPort {
memberId: Long?,
canViewAdultContent: Boolean,
now: LocalDateTime,
onlyPaid: Boolean = false,
onlyFree: Boolean = false,
onlyPointAvailable: Boolean = false
): Int
@@ -254,6 +258,7 @@ interface MainContentAllQueryPort {
sort: ContentSort,
offset: Long,
limit: Int,
onlyPaid: Boolean = false,
onlyFree: Boolean = false,
onlyPointAvailable: Boolean = false
): List<MainContentAllAudio>
@@ -559,6 +564,41 @@ interface MainContentAllQueryPort {
- GREEN: `git diff --check` 성공.
- 확인: `rg -n "duration|publishedDaysOfWeek|isProceeding|contentCount|paidContentCount" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/all src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all` 실행 시 공개 DTO 소스에는 제거 대상 필드가 없고, E2E fixture의 공개 조건 설정과 DTO 테스트의 부재 검증만 검색되었다.
### Phase 6: AUDIO 유료 오디오 필터 보강
- [x] **Task 6.1: service와 port 계약에 AUDIO 전용 유료 필터 추가**
- Files:
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/port/out/MainContentAllQueryPort.kt`
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/application/MainContentAllQueryService.kt`
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/all/application/MainContentAllQueryServiceTest.kt`
- RED: `type=AUDIO`가 audio count/list port를 `onlyPaid=true`, `onlyFree=false`, `onlyPointAvailable=false`로 호출하는 실패 테스트를 작성한다.
- RED: `type=FREE`는 `onlyFree=true`, `onlyPaid=false`를 유지하고, `type=POINT`는 `onlyPointAvailable=true`, `onlyPaid=false`를 유지하는 실패 테스트를 작성한다.
- 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest`
- GREEN: `MainContentAllQueryPort.countAudios(...)`, `findAudios(...)`에 `onlyPaid` 파라미터를 추가하고 `MainContentAllQueryService`의 `MainContentAllType.AUDIO` 분기에서만 `onlyPaid = true`를 전달한다.
- REFACTOR: `FREE`, `POINT` 분기에서 `AUDIO` 전용 유료 필터가 전파되지 않는지 fake port 검증 필드를 정리한다.
- 기대 결과: `resolvedType == MainContentAllType.AUDIO`일 때만 유료 필터가 port 계약에 명시된다.
- 검증 기록:
- RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest` 실행 시 `MainContentAllQueryServiceTest.kt:37`의 `assertTrue(port.lastOnlyPaid)` 실패로 `AUDIO` 분기가 `onlyPaid=true`를 전달하지 않음을 확인했다.
- GREEN: 동일 명령 성공으로 `AUDIO`는 `onlyPaid=true`, `FREE`/`POINT`는 `onlyPaid=false`로 port에 전달됨을 확인했다.
- [x] **Task 6.2: repository와 E2E에서 AUDIO 무료 제외 검증**
- Files:
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepository.kt`
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/all/adapter/out/persistence/DefaultMainContentAllQueryRepositoryTest.kt`
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all/adapter/in/web/MainContentAllEndToEndTest.kt`
- RED: repository 테스트에 유료 오디오와 무료 오디오 fixture를 함께 만들고, `onlyPaid=true`인 `countAudios(...)`와 `findAudios(...)`가 `price == 0` 오디오를 제외하는 실패 테스트를 작성한다.
- RED: `onlyFree=true`는 기존처럼 `price == 0`만 반환하고, `onlyPointAvailable=true`는 `isPointAvailable == true` 조건을 유지하는 회귀 테스트를 함께 확인한다.
- RED: E2E 테스트에서 `GET /api/v2/audio/contents?type=AUDIO`와 type 미지정 기본 조회가 무료 오디오를 반환하지 않는 실패 테스트를 작성한다.
- 실패 확인:
- `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest`
- `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest`
- GREEN: repository의 오디오 조회 조건에 `onlyPaid=true`일 때만 `price > 0` 조건을 추가한다.
- REFACTOR: `onlyPaid`, `onlyFree`, `onlyPointAvailable` 조건이 각 type의 명시적 필터로만 적용되는지 테스트명과 fixture를 정리한다.
- 기대 결과: `AUDIO` 목록과 전체 개수에서 무료 콘텐츠가 제외되고, `FREE`/`POINT` 조회 계약은 기존 의미를 유지한다.
- 검증 기록:
- RED: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest` 실행 시 `DefaultMainContentAllQueryRepositoryTest.kt:70`, `MainContentAllEndToEndTest.kt:83` 실패로 `onlyPaid` 조건이 무료 오디오를 제외하지 못함을 확인했다.
- GREEN: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest` 성공으로 service, repository, E2E의 AUDIO 유료 필터를 확인했다.
---
## 4. 실행 명령
@@ -570,6 +610,7 @@ interface MainContentAllQueryPort {
- Service 테스트: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest`
- Repository 테스트: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest`
- End-to-end 테스트: `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest`
- AUDIO 유료 필터 회귀 테스트: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest`
- 전체 신규 패키지 테스트: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.content.all.*' --tests 'kr.co.vividnext.sodalive.v2.api.content.all.*'`
- 포맷 검증: `./gradlew ktlintCheck`
- 문서 변경 후 명령 유효성 확인: `./gradlew tasks --all`
@@ -612,3 +653,17 @@ interface MainContentAllQueryPort {
- GREEN: `./gradlew ktlintCheck` 성공.
- GREEN: `git diff --check` 성공.
- 확인: `rg -n "duration|publishedDaysOfWeek|isProceeding|contentCount|paidContentCount" src/main/kotlin/kr/co/vividnext/sodalive/v2/api/content/all src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/all` 실행 시 공개 DTO 소스에는 제거 대상 필드가 없고, E2E fixture의 공개 조건 설정과 DTO 테스트의 부재 검증만 검색되었다.
- 2026-07-10 AUDIO 유료 콘텐츠 문서 선반영
- 문서: `docs/20260624_메인_콘텐츠_전체_탭_API/prd.md`에 `type=AUDIO`는 `price > 0` 유료 공개 오디오만 조회하도록 요구사항을 보강했다.
- 문서: `docs/20260624_메인_콘텐츠_전체_탭_API/plan-task.md`에 `onlyPaid` port 계약 초안과 Phase 6 미완료 task를 추가했다.
- GREEN: `git diff --check` 성공.
- GREEN: `./gradlew tasks --all`은 최초 샌드박스 실행에서 `~/.gradle` lock 파일 접근 권한으로 실패했고, 권한 승인 후 재실행해 성공했다.
- 참고: 이번 단계는 사용자 요청에 따라 연관 문서만 수정했으며 production/test 코드는 변경하지 않았다.
- 2026-07-10 Phase 6 AUDIO 유료 필터 구현 검증
- RED: service 테스트에서 `MainContentAllQueryServiceTest.kt:37`의 `assertTrue(port.lastOnlyPaid)` 실패로 `AUDIO` 분기의 `onlyPaid` 미전달을 확인했다.
- GREEN: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest` 성공으로 `AUDIO`만 `onlyPaid=true`를 전달하고 `FREE`/`POINT`는 기존 필터 의미를 유지함을 확인했다.
- RED: repository/E2E 테스트에서 `DefaultMainContentAllQueryRepositoryTest.kt:70`, `MainContentAllEndToEndTest.kt:83` 실패로 `onlyPaid` 조건이 `price == 0` 무료 오디오를 제외하지 못함을 확인했다.
- GREEN: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest --tests kr.co.vividnext.sodalive.v2.content.all.adapter.out.persistence.DefaultMainContentAllQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.api.content.all.adapter.in.web.MainContentAllEndToEndTest` 성공으로 명시적 `type=AUDIO`와 기본 type 조회에서 무료 오디오가 제외됨을 확인했다.
- GREEN: `./gradlew test` 성공.
- GREEN: `./gradlew ktlintCheck` 성공.
- GREEN: `git diff --check` 성공.