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 단위 구현을 진행한다. 각 단계는 체크박스(`- [ ]`)로 진행 상태를 갱신한다. > **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는 전체 탭 전용으로 최소 필드만 둔다. **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한다. - `dayOfWeek`가 invalid이면 요일 조건을 적용하지 않고 `dayOfWeek = null`로 fallback한다.
- `type != SERIES`이면 `dayOfWeek`는 조회 조건에 적용하지 않고 응답에서 `null`로 내려준다. - `type != SERIES`이면 `dayOfWeek`는 조회 조건에 적용하지 않고 응답에서 `null`로 내려준다.
- `type=ORIGINAL`에는 `dayOfWeek`를 적용하지 않는다. - `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`를 포함한다. - 전체 응답은 `totalCount`, `audios`, `series`, `sort`, `dayOfWeek`, `page`, `size`, `hasNext`를 포함한다.
- `AUDIO`, `FREE`, `POINT``audios`만 채우고 `series`는 빈 배열로 내려준다. - `AUDIO`, `FREE`, `POINT``audios`만 채우고 `series`는 빈 배열로 내려준다.
- `SERIES`, `ORIGINAL``series`만 채우고 `audios`는 빈 배열로 내려준다. - `SERIES`, `ORIGINAL``series`만 채우고 `audios`는 빈 배열로 내려준다.
@@ -243,6 +246,7 @@ interface MainContentAllQueryPort {
memberId: Long?, memberId: Long?,
canViewAdultContent: Boolean, canViewAdultContent: Boolean,
now: LocalDateTime, now: LocalDateTime,
onlyPaid: Boolean = false,
onlyFree: Boolean = false, onlyFree: Boolean = false,
onlyPointAvailable: Boolean = false onlyPointAvailable: Boolean = false
): Int ): Int
@@ -254,6 +258,7 @@ interface MainContentAllQueryPort {
sort: ContentSort, sort: ContentSort,
offset: Long, offset: Long,
limit: Int, limit: Int,
onlyPaid: Boolean = false,
onlyFree: Boolean = false, onlyFree: Boolean = false,
onlyPointAvailable: Boolean = false onlyPointAvailable: Boolean = false
): List<MainContentAllAudio> ): List<MainContentAllAudio>
@@ -559,6 +564,41 @@ interface MainContentAllQueryPort {
- GREEN: `git diff --check` 성공. - 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 테스트의 부재 검증만 검색되었다. - 확인: `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. 실행 명령 ## 4. 실행 명령
@@ -570,6 +610,7 @@ interface MainContentAllQueryPort {
- Service 테스트: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.all.application.MainContentAllQueryServiceTest` - 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` - 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` - 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 test --tests 'kr.co.vividnext.sodalive.v2.content.all.*' --tests 'kr.co.vividnext.sodalive.v2.api.content.all.*'`
- 포맷 검증: `./gradlew ktlintCheck` - 포맷 검증: `./gradlew ktlintCheck`
- 문서 변경 후 명령 유효성 확인: `./gradlew tasks --all` - 문서 변경 후 명령 유효성 확인: `./gradlew tasks --all`
@@ -612,3 +653,17 @@ interface MainContentAllQueryPort {
- GREEN: `./gradlew ktlintCheck` 성공. - GREEN: `./gradlew ktlintCheck` 성공.
- GREEN: `git diff --check` 성공. - 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 테스트의 부재 검증만 검색되었다. - 확인: `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` 성공.

View File

@@ -1,7 +1,7 @@
# PRD: 메인 콘텐츠 전체 탭 API # PRD: 메인 콘텐츠 전체 탭 API
## 1. Overview ## 1. Overview
메인 콘텐츠 탭의 내부 전체 탭에서 오디오, 시리즈, 오리지널, 무료, 포인트 구분별 공개 콘텐츠를 정렬과 페이징으로 조회하는 v2 API를 제공한다. 메인 콘텐츠 탭의 내부 전체 탭에서 유료 오디오, 시리즈, 오리지널, 무료, 포인트 구분별 공개 콘텐츠를 정렬과 페이징으로 조회하는 v2 API를 제공한다.
--- ---
@@ -17,6 +17,8 @@
- 메인 콘텐츠 전체 탭 조회 API를 `kr.co.vividnext.sodalive.v2` 하위 신규 코드로 제공한다. - 메인 콘텐츠 전체 탭 조회 API를 `kr.co.vividnext.sodalive.v2` 하위 신규 코드로 제공한다.
- 기존 패턴과 동일하게 API 조립 계층과 도메인 조회 계층을 분리한다. - 기존 패턴과 동일하게 API 조립 계층과 도메인 조회 계층을 분리한다.
- 구분은 `AUDIO`, `SERIES`, `ORIGINAL`, `FREE`, `POINT`를 지원한다. - 구분은 `AUDIO`, `SERIES`, `ORIGINAL`, `FREE`, `POINT`를 지원한다.
- `AUDIO` 구분은 유료 오디오 콘텐츠만 조회한다.
- `FREE` 구분은 무료 오디오 콘텐츠만 조회하며, `AUDIO` 구분 결과와 가격 조건이 겹치지 않는다.
- 공개된 콘텐츠만 조회한다. - 공개된 콘텐츠만 조회한다.
- 회원이 차단했거나 회원을 차단한 크리에이터의 콘텐츠는 노출하지 않는다. - 회원이 차단했거나 회원을 차단한 크리에이터의 콘텐츠는 노출하지 않는다.
- 비회원은 19금 콘텐츠를 노출하지 않는다. - 비회원은 19금 콘텐츠를 노출하지 않는다.
@@ -48,7 +50,7 @@
--- ---
## 6. User Stories ## 6. User Stories
- 사용자는 오디오 콘텐츠 전체 목록을 최신순, 인기순, 가격순으로 보고 싶다. - 사용자는 유료 오디오 콘텐츠 전체 목록을 최신순, 인기순, 가격순으로 보고 싶다.
- 사용자는 선택한 요일의 시리즈 목록을 보고 싶다. - 사용자는 선택한 요일의 시리즈 목록을 보고 싶다.
- 사용자는 오리지널 시리즈만 따로 보고 싶다. - 사용자는 오리지널 시리즈만 따로 보고 싶다.
- 사용자는 무료 오디오만 따로 보고 싶다. - 사용자는 무료 오디오만 따로 보고 싶다.
@@ -68,7 +70,7 @@
- 회원 조회 시 `@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?` 패턴을 사용한다. - 회원 조회 시 `@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?` 패턴을 사용한다.
- 요청 query parameter는 `type`, `sort`, `dayOfWeek`, `page`, `size`를 사용한다. - 요청 query parameter는 `type`, `sort`, `dayOfWeek`, `page`, `size`를 사용한다.
- `type` 값은 아래 enum으로 정의한다. - `type` 값은 아래 enum으로 정의한다.
- `AUDIO`: 오디오 - `AUDIO`: 유료 오디오
- `SERIES`: 시리즈 - `SERIES`: 시리즈
- `ORIGINAL`: 오리지널 - `ORIGINAL`: 오리지널
- `FREE`: 무료 - `FREE`: 무료
@@ -117,13 +119,15 @@
### Feature C. 오디오 구분 ### Feature C. 오디오 구분
#### Requirements #### Requirements
- `type=AUDIO`는 차단 관계가 아닌 모든 크리에이터의 공개 오디오 콘텐츠를 조회한다. - `type=AUDIO`는 차단 관계가 아닌 모든 크리에이터의 유료 공개 오디오 콘텐츠를 조회한다.
- 전체 개수는 같은 공개/차단/성인 콘텐츠 조건을 적용한 오디오 콘텐츠 개수다. - 유료 오디오는 `price > 0`인 공개 오디오로 정의한다.
- 전체 개수는 같은 공개/차단/성인 콘텐츠 조건과 유료 조건을 적용한 오디오 콘텐츠 개수다.
- 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다. - 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다.
- 응답 item은 기존 추천 탭의 `AudioCardResponse` 필드 의미를 우선 재사용한다. - 응답 item은 기존 추천 탭의 `AudioCardResponse` 필드 의미를 우선 재사용한다.
#### Edge Cases #### Edge Cases
- 시리즈에 속하지 않은 오디오도 목록에 포함한다. - 시리즈에 속하지 않은 유료 오디오도 목록에 포함한다.
- 무료 오디오는 `type=AUDIO` 목록과 개수에서 제외하고 `type=FREE`에서만 조회한다.
- 오디오의 오리지널 여부는 기존 추천 탭과 동일하게 해당 오디오가 속한 시리즈의 `isOriginal` 기준으로 판단한다. - 오디오의 오리지널 여부는 기존 추천 탭과 동일하게 해당 오디오가 속한 시리즈의 `isOriginal` 기준으로 판단한다.
### Feature D. 시리즈 구분 ### Feature D. 시리즈 구분
@@ -161,7 +165,8 @@
#### Requirements #### Requirements
- `type=FREE`는 차단 관계가 아닌 모든 크리에이터의 무료 오디오 콘텐츠를 조회한다. - `type=FREE`는 차단 관계가 아닌 모든 크리에이터의 무료 오디오 콘텐츠를 조회한다.
- 무료 오디오는 `price == 0`인 공개 오디오로 정의한다. - 무료 오디오는 `price == 0`인 공개 오디오로 정의한다.
- 정렬, 페이징, 전체 개수, 성인 콘텐츠 정책은 `AUDIO`와 동일하다. - 공개/차단/성인 콘텐츠 정책과 정렬, 페이징, 전체 개수 산정 방식은 오디오 공통 정책을 따른다.
- `type=FREE``type=AUDIO`의 유료 조건을 상속하지 않는다.
- 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다. - 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다.
#### Edge Cases #### Edge Cases
@@ -172,7 +177,8 @@
#### Requirements #### Requirements
- `type=POINT`는 차단 관계가 아닌 모든 크리에이터의 포인트 사용 가능 오디오 콘텐츠를 조회한다. - `type=POINT`는 차단 관계가 아닌 모든 크리에이터의 포인트 사용 가능 오디오 콘텐츠를 조회한다.
- 포인트 오디오는 `isPointAvailable == true`인 공개 오디오로 정의한다. - 포인트 오디오는 `isPointAvailable == true`인 공개 오디오로 정의한다.
- 정렬, 페이징, 전체 개수, 성인 콘텐츠 정책은 `AUDIO`와 동일하다. - 공개/차단/성인 콘텐츠 정책과 정렬, 페이징, 전체 개수 산정 방식은 오디오 공통 정책을 따른다.
- `type=POINT``type=AUDIO`의 유료 조건을 상속하지 않고 `isPointAvailable == true` 조건만 추가한다.
- 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다. - 응답 목록은 `audios`에 내려주고 `series`는 빈 배열로 내려준다.
#### Edge Cases #### Edge Cases
@@ -215,7 +221,7 @@ Authorization: Bearer {accessToken} (optional)
- 비회원 조회를 허용한다. - 비회원 조회를 허용한다.
- `SecurityConfig``GET /api/v2/audio/contents` permitAll 설정을 추가한다. - `SecurityConfig``GET /api/v2/audio/contents` permitAll 설정을 추가한다.
- `type` 미지정 시 `AUDIO`를 기본값으로 사용한다. - `type` 미지정 시 `AUDIO`를 기본값으로 사용하며, 유료 오디오만 조회한다.
- `sort` 미지정 또는 invalid 값은 `LATEST`로 fallback한다. - `sort` 미지정 또는 invalid 값은 `LATEST`로 fallback한다.
- `type=SERIES`에서 요일 선택이 필요하면 `dayOfWeek`를 함께 보낸다. - `type=SERIES`에서 요일 선택이 필요하면 `dayOfWeek`를 함께 보낸다.
- 예: `GET /api/v2/audio/contents?type=SERIES&dayOfWeek=MON&sort=LATEST&page=0&size=20` - 예: `GET /api/v2/audio/contents?type=SERIES&dayOfWeek=MON&sort=LATEST&page=0&size=20`
@@ -310,6 +316,8 @@ data class MainContentSeriesResponse(
- `LangContext`: 시리즈 제목 다국어 처리 - `LangContext`: 시리즈 제목 다국어 처리
### 구현 주의사항 ### 구현 주의사항
- `type=AUDIO``price > 0` 조건을 적용하고, `type=FREE``price == 0` 조건을 적용해 두 구분의 결과가 겹치지 않게 한다.
- `type=POINT``isPointAvailable == true` 조건을 유지하며, `AUDIO` 전용 유료 필터를 암묵적으로 재사용하지 않는다.
- 기존 추천 탭의 무료/포인트 오디오는 랜덤 조회지만, 전체 탭은 사용자가 선택한 `sort` 기준으로 조회한다. - 기존 추천 탭의 무료/포인트 오디오는 랜덤 조회지만, 전체 탭은 사용자가 선택한 `sort` 기준으로 조회한다.
- 기존 legacy 요일별 시리즈 API는 `dayOfWeek` query parameter로 `SeriesPublishedDaysOfWeek` enum을 받으므로 v2 전체 탭도 같은 parameter 이름과 enum 값을 사용한다. - 기존 legacy 요일별 시리즈 API는 `dayOfWeek` query parameter로 `SeriesPublishedDaysOfWeek` enum을 받으므로 v2 전체 탭도 같은 parameter 이름과 enum 값을 사용한다.
- 기존 v2 채널 오디오/시리즈 탭처럼 invalid parameter fallback을 유지하려면 controller에서는 `dayOfWeek: String?`으로 받고 policy/service 경계에서 `SeriesPublishedDaysOfWeek`로 보정한다. - 기존 v2 채널 오디오/시리즈 탭처럼 invalid parameter fallback을 유지하려면 controller에서는 `dayOfWeek: String?`으로 받고 policy/service 경계에서 `SeriesPublishedDaysOfWeek`로 보정한다.