diff --git a/docs/20260805_추천탭_배너_조회조건/plan-task.md b/docs/20260805_추천탭_배너_조회조건/plan-task.md new file mode 100644 index 00000000..4678d43d --- /dev/null +++ b/docs/20260805_추천탭_배너_조회조건/plan-task.md @@ -0,0 +1,90 @@ +# 추천 탭 배너 조회 조건 보정 Implementation Plan + +| 문서 항목 | 내용 | +|---|---| +| 상태 | 구현 완료 | +| 작성일 | 2026-08-05 | +| 요구사항 기준 | `docs/20260805_추천탭_배너_조회조건/prd.md` | +| API 기준 | 기존 공개 API 계약 유지 | +| 현재 Phase | Phase 1: 배너 조회 조건 보정 | +| 현재 활성 Goal | 없음 | + +## 목표 + +메인 홈 추천과 메인 콘텐츠 추천에서 화면별 탭과 회원의 성인 콘텐츠 조회 가능 여부에 맞는 배너만 조회한다. + +## 현재 상태 + +| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | +|---:|---|---:|---|---| +| 1 | 완료 | `2/2` | 없음 | 없음 | + +## 범위 + +### 포함 + +- 홈 추천 배너의 `tab_id IS NULL` 조건 유지 및 성인 배너 필터 추가 +- 콘텐츠 추천 배너의 `tab_id = 2` 조건과 성인 배너 필터 추가 +- application에서 계산한 성인 콘텐츠 조회 가능 여부를 persistence port까지 전달 +- 관련 service/facade/Repository focused test와 직접 영향 범위 회귀 + +### 제외 + +- 공개 API endpoint와 응답 DTO 변경 +- 배너 언어/활성/차단/대상 유효성/정렬/limit 정책 변경 +- DB 데이터 또는 스키마 변경 + +## 기술적 제약 + +- Kotlin, Spring Boot 2.7.14, QueryDSL 기존 패턴을 유지한다. +- 성인 콘텐츠 조회 가능 여부는 `MemberContentPreferenceService.canViewAdultContent(member)` 결과를 재사용한다. +- 조회 가능 여부가 `false`이면 `audioContentBanner.isAdult.isFalse`, `true`이면 성인 조건을 추가하지 않는다. +- 신규 공통 추상화나 의존성을 추가하지 않는다. + +### Phase 1: 배너 조회 조건 보정 + +- [x] **Task 1.1: 추천 API별 탭 및 성인 배너 조회 조건 구현 (`P1-T1`)** + - Objective: 두 추천 API의 배너가 확정된 탭과 성인 콘텐츠 조회 정책에 따라 반환된다. + - 시작 조건: PRD `BANNER-001~004`가 확정되어 있다. + - 완료 증거: 신규/보강 테스트가 RED 후 GREEN이고 관련 production/test 코드가 컴파일된다. + - 범위 밖: 언어 필터와 공개 응답 스키마 변경. + - Modify: + - `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt` + - `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt` + - `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt` + - `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt` + - `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepository.kt` + - `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacadeTest.kt` + - `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt` + - `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt` + - `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/recommendation/adapter/out/persistence/DefaultAudioRecommendationQueryRepositoryTest.kt` + - RED: 홈 성인 조회 플래그 전달, 홈 비성인 필터, 콘텐츠 `tab_id = 2` 및 비성인 필터를 검증하는 테스트를 먼저 작성하고 실패를 확인한다. + - GREEN: 기존 QueryDSL 조건에 필요한 탭/성인 조건만 추가하고 홈 application 경로에 플래그를 전달한다. + - REFACTOR: 중복되지 않는 기존 조건 helper 패턴을 따르고 불필요한 변경이 없는지 확인한다. + - Verify: + - `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacadeTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.content.recommendation.adapter.out.persistence.DefaultAudioRecommendationQueryRepositoryTest` + - 검증 기록: + - RED: 두 Repository 테스트를 실행해 `shouldExcludeAdultHomeBannersWhenAdultContentIsNotVisible`, `shouldFindBannersForContentRecommendationTabWithAdultVisibility` 두 건의 기대값 실패를 확인했다. + - RED: 홈 service 테스트는 `includeAdultBanners` 미구현 컴파일 실패, Facade 테스트는 플래그 미전달 assertion 실패를 각각 확인했다. + - GREEN: 위 4개 focused test class를 함께 실행해 `BUILD SUCCESSFUL`을 확인했다. + +- [x] **Task 1.2: 영향 범위 회귀 및 문서 검증 (`P1-GATE`)** + - Objective: 추천 배너 변경이 기존 API 계약과 코드 품질 규칙을 깨지 않았음을 확인한다. + - 시작 조건: `P1-T1`이 완료되어 있다. + - 완료 증거: focused test, `ktlintCheck`, `tasks --all`, `git diff --check`가 통과하고 결과가 기록되어 있다. + - 범위 밖: 전체 회귀 테스트. 변경이 배너 조회 경로 두 곳에 한정되어 targeted test로 직접 영향 범위를 판단할 수 있으므로 생략한다. + - Verify: + - `./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.application.HomeRecommendationFacadeTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.content.recommendation.adapter.out.persistence.DefaultAudioRecommendationQueryRepositoryTest` + - `./gradlew ktlintCheck` + - `./gradlew tasks --all` + - `git diff --check` + - 검증 기록: + - `HomeRecommendationControllerTest`, `AudioRecommendationControllerTest`, `AudioRecommendationEndToEndTest`를 함께 실행해 `BUILD SUCCESSFUL`을 확인했다. + - `./gradlew ktlintCheck`: `BUILD SUCCESSFUL`. + - `./gradlew tasks --all`: `BUILD SUCCESSFUL`. + - `git diff --check`: 출력 없음. + +## 검증 기록 + +- 최초 Gradle 실행은 sandbox의 `~/.gradle` wrapper lock 접근 제한으로 실패했으며, 승인된 동일 명령을 재실행해 검증을 완료했다. +- 전체 회귀 테스트는 실행하지 않았다. 변경이 두 배너 조회 조건과 홈 플래그 전달에 한정되어 focused Repository/application 테스트와 두 API의 controller/E2E 회귀로 직접 영향 범위를 검증했다. diff --git a/docs/20260805_추천탭_배너_조회조건/prd.md b/docs/20260805_추천탭_배너_조회조건/prd.md new file mode 100644 index 00000000..9b73a586 --- /dev/null +++ b/docs/20260805_추천탭_배너_조회조건/prd.md @@ -0,0 +1,71 @@ +# PRD: 추천 탭 배너 조회 조건 보정 + +## 문서 정보 + +| 항목 | 내용 | +|---|---| +| 문서 상태 | 구현 완료 | +| 작성일 | 2026-08-05 | +| 최종 수정일 | 2026-08-05 | +| 대상 제품 | 메인 홈 추천 탭, 메인 콘텐츠 추천 탭 | +| 작성자·결정권자 | 사용자 | +| 관련 구현 계획 | `docs/20260805_추천탭_배너_조회조건/plan-task.md` | +| 관련 기존 문서 | `docs/20260529_메인_홈_추천_API/prd.md`, `docs/20260623_메인_콘텐츠_추천_탭_API/prd.md` | + +## 1. Overview + +메인 홈 추천 API와 메인 콘텐츠 추천 API의 배너 조회에 회원의 성인 콘텐츠 조회 가능 여부를 반영하고, 두 화면이 서로 다른 탭의 배너를 조회하도록 조건을 보정한다. + +## 2. Problem Statement + +- 두 추천 API가 모두 `content_banner.tab_id IS NULL`인 같은 배너를 조회한다. +- 두 추천 API의 배너 조회가 `content_banner.is_adult`를 필터링하지 않아 성인 콘텐츠 조회 불가 사용자에게 성인 배너가 노출될 수 있다. + +문제를 해결했다는 판단은 두 API의 탭 조건과 성인 배너 노출 조건이 Repository 테스트로 구분되어 검증되는 것으로 한다. + +## 3. Goals + +- 성인 콘텐츠 조회 불가 사용자는 `is_adult = false`인 배너만 조회한다. +- 성인 콘텐츠 조회 가능 사용자는 성인·비성인 배너를 모두 조회한다. +- 메인 홈 추천 API는 기존처럼 `tab_id IS NULL`인 배너를 조회한다. +- 메인 콘텐츠 추천 API는 `tab_id = 2`인 배너를 조회한다. + +## 4. Non-Goals + +- 공개 API endpoint와 응답 DTO를 변경하지 않는다. +- 배너 언어 필터, 정렬, 최대 조회 개수, 대상 활성/차단 정책을 변경하지 않는다. +- 배너 또는 탭 데이터와 DB 스키마를 변경하지 않는다. + +## 5. 기능 요구사항 + +| ID | 상태 | 요구사항 | 수용 기준 | Goal 연결 | +|---|---|---|---|---| +| `BANNER-001` | 확정 | `GET /api/v2/home/recommendations`의 배너는 `tab_id IS NULL` 조건을 유지한다. | 탭이 없는 활성 배너만 조회하고 `tab_id = 2` 배너는 조회하지 않는다. | `P1-T1` | +| `BANNER-002` | 확정 | `GET /api/v2/audio/recommendations`의 배너는 `tab_id = 2` 조건을 사용한다. | `tab_id = 2`인 활성 배너만 조회하고 탭이 없는 배너는 조회하지 않는다. | `P1-T1` | +| `BANNER-003` | 확정 | 두 API 모두 회원의 성인 콘텐츠 조회 가능 여부를 배너 조회에 반영한다. | 조회 불가이면 비성인 배너만, 조회 가능이면 성인·비성인 배너를 모두 반환한다. | `P1-T1` | +| `BANNER-004` | 확정 | 기존 배너 활성/차단/대상 유효성/정렬/limit 정책을 유지한다. | 기존 관련 Repository 테스트가 계속 통과한다. | `P1-GATE` | + +## 6. API 계약 + +| Method | Path | 변경 내용 | +|---|---|---| +| `GET` | `/api/v2/home/recommendations` | 응답 스키마 변경 없이 배너의 성인 조회 조건만 추가한다. | +| `GET` | `/api/v2/audio/recommendations` | 응답 스키마 변경 없이 배너 탭 조건을 `tab_id = 2`로 변경하고 성인 조회 조건을 추가한다. | + +## 7. 성공 기준 + +- [x] 홈 추천 배너가 `tab_id IS NULL` 조건을 유지한다. +- [x] 콘텐츠 추천 배너가 `tab_id = 2` 조건만 사용한다. +- [x] 성인 콘텐츠 조회 불가/가능 사용자의 배너 결과가 확정 정책과 일치한다. +- [x] 기존 배너 응답 스키마와 활성/차단/정렬/limit 정책이 유지된다. + +## 8. Open Questions + +- 없음. + +## 9. Decision Log + +| 날짜 | ID | 상태 | 결정 | 근거 | 영향 요구사항·Goal | +|---|---|---|---|---|---| +| 2026-08-05 | `DEC-001` | 확정 | 성인 조회 불가이면 비성인 배너만, 조회 가능이면 성인·비성인 배너를 모두 조회한다. | 사용자 답변 A | `BANNER-003`, `P1-T1` | +| 2026-08-05 | `DEC-002` | 확정 | 홈 추천은 `tab_id IS NULL`, 콘텐츠 추천은 `tab_id = 2`를 사용한다. | 사용자 직접 요구사항 | `BANNER-001`, `BANNER-002`, `P1-T1` |