docs(content-ranking): 커버 이미지 CDN 정책을 기록한다

This commit is contained in:
2026-06-25 16:03:10 +09:00
parent 4f3f8d1fa7
commit e411beb649
2 changed files with 36 additions and 0 deletions

View File

@@ -511,3 +511,27 @@ data class AudioRankingSnapshotRecord(
- 2026-06-24 Phase 6 트랜잭션 가시성 검증: `getRankings()``@Transactional`이 다시 붙지 않도록 회귀 테스트를 추가했다. RED 확인 후 수정했고, `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryServiceTest`, `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.content.ranking.*'`, `./gradlew ktlintCheck` 통과.
- 2026-06-24 Phase 8 문서 확인: `rg -n "07:30|visibleFromAt|visible_from_at|ranking_type|크리에이터 랭킹" docs/20260608_크리에이터_랭킹 docs/20260623_메인_콘텐츠_랭킹_탭_API/plan-task.md`로 크리에이터 랭킹 현재 07:30 스케줄과 다음 범위의 `visible_from_at`, `ranking_type` DDL 검토 시작점을 확인했다.
- 2026-06-24 Phase 8 범위 확정: 크리에이터 랭킹 코드/DDL은 수정하지 않고, 다음 범위가 별도 PRD 문서 수정부터 시작되도록 Task 8.1 완료 상태와 검증 기록만 갱신했다.
### Phase 9: `coverImageUrl` CDN host 누락 버그 수정
- [x] **Task 9.1: `AudioRankingQueryService` 응답 변환 지점에서 CDN URL 정책 고정**
- Files:
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/application/AudioRankingQueryService.kt`
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/application/AudioRankingQueryServiceTest.kt`
- Modify: `docs/20260623_메인_콘텐츠_랭킹_탭_API/prd.md`
- Modify: `docs/20260623_메인_콘텐츠_랭킹_탭_API/plan-task.md`
- 버그 내용: 메인 콘텐츠 랭킹 탭 API는 스냅샷의 `coverImageUrl` 값을 `AudioRankingQueryService.toItem(...)`에서 그대로 `AudioRankingItem.coverImageUrl`로 옮기고 있었다. 스냅샷 생성 과정의 원천 값은 `audio_content.cover_image` 계열의 저장 path이므로, 공개 API 응답도 `cover-1.png`처럼 host 없는 path만 내려갔다.
- 영향 범위: `GET /api/v2/audio/rankings`의 item `coverImageUrl`만 대상이다. 순위 계산, 최신/직전 visible snapshot 조회, 19금 필터, 차단 필터, fallback job 실행, 스냅샷 저장 구조와 DDL은 변경하지 않는다.
- 원인: 콘텐츠 랭킹 조회 서비스가 크리에이터 랭킹의 `profileImageUrl.toCdnUrl(cloudFrontHost)` 패턴이나 v2 콘텐츠/크리에이터 조회 계층의 `toCdnUrl` 패턴을 적용하지 않았다. DTO 변환 계층은 domain item 값을 그대로 응답으로 내보내므로, domain item 조립 시점에 URL 변환이 누락되면 Response에서도 그대로 노출된다.
- RED: `AudioRankingQueryServiceTest.shouldReturnLatestVisibleSnapshotsWithRankChangesAndNewFlags`에 스냅샷 fixture의 `coverImageUrl = "cover-N.png"`가 응답 item에서는 `https://cdn.test/cover-N.png`로 변환되어야 한다는 assertion을 추가한다. 기존 구현에서는 path만 반환하므로 이 assertion이 실패해야 한다.
- GREEN: `AudioRankingQueryService` 생성자에 `@Value("\${cloud.aws.cloud-front.host}") private val cloudFrontHost: String`을 주입하고, `AudioRankingSnapshotRecord.toItem(...)`에서 `coverImageUrl.toCdnUrl(cloudFrontHost)`를 사용한다. 이 방식은 `null`/blank는 `null`, 이미 `http://` 또는 `https://`로 시작하는 값은 그대로 유지하는 기존 공통 확장 함수를 재사용한다.
- REFACTOR: 별도 URL helper를 새로 만들지 않는다. 스냅샷 저장 데이터를 full URL로 마이그레이션하지 않고, 공개 응답 조립 지점에서만 변환해 기존 데이터와 신규 데이터 모두 동일하게 처리한다.
- 기대 결과: `GET /api/v2/audio/rankings` 응답의 `items[*].coverImageUrl`은 path가 아니라 `cloud.aws.cloud-front.host`가 포함된 이미지 URL로 내려간다.
## Phase 9 검증 기록
- 2026-06-25 문서 갱신: 사용자 후속 요청에 따라 `prd.md``coverImageUrl` host 누락 버그, 공개 응답 URL 정책, `toCdnUrl` 기반 변환 규칙을 추가했다. `plan-task.md`에는 버그 내용, 영향 범위, 원인, RED/GREEN/REFACTOR 기준을 Phase 9로 누적 기록했다.
- 2026-06-25 focused 검증: `./gradlew test --tests kr.co.vividnext.sodalive.v2.content.ranking.application.AudioRankingQueryServiceTest` 실행 결과 `BUILD SUCCESSFUL in 30s`를 확인했다. 이 테스트는 스냅샷 fixture의 path 값(`cover-N.png`)이 응답 item에서는 `https://cdn.test/cover-N.png`로 변환되는지 검증한다.
- 2026-06-25 문서 명령 검증: `./gradlew tasks --all` 실행 결과 `BUILD SUCCESSFUL in 8s`를 확인했고, `rg -n "coverImageUrl|Phase 9|cdn|cloud-front|toCdnUrl|host 없는 path|CDN host" docs/20260623_메인_콘텐츠_랭킹_탭_API/prd.md docs/20260623_메인_콘텐츠_랭킹_탭_API/plan-task.md`로 PRD와 plan-task에 버그 내용 및 수정 정책이 반영된 위치를 확인했다.
- 2026-06-25 포맷 검증: `./gradlew ktlintCheck` 실행 결과 `BUILD SUCCESSFUL in 32s`를 확인했다.
- 2026-06-25 회귀 검증: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.content.ranking.*'` 실행 결과 `BUILD SUCCESSFUL in 1m 4s`를 확인했다. `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.content.ranking.*'`를 API 테스트와 병렬 실행했을 때는 `build/test-results/test/TEST-*.xml` 파일 쓰기 충돌로 실패했으나, 동일 명령을 단독 재실행해 `BUILD SUCCESSFUL in 19s`를 확인했다.

View File

@@ -13,6 +13,7 @@
- `매출`, `판매량`, `댓글 수`, `좋아요`는 기존 랭킹과 동일한 원천 지표를 사용하되, 순위 변화와 신규 진입 여부를 안정적으로 계산하려면 v2 스냅샷 생성 시점에 완료 주차 기준으로 직접 집계해야 한다.
- 조회 시마다 모든 랭킹 타입의 원천 데이터를 집계하면 응답 지연과 계산 중복이 커지고, 운영 서버와 테스트 환경에서 같은 기준의 결과를 재현하기 어렵다.
- 기존 v2 패키지에 크리에이터 랭킹 스냅샷/작업 이력/fallback 패턴과 콘텐츠 추천 탭의 API 조립 계층/도메인 조회 계층 분리 패턴이 있으므로 이를 우선 재사용해야 한다.
- 2026-06-25 후속 확인 결과, 메인 콘텐츠 랭킹 탭 API의 `coverImageUrl` 응답이 `cloud.aws.cloud-front.host`가 포함된 완성 URL이 아니라 `cover-*.png` 같은 저장 path만 내려가는 버그가 확인되었다. 앱 클라이언트는 공개 API의 이미지 필드를 직접 렌더링 가능한 URL로 기대하므로, 다른 v2 콘텐츠/크리에이터 조회 API와 동일하게 CDN host를 포함해 반환해야 한다.
---
@@ -30,6 +31,7 @@
- fallback 실행은 스케줄 실행 기록처럼 저장하며, 동일 랭킹 타입과 동일 집계 기간 기준 최대 3회까지만 시도한다.
- PRD에 API endpoint와 Response data class 초안을 포함한다.
- 신규 Entity가 생성되는 경우 같은 작업 디렉터리에 대응 DB table 생성/수정 DDL을 기록한다.
- `coverImageUrl`은 스냅샷 또는 DB에 저장된 path를 그대로 공개하지 않고, 공개 Response를 만들기 전에 `cloud.aws.cloud-front.host`를 포함한 URL로 변환한다.
---
@@ -91,6 +93,7 @@
- 후보가 20개 미만이면 가능한 개수만 내려준다.
- 특정 랭킹 타입의 새 스냅샷 생성이 실패하면 해당 타입은 직전 공개 스냅샷을 유지한다.
- 콘텐츠 제목, 크리에이터 닉네임, 커버 이미지가 기존 정책상 마스킹되어야 하는 경우 기존 콘텐츠 랭킹/추천 조회 정책을 따른다.
- `coverImageUrl`은 스냅샷 저장값이 path 형태여도 공개 응답에서는 `https://...` 또는 `http://...`로 시작하는 완성 URL이어야 한다. 이미 완성 URL인 값은 중복 prefix를 붙이지 않는다.
### Feature B. rank, rankChange, isNew 의미
@@ -299,6 +302,15 @@ data class AudioRankingItemResponse(
)
```
`coverImageUrl` 응답 정책은 다음과 같다.
- 스냅샷 테이블의 표시용 커버 이미지 값은 원천 `audio_content.cover_image`와 같은 path 형태로 저장될 수 있다.
- 공개 API 응답의 `coverImageUrl`은 클라이언트가 바로 이미지 로딩에 사용할 수 있도록 `cloud.aws.cloud-front.host`를 prefix로 포함한다.
- 변환은 `v2/common/domain/CdnUrlExtensions.kt``toCdnUrl` 정책을 따른다.
- `null`, 빈 문자열, blank 값은 `null`로 유지한다.
- 이미 `https://` 또는 `http://`로 시작하는 값은 외부/완성 URL로 보고 그대로 유지한다.
- 이 정책은 스냅샷 생성, 정렬, `rankChange`, `isNew`, fallback 여부와 무관한 Response 조립 정책이며, 기존 스냅샷 데이터의 재생성이나 DDL 변경을 요구하지 않는다.
응답 예시는 다음과 같다.
```json