test #433
@@ -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` 성공.
|
||||||
|
|||||||
@@ -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`로 보정한다.
|
||||||
|
|||||||
Reference in New Issue
Block a user