187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# PRD: 현재 진행 중인 라이브 조회 API
|
|
|
|
## 1. Overview
|
|
메인 홈에서 현재 진행 중인 라이브 목록을 20개씩 페이징 조회하는 v2 API를 제공한다.
|
|
|
|
---
|
|
|
|
## 2. Problem
|
|
- 메인 홈 추천 탭 통합 API는 상단에 현재 진행 중인 라이브를 일부 내려주지만, 별도 목록 조회에 필요한 응답 필드가 부족하다.
|
|
- 기존 `GET /api/v2/home/recommendations/lives`는 `roomId`, `creatorNickname`, `creatorProfileImage`만 내려주며 이번 요구사항의 `title`, `price`, `beginDateTimeUtc`를 포함하지 않는다.
|
|
- 기존 공개 API 스키마를 변경하면 클라이언트 회귀 영향이 생길 수 있으므로, 신규 API 계약을 별도로 명시해야 한다.
|
|
- 기존 v2 홈 추천/팔로잉 탭에는 현재 진행 중인 라이브 조회 조건과 API 조립 계층/도메인 조회 계층 분리 패턴이 있으므로 이를 우선 재활용해야 한다.
|
|
|
|
---
|
|
|
|
## 3. Goals
|
|
- 현재 진행 중인 라이브 목록 조회 API를 `kr.co.vividnext.sodalive.v2` 하위에 제공한다.
|
|
- 한 page당 20개씩 조회한다.
|
|
- 응답 item에는 `roomId`, `creatorNickname`, `creatorProfileImage`, `title`, `price`, `beginDateTimeUtc`를 포함한다.
|
|
- 기존 패턴과 동일하게 클라이언트 공개 API 조립 계층과 도메인 조회 계층을 분리한다.
|
|
- 기존 메인 홈 추천 탭의 라이브 조회 조건을 최대한 재사용한다.
|
|
- 인증 회원만 조회할 수 있게 하고, 회원별 차단/성인 콘텐츠 노출 조건을 반영한다.
|
|
- 기존 공개 API 응답 스키마는 변경하지 않는다.
|
|
|
|
---
|
|
|
|
## 4. Non-Goals
|
|
- 기존 `GET /api/v2/home/recommendations` 응답 스키마를 변경하지 않는다.
|
|
- 기존 `GET /api/v2/home/recommendations/lives` 응답 스키마를 변경하지 않는다.
|
|
- 라이브 생성, 예약, 입장, 종료 API는 포함하지 않는다.
|
|
- 라이브 추천 산식, 스냅샷, 랭킹, 배너 정책은 변경하지 않는다.
|
|
- 앱 표시용 가격 단위, 다국어 문구, 날짜 포맷은 서버에서 처리하지 않는다.
|
|
- 20개 외 page size를 클라이언트가 지정하는 기능은 이번 범위에 포함하지 않는다.
|
|
|
|
---
|
|
|
|
## 5. Target Users
|
|
- 회원: 메인 홈에서 현재 진행 중인 라이브 목록을 더 탐색하는 사용자
|
|
- 앱 클라이언트: 현재 라이브 목록 화면 또는 추천 탭의 추가 로딩 화면을 구성하는 클라이언트
|
|
|
|
---
|
|
|
|
## 6. User Stories
|
|
- 사용자는 메인 홈에서 현재 진행 중인 라이브를 20개씩 추가로 보고 싶다.
|
|
- 사용자는 라이브 제목과 가격을 목록에서 바로 확인하고 싶다.
|
|
- 앱 클라이언트는 다음 page 존재 여부를 응답에서 확인해 무한 스크롤 또는 더보기 UI를 구성하고 싶다.
|
|
- 앱 클라이언트는 기존 추천 탭 상단 라이브와 동일한 노출 정책으로 별도 목록을 조회하고 싶다.
|
|
|
|
---
|
|
|
|
## 7. Core Features
|
|
|
|
### Feature A. 현재 진행 중인 라이브 목록 API
|
|
|
|
#### Requirements
|
|
- 신규 API endpoint는 `GET /api/v2/home/on-air-lives`로 정의한다.
|
|
- 응답 wrapper는 기존 패턴과 동일하게 `ApiResponse.ok(...)`를 사용한다.
|
|
- `page` query parameter를 받는다.
|
|
- `page`를 보내지 않으면 기본값 `0`을 사용한다.
|
|
- `page`는 0부터 시작하는 page index로 처리한다.
|
|
- `size` query parameter는 받지 않고, page size는 항상 20으로 고정한다.
|
|
- 다음 page 존재 여부는 `size + 1`개를 조회하거나 동등한 방식으로 판단한다.
|
|
- 응답 목록에는 최대 20개만 내려준다.
|
|
- 인증 회원만 조회할 수 있다.
|
|
- 인증 회원 조회는 기존 v2 컨트롤러 패턴과 동일하게 `@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?`를 사용한다.
|
|
- `member == null`이면 기존 인증 필요 API와 동일하게 `common.error.bad_credentials` 계열 오류를 반환한다.
|
|
- 현재 진행 중인 라이브는 기존 홈 추천 라이브와 동일하게 `live_room.is_active = true`, `channel_name is not null`, `channel_name <> ''` 조건을 기본으로 한다.
|
|
- 방송자는 `member.is_active = true`인 대상만 노출한다.
|
|
- 정렬은 기존 홈 추천 라이브와 동일하게 `live_room.begin_date_time desc`, `live_room.id desc`로 한다.
|
|
- 양방향 차단 관계가 있는 크리에이터의 라이브는 제외한다.
|
|
- 성인 라이브 노출 여부는 `MemberContentPreferenceService.canViewAdultContent(member)` 결과를 따른다.
|
|
- 프로필 이미지는 기존 홈 추천/팔로잉 탭과 동일하게 CDN URL로 변환하고, 값이 없으면 기본 프로필 이미지 URL을 내려준다.
|
|
|
|
#### Edge Cases
|
|
- 조회 결과가 없으면 `items = emptyList()`, `hasNext = false`를 내려준다.
|
|
- 비회원이 조회하면 목록을 내려주지 않고 인증 오류를 반환한다.
|
|
- `page`가 0보다 작으면 기존 홈 추천 컨트롤러의 `normalizePage` 패턴과 동일하게 0으로 보정한다.
|
|
- 매우 큰 `page` 값은 기존 홈 추천 컨트롤러의 `MAX_PAGE = 10_000` 패턴과 동일하게 상한 보정한다.
|
|
- 20개보다 적게 조회되면 가능한 개수만 내려주고 성공 처리한다.
|
|
- 라이브 제목이 빈 문자열이면 별도 fallback을 만들지 않고 저장된 `LiveRoom.title` 값을 그대로 내려준다.
|
|
- 라이브 가격은 `LiveRoom.price` 값을 그대로 내려준다.
|
|
- 라이브 시작 시간은 `LiveRoom.beginDateTime`을 기존 UTC ISO 문자열 변환 패턴으로 변환해 `beginDateTimeUtc`로 내려준다.
|
|
|
|
### Feature B. 계층 분리와 재사용 정책
|
|
|
|
#### Requirements
|
|
- 클라이언트 공개 API 조립 계층은 `kr.co.vividnext.sodalive.v2.api.home.live` 하위에 둔다.
|
|
- API 조립 계층 후보 파일은 다음과 같다.
|
|
- `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveController.kt`
|
|
- `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacade.kt`
|
|
- `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponse.kt`
|
|
- 도메인 조회 계층은 기존 `kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryService`와 `HomeRecommendationQueryPort.findLiveRecommendations(...)` 확장 재사용을 기본안으로 한다.
|
|
- 기존 `HomeLiveRecommendationRecord`에 `title`, `price`, `beginDateTime`을 추가해 신규 API DTO로 조립할 수 있게 한다.
|
|
- 기존 `HomeLiveItem`은 기존 필드만 매핑해 기존 추천 탭 공개 응답 스키마를 유지한다.
|
|
- 기존 `DefaultHomeRecommendationQueryRepository.findLiveRecommendations(...)`의 조회 조건과 정렬을 유지하되 `liveRoom.title`, `liveRoom.price`, `liveRoom.beginDateTime` select를 추가한다.
|
|
- API 조립 계층은 도메인 조회 결과를 공개 응답 DTO로 변환하고, CDN URL 변환/기본 프로필 이미지 정책은 기존 홈 추천 패턴을 따른다.
|
|
|
|
#### Edge Cases
|
|
- `HomeRecommendationQueryService` 확장으로 추천 도메인 결합이 과도하다고 판단되면 구현 계획 단계에서 `kr.co.vividnext.sodalive.v2.home.live` 하위 전용 query service/port/repository를 만들 수 있다. 이 경우에도 기존 조회 조건, 정렬, 테스트 케이스는 동일하게 유지한다.
|
|
- 기존 record 확장 시 생성자 projection 순서와 모든 매핑 호출부를 함께 수정해야 한다.
|
|
|
|
### Feature C. Response 스키마
|
|
|
|
#### Requirements
|
|
- 응답 최상위 DTO 이름은 `HomeOnAirLivePageResponse`를 기본안으로 한다.
|
|
- 응답 item DTO 이름은 `HomeOnAirLiveResponse`를 기본안으로 한다.
|
|
- 응답 item은 다음 값을 포함한다.
|
|
- `roomId`: 라이브 방 id
|
|
- `creatorNickname`: 방송자 닉네임
|
|
- `creatorProfileImage`: 방송자 프로필 이미지 CDN URL
|
|
- `title`: 라이브 제목
|
|
- `price`: 라이브 입장 가격
|
|
- `beginDateTimeUtc`: 라이브 시작 시간 UTC ISO 문자열
|
|
- page metadata는 기존 `HomeRecommendationPageResponse`와 동일한 의미로 `page`, `size`, `hasNext`를 포함한다.
|
|
- `size`는 항상 `20`으로 내려준다.
|
|
|
|
#### Edge Cases
|
|
- `creatorProfileImage` 원본 값이 없으면 기본 프로필 이미지 CDN URL을 내려준다.
|
|
- `price`가 무료이면 `0`을 내려준다.
|
|
- `beginDateTimeUtc`는 `LiveRoom.beginDateTime`을 UTC ISO 문자열로 변환한 값으로 내려준다.
|
|
|
|
---
|
|
|
|
## 8. API Endpoint
|
|
|
|
```http
|
|
GET /api/v2/home/on-air-lives?page=0
|
|
Authorization: Bearer {accessToken}
|
|
```
|
|
|
|
- `page`: 선택값, 기본값 `0`, 0부터 시작하는 page index
|
|
- `size`: 받지 않음, 서버에서 20으로 고정
|
|
- `SecurityConfig`에 `GET /api/v2/home/on-air-lives` authenticated 설정을 추가한다.
|
|
- 회원 token이 없거나 anonymous이면 기존 인증 필요 API와 동일하게 인증 오류를 반환한다.
|
|
- 인증 회원 기준으로 차단/성인 콘텐츠 노출 조건을 반영한다.
|
|
|
|
---
|
|
|
|
## 9. Response Data Class
|
|
|
|
```kotlin
|
|
data class HomeOnAirLivePageResponse(
|
|
val items: List<HomeOnAirLiveResponse>,
|
|
val page: Int,
|
|
val size: Int,
|
|
val hasNext: Boolean
|
|
)
|
|
|
|
data class HomeOnAirLiveResponse(
|
|
val roomId: Long,
|
|
val creatorNickname: String,
|
|
val creatorProfileImage: String,
|
|
val title: String,
|
|
val price: Int,
|
|
val beginDateTimeUtc: String
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Technical Constraints
|
|
- Kotlin, Spring Boot 2.7.14, Java 17, Gradle Wrapper 구조를 유지한다.
|
|
- 신규 코드는 기존 v2 패키지 구조와 네이밍을 따른다.
|
|
- 공개 API 조립 계층은 `kr.co.vividnext.sodalive.v2.api.*` 하위에 두고, 재사용 가능한 조회 책임은 API 패키지 밖 도메인 조회 계층에 둔다.
|
|
- 기존 `ApiResponse.ok(...)` 응답 wrapper를 사용한다.
|
|
- QueryDSL 기반 조회 패턴을 유지한다.
|
|
- 공개 API 스키마 변경은 신규 endpoint에만 한정한다.
|
|
- 구현 계획 단계에서는 TDD 기준으로 controller/facade/query repository 테스트를 작성한 뒤 최소 구현한다.
|
|
|
|
---
|
|
|
|
## 11. Reuse Candidates
|
|
- `HomeRecommendationController`: page 정규화, `ApiResponse.ok(...)`, `requireMember(...)` 인증 필수 패턴 참고
|
|
- `HomeRecommendationFacade`: `size + 1` 조회 후 `hasNext`를 판단하는 page 응답 조립 패턴 참고
|
|
- `HomeRecommendationPageResponse`: page metadata 의미 참고
|
|
- `HomeRecommendationQueryService.findLiveRecommendations(...)`: 현재 진행 중인 라이브 도메인 조회 진입점으로 확장 재사용
|
|
- `HomeRecommendationQueryPort.HomeLiveRecommendationRecord`: `title`, `price`, `beginDateTime`을 추가해 신규 API 응답 조립에 재사용
|
|
- `DefaultHomeRecommendationQueryRepository.findLiveRecommendations(...)`: 진행 중 라이브 조건, 정렬, 차단 필터, 성인 라이브 필터 재사용
|
|
- `HomeFollowingLive`/`DefaultHomeFollowingQueryRepository.findOnAirLives(...)`: `title` 포함 라이브 응답 모델링과 CDN URL 변환 패턴 참고
|
|
- `LiveRoom`: `title`, `price`, `beginDateTime`, `channelName`, `isAdult`, `isActive` 필드 사용
|
|
- `MemberContentPreferenceService.canViewAdultContent(member)`: 성인 라이브 노출 가능 여부 판단
|
|
|
|
---
|
|
|
|
## 12. Open Questions
|
|
- 없음. 현재 PRD는 인증 회원만 조회 가능, page size 20 고정, 기존 추천 라이브 조건 재사용을 기본 가정으로 작성한다.
|