Test #451
@@ -0,0 +1,357 @@
|
|||||||
|
# 라이브 크리에이터 입장 제한 구현 계획
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use `subagent-driven-development` or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
| 문서 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 상태 | Phase 1~3 구현 및 검증 완료 |
|
||||||
|
| 작성일 | `2026-09-17` |
|
||||||
|
| 요구사항 기준 | `docs/20260917_라이브_크리에이터_입장제한/prd.md` |
|
||||||
|
| API 기준 | 기존 API 응답 스키마 변경 없음 |
|
||||||
|
| 현재 Phase | 완료 |
|
||||||
|
| 현재 활성 Goal | 없음 |
|
||||||
|
| 다음 Goal | 없음 |
|
||||||
|
|
||||||
|
## 목표
|
||||||
|
|
||||||
|
다른 크리에이터가 `isAvailableJoinCreator = false` 라이브 방을 볼 수도, 직접 입장할 수도 없게 하고, 입장 가능 성별(`genderRestriction`)도 조회와 입장 경계에서 일관되게 적용한다.
|
||||||
|
|
||||||
|
## 현재 상태
|
||||||
|
|
||||||
|
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||||
|
|---:|---|---:|---|---|
|
||||||
|
| 1 | 완료 | `2/2` | 없음 | 없음 |
|
||||||
|
| 2 | 완료 | `1/1` | 없음 | 없음 |
|
||||||
|
| 3 | 완료 | `2/2` | 없음 | 없음 |
|
||||||
|
|
||||||
|
## 범위
|
||||||
|
|
||||||
|
### 포함
|
||||||
|
|
||||||
|
- `v2` 홈 추천/온에어 라이브 조회의 `isAvailableJoinCreator` 및 `genderRestriction` 필터 누락 보정.
|
||||||
|
- `v2` 크리에이터 채널 라이브 탭의 조회자 크리에이터 판정 보정.
|
||||||
|
- `/live/room/enter`에서 다른 크리에이터의 제한 방 직접 입장 차단과 기존 성별 입장 차단 회귀 검증.
|
||||||
|
- focused test와 직접 영향 회귀 검증.
|
||||||
|
- 후속 리뷰 `REV-001`: `/live/room/info/{id}`의 토큰 발급 전 크리에이터·성별 제한 적용.
|
||||||
|
- 후속 리뷰 `REV-002`: 채널 소유자 본인의 제한 방 조회를 repository 회귀 테스트로 고정.
|
||||||
|
|
||||||
|
### 제외
|
||||||
|
|
||||||
|
- 공개 API DTO 필드 추가/삭제.
|
||||||
|
- DB schema 변경.
|
||||||
|
- 기존 성인/성별/차단/강퇴/비공개/결제 정책 재설계.
|
||||||
|
- 관리자용 라이브 조회 정책 변경.
|
||||||
|
|
||||||
|
## 기술적 제약
|
||||||
|
|
||||||
|
- 기술 스택: Kotlin, Spring Boot 2.7.14, QueryDSL, Gradle Wrapper.
|
||||||
|
- 기존 QueryDSL 조건 패턴을 재사용하고 새 abstraction은 만들지 않는다.
|
||||||
|
- 크리에이터 제한 조건은 `isViewerCreator && memberId != null`일 때만 적용하며, 방 생성자는 `liveRoom.member.id.eq(memberId)`로 예외 처리한다.
|
||||||
|
- 성별 제한 조건은 기존 조회 경로와 같은 정책을 따른다. `Gender.MALE`은 `ALL`, `MALE_ONLY`, `Gender.FEMALE`은 `ALL`, `FEMALE_ONLY`, `Gender.NONE` 또는 비로그인/null 유효 성별은 필터 없음이다.
|
||||||
|
- 방 생성자 본인은 성별 제한 조건에서도 `liveRoom.member.id.eq(memberId)`로 예외 처리한다.
|
||||||
|
- 모든 production 변경 Task는 `RED → GREEN → REFACTOR` 순서로 진행한다.
|
||||||
|
- 공개 API schema 변경은 금지한다.
|
||||||
|
|
||||||
|
## Phase 1: 리스트 노출 제한 보정
|
||||||
|
|
||||||
|
**Phase 결과:** 다른 크리에이터가 문제 의심 리스트 경로에서 제한 방을 받지 않고, 성별 제한에 맞지 않는 사용자가 v2 홈 추천/온에어 라이브에서 제한 방을 받지 않는다.
|
||||||
|
|
||||||
|
**선행조건:** PRD 확정.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P1-T1`, `P1-T2`, `P1-GATE` 완료 및 검증 기록 누적.
|
||||||
|
|
||||||
|
### 구현 항목
|
||||||
|
|
||||||
|
#### Task 1.1 v2 홈 추천/온에어 라이브 필터 적용
|
||||||
|
|
||||||
|
**Goal 실행 `P1-T1`:** `findLiveRecommendations()` 계열 호출에서 조회자 크리에이터 여부와 유효 성별을 전달하고 QueryDSL 필터를 적용한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `LCR-001` 확정.
|
||||||
|
- **완료 증거:** 다른 크리에이터가 제한 방을 받지 않고, 성별 제한에 맞지 않는 사용자가 제한 방을 받지 않으며, 방 생성자 본인은 예외인 focused test 통과.
|
||||||
|
- **범위 밖:** `/live/room/enter` 입장 차단.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt`
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt`
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt`
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt`
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacade.kt`
|
||||||
|
- Test: repository/service 기존 테스트 위치를 우선 확인하고, 없으면 `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- `findLiveRecommendations(offset, limit, memberId, includeAdultLives, isViewerCreator, effectiveViewerGender)` 형태로 파라미터를 추가한다.
|
||||||
|
- `isViewerCreator` 기본값은 `false`로 둬 snapshot refresh 등 기존 호출을 보존한다.
|
||||||
|
- `effectiveViewerGender` 기본값은 `null`로 둬 비로그인/성별 미설정과 snapshot refresh 등 기존 호출을 보존한다.
|
||||||
|
- `HomeRecommendationFacade`와 `HomeOnAirLiveFacade`는 `member.auth?.gender` 우선, 없으면 `member.gender`로 기존 경로와 같은 유효 성별을 계산해 전달한다.
|
||||||
|
|
||||||
|
- [x] **RED:** 크리에이터 조회자에게 `isAvailableJoinCreator = false` 라이브가 제외되는 테스트, 성별 제한에 맞지 않는 조회자에게 `genderRestriction` 제한 라이브가 제외되는 테스트, 방 생성자 본인은 포함되는 테스트를 작성한다.
|
||||||
|
- [x] **RED 확인:** `./gradlew test --tests '*DefaultHomeRecommendationQueryRepositoryTest'`를 실행해 필터 미구현으로 크리에이터 제한 방 또는 성별 제한 방이 포함되는 assertion 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** `HomeRecommendationQueryPort`, `HomeRecommendationQueryService`, `DefaultHomeRecommendationQueryRepository`, `HomeRecommendationFacade`, `HomeOnAirLiveFacade`에 `isViewerCreator`, `effectiveViewerGender` 전달과 조건을 최소 추가한다.
|
||||||
|
- [x] **GREEN 확인:** `./gradlew test --tests '*DefaultHomeRecommendationQueryRepositoryTest'`를 다시 실행해 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 이번 Task가 만든 중복만 정리하고 `./gradlew ktlintCheck` 및 focused test 결과를 Progress에 기록한다.
|
||||||
|
|
||||||
|
#### Task 1.2 v2 크리에이터 채널 라이브 탭 판정 보정
|
||||||
|
|
||||||
|
**Goal 실행 `P1-T2`:** 크리에이터 채널 라이브 탭에서 “조회자가 크리에이터인지” 기준으로 제한 방 노출을 막는다.
|
||||||
|
|
||||||
|
- **시작 조건:** `LCR-002` 확정.
|
||||||
|
- **완료 증거:** 다른 크리에이터는 제한 방을 받지 않고 방 생성자 본인은 받을 수 있는 focused test 통과.
|
||||||
|
- **범위 밖:** 크리에이터 채널 홈은 이미 `viewer.role == MemberRole.CREATOR`를 사용하므로 변경하지 않는다.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/application/CreatorChannelLiveQueryService.kt`
|
||||||
|
- Test: 기존 테스트 위치를 우선 확인하고, 없으면 `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/application/CreatorChannelLiveQueryServiceTest.kt`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
|
||||||
|
- `queryPort.findCurrentLive(..., isViewerCreator = viewer.role == MemberRole.CREATOR, ...)`로 전달한다.
|
||||||
|
- repository의 기존 `creatorJoinLiveCondition(viewerId, isViewerCreator)`와 본인 예외는 유지한다.
|
||||||
|
|
||||||
|
- [x] **RED:** 다른 크리에이터 viewer가 `isAvailableJoinCreator = false` 현재 라이브를 받지 않는 service test를 작성한다.
|
||||||
|
- [x] **RED 확인:** `./gradlew test --tests '*CreatorChannelLiveQueryServiceTest'`를 실행해 기존 `viewerId == creatorId` 판정 때문에 제한 방이 반환되는 assertion 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** `CreatorChannelLiveQueryService`의 `isViewerCreator` 계산을 `viewer.role == MemberRole.CREATOR`로 변경한다.
|
||||||
|
- [x] **GREEN 확인:** 같은 focused test를 다시 실행해 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 중복 정리 없이 끝낼 수 있으면 그대로 두고 focused test 및 `./gradlew ktlintCheck` 결과를 Progress에 기록한다.
|
||||||
|
|
||||||
|
### 검증 방법
|
||||||
|
|
||||||
|
#### Phase 1 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P1-GATE`:** 리스트 노출 제한과 성별 제한 경로가 PRD 요구사항과 일치하는지 판정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P1-T1`, `P1-T2` 완료.
|
||||||
|
- **완료 증거:** 아래 명령 통과와 수동 대조 기록.
|
||||||
|
- **범위 밖:** 입장 차단 구현.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest'
|
||||||
|
./gradlew ktlintCheck
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** focused test와 ktlint가 0 exit code로 종료한다.
|
||||||
|
|
||||||
|
수동 대조:
|
||||||
|
|
||||||
|
- [x] `HomeFollowingQueryService`와 `CreatorChannelHomeQueryService`의 기존 정상 조건은 변경하지 않았음을 확인한다.
|
||||||
|
- [x] `HomeRecommendationFacade.getHomeRecommendations`, `HomeRecommendationFacade.getLives`, `HomeOnAirLiveFacade.getOnAirLives`가 모두 조회자 크리에이터 여부와 유효 성별을 전달함을 확인한다.
|
||||||
|
|
||||||
|
## Phase 2: 직접 입장 차단
|
||||||
|
|
||||||
|
**Phase 결과:** 다른 크리에이터가 제한 방 ID를 알고 있어도 `/live/room/enter`로 입장할 수 없고, 성별 제한에 맞지 않는 사용자의 직접 입장도 기존 정책대로 차단된다.
|
||||||
|
|
||||||
|
**선행조건:** `P1-GATE` 완료.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P2-T1`, `P2-GATE` 완료 및 검증 기록 누적.
|
||||||
|
|
||||||
|
### 구현 항목
|
||||||
|
|
||||||
|
#### Task 2.1 `/live/room/enter` 크리에이터 제한 차단 및 성별 제한 회귀 검증
|
||||||
|
|
||||||
|
**Goal 실행 `P2-T1`:** `LiveRoomService.enterLive()`에서 다른 크리에이터의 제한 방 입장을 결제/roomInfo 변경 전 차단하고, 기존 성별 제한 입장 차단을 회귀 테스트로 고정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `LCR-003` 확정, `P1-GATE` 완료.
|
||||||
|
- **완료 증거:** 다른 크리에이터는 예외, 성별 제한에 맞지 않는 사용자는 `live.room.gender_restricted` 예외, 방 생성자 본인과 일반 유저는 기존 정책 유지 test 통과.
|
||||||
|
- **범위 밖:** error envelope schema 변경.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt`
|
||||||
|
- Test: 기존 테스트 위치를 우선 확인하고, 없으면 `src/test/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomServiceTest.kt`
|
||||||
|
- Optional Modify: 메시지 key 추가가 필요하다고 확인될 때만 `src/main/resources/messages*.properties` 계열 파일
|
||||||
|
|
||||||
|
**Implementation rule:**
|
||||||
|
|
||||||
|
`enterLive()`에서 room 조회 후, 결제나 `LiveRoomInfo` 변경 전에 아래 의미의 조건을 추가한다.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
if (
|
||||||
|
member.role == MemberRole.CREATOR &&
|
||||||
|
room.member!!.id!! != member.id!! &&
|
||||||
|
!room.isAvailableJoinCreator
|
||||||
|
) {
|
||||||
|
throw SodaException(messageKey = "live.room.not_found")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
기존 message key 재사용이 부적절하다고 확인되면 PRD와 plan-task를 먼저 갱신한 뒤 새 message key를 추가한다.
|
||||||
|
|
||||||
|
성별 제한은 현재 구현된 아래 의미의 조건을 유지한다. 이 작업에서는 해당 조건을 약화하지 말고 회귀 테스트로 고정한다.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
if (room.member!!.id!! != member.id!! && !member.canEnter(room.genderRestriction)) {
|
||||||
|
throw SodaException(messageKey = "live.room.gender_restricted")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **RED:** 다른 크리에이터가 `isAvailableJoinCreator = false` 방에 입장하면 예외가 발생하고 `roomInfoRepository.save`와 `canPaymentService.spendCan`이 호출되지 않는 테스트를 작성한다. 성별 제한에 맞지 않는 사용자가 입장하면 `live.room.gender_restricted` 예외가 발생하는 회귀 테스트도 작성한다.
|
||||||
|
- [x] **RED 확인:** `./gradlew test --tests '*LiveRoomServiceTest'`를 실행해 기존 구현이 입장을 허용하거나 후속 저장을 호출하는 assertion 실패를 확인한다.
|
||||||
|
- [x] **GREEN:** `LiveRoomService.enterLive()`에 최소 조건을 추가한다.
|
||||||
|
- [x] **GREEN 확인:** 같은 focused test를 다시 실행해 성공을 확인한다.
|
||||||
|
- [x] **REFACTOR:** 방 생성자 본인, 일반 유저, `Gender.NONE` 사용자 회귀 테스트를 함께 실행하고 `./gradlew ktlintCheck` 결과를 Progress에 기록한다.
|
||||||
|
|
||||||
|
### 검증 방법
|
||||||
|
|
||||||
|
#### Phase 2 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P2-GATE`:** 입장 차단, 리스트 노출 차단, 성별 제한 전체 흐름을 최종 판정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P2-T1` 완료.
|
||||||
|
- **완료 증거:** 아래 명령 통과와 계획 문서 Progress 기록.
|
||||||
|
- **범위 밖:** 실패 test 삭제·완화, 무관한 리팩터링.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest' --tests '*LiveRoomServiceTest'
|
||||||
|
./gradlew ktlintCheck
|
||||||
|
./gradlew test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** focused test, ktlint, 전체 test가 0 exit code로 종료한다. 크리에이터 제한과 성별 제한이 모두 조회/입장 경계에서 검증된다. 전체 test가 환경 문제로 실패하면 실패 원인과 focused/영향 범위 대체 검증을 Progress에 기록한다.
|
||||||
|
|
||||||
|
## Phase 3: 후속 리뷰 항목 구현
|
||||||
|
|
||||||
|
**근거:** `reviews/현재변경사항-review.md`의 `REV-001`, `REV-002` 및 두 항목을 후속 구현사항으로 추가하라는 사용자 요청.
|
||||||
|
|
||||||
|
**Phase 결과:** 제한 사용자는 방 정보 API로 토큰을 발급받지 못하고, 채널 소유자 본인의 제한 방 조회는 실제 repository 테스트로 보장된다.
|
||||||
|
|
||||||
|
**선행조건:** Phase 1~2 완료 및 후속 리뷰 확인. 기존 완료 체크박스와 검증 기록은 유지한다.
|
||||||
|
|
||||||
|
**Phase 완료 조건:** `P3-T1`, `P3-T2`, `P3-GATE` 완료와 실행 증거 기록.
|
||||||
|
|
||||||
|
### Task 3.1 토큰 발급 경계의 입장 제한 보강
|
||||||
|
|
||||||
|
- [x] **Task 3.1 완료**
|
||||||
|
|
||||||
|
**Goal 실행 `P3-T1`:** `REV-001`에 따라 `getRoomInfo()`에서 토큰 생성 전에 크리에이터·성별 제한을 적용하고 허용 사용자 회귀를 검증한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `REV-001`의 제한 검사 누락 확인. 실제 RTC 우회 접속은 아직 재현되지 않았으므로 서버의 토큰 생성 호출 여부부터 검증한다.
|
||||||
|
- **완료 증거:** 제한 사용자에 대한 실패 테스트 → 최소 수정 후 예외 key 및 RTC/RTM 생성기 무호출 검증 → 소유자·허용 사용자 회귀 통과.
|
||||||
|
- **범위 밖:** 새로운 입장 완료 여부 검사, 성인/비공개/결제 정책 재설계, 기존 발급 토큰 회수, Agora SDK 변경, API/DB 스키마 변경.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `docs/20260917_라이브_크리에이터_입장제한/prd.md` — LCR-003 및 성공 기준에 방 정보 API의 토큰 발급 경계를 명시.
|
||||||
|
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt` — `getRoomInfo()`.
|
||||||
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomServiceTest.kt`.
|
||||||
|
- Read: `src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt`, `src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt`.
|
||||||
|
|
||||||
|
**구현 기준:** 기존 방/roomInfo 존재 및 상호 차단 검사를 유지하고 첫 토큰 생성 호출 전에 `/enter`와 동일한 조건을 적용한다. 다른 크리에이터의 제한 방 접근은 `live.room.not_found`, 다른 사용자의 성별 불일치는 `live.room.gender_restricted`로 거절한다. 두 제한이 겹치면 크리에이터 제한을 먼저 판정한다. 생성자는 두 제한의 예외이며 성별은 `Member.canEnter()`를 재사용해 인증 성별 우선 및 `Gender.NONE` 허용 정책을 유지한다. RTC, RTM, v2v 토큰 모두 제한 검사를 통과한 뒤에만 생성한다.
|
||||||
|
|
||||||
|
- [x] **문서 정합성:** production 수정 전에 기존 PRD의 LCR-003, 관련 흐름·성공 기준에 토큰 발급 제한을 반영하고 후속 검증 항목은 미완료로 둔다.
|
||||||
|
- [x] **RED:** roomInfo와 room이 존재하고 차단 관계가 없는 fixture에서 다른 크리에이터의 제한 방 접근 및 성별 불일치 접근을 테스트한다. 토큰 생성기는 테스트용 문자열을 반환하도록 준비하고 정상 응답 구성에 필요한 의존성도 설정해, 무관한 null 오류가 아닌 예상 예외 미발생으로 실패하는지 확인한다.
|
||||||
|
- [x] **GREEN:** 위 조건을 최소 추가하고 예외 key, `rtcTokenBuilder`와 `rtmTokenBuilder`의 무호출을 검증한다. `/enter` 거절 후 `/info/{id}`에 해당하는 서비스 호출도 토큰을 생성하지 않는지 확인한다.
|
||||||
|
- [x] **회귀:** 소유자는 크리에이터·성별 제한이 모두 걸려도 허용되고, 일반 사용자와 허용 방의 다른 크리에이터는 성별이 맞으면 응답을 받는다. 인증 성별 우선, `Gender.NONE` 허용, 기존 상호 차단 거절을 확인한다.
|
||||||
|
- [x] **REFACTOR/기록:** 불필요한 추상화 없이 기존 스타일을 유지하고 아래 명령의 결과와 무엇을/왜/어떻게 검증했는지를 이 Task 아래에 누적한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests '*LiveRoomServiceTest' --tests '*LiveRoomServiceAdultVisibilityPolicyTest'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** RED 단계는 제한 검사 누락으로 실패하고, GREEN 이후는 exit 0. 차단 경로에서는 토큰이 생성되지 않는다.
|
||||||
|
|
||||||
|
**검증 기록 (2026-09-17):** `LiveRoomServiceTest`에 정상 응답 fixture와 제한·허용 회귀 10건을 추가했다. RED에서 다른 크리에이터, 성별 불일치, 중첩 제한, `/enter` 거절 후 정보 조회 테스트가 모두 예상 `SodaException` 미발생으로 실패했다. `getRoomInfo()`의 기존 상호 차단 검사 뒤와 모든 RTC/RTM/v2v 토큰 생성 앞에 `/enter`와 동일한 크리에이터·성별 조건을 추가한 뒤 `./gradlew test --rerun-tasks --tests '*LiveRoomServiceTest' --tests '*LiveRoomServiceAdultVisibilityPolicyTest'`가 20건 모두 통과하며 `BUILD SUCCESSFUL`로 종료했다. 거절 경로의 token builder 무호출과 소유자·성별 일치 사용자·허용 방 크리에이터·인증 성별 우선·`Gender.NONE`·기존 상호 차단 회귀를 확인했다. 실제 HTTP/RTC 연결은 검증하지 않았다. P3-T1 리뷰 결과 Blocker는 0건이다.
|
||||||
|
|
||||||
|
### Task 3.2 채널 소유자 제한 방 조회 회귀 테스트
|
||||||
|
|
||||||
|
- [x] **Task 3.2 완료**
|
||||||
|
|
||||||
|
**Goal 실행 `P3-T2`:** `REV-002`에 따라 채널 소유자 본인이 `isAvailableJoinCreator = false`인 현재 방을 조회할 수 있음을 H2 repository 테스트로 고정한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `REV-002` 확인. 기본 실행 순서는 `P3-T1` 이후이며 파일 변경은 독립적이다.
|
||||||
|
- **완료 증거:** 실제 repository의 `findCurrentLive()` 결과가 생성한 제한 방 ID와 일치하고 기존 타인 필터 테스트도 통과한다.
|
||||||
|
- **범위 밖:** production 조회 조건 수정, 채널 API/DTO 변경, 조회 정책 재설계.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/adapter/out/persistence/DefaultCreatorChannelLiveQueryRepositoryTest.kt`.
|
||||||
|
- Read: `src/main/kotlin/kr/co/vividnext/sodalive/v2/creator/channel/live/adapter/out/persistence/DefaultCreatorChannelLiveQueryRepository.kt`.
|
||||||
|
|
||||||
|
**TDD 예외 사유:** 소유자 예외는 이미 구현돼 있고 이번 Task는 누락된 테스트만 추가한다. RED를 만들기 위해 정상 production 코드를 변경하지 않는다. 대체 검증은 실제 H2 조회 결과 assertion과 기존 타인 제외 테스트의 동시 통과다.
|
||||||
|
|
||||||
|
- [x] **테스트 추가:** 기존 fixture로 활성·비성인·유효 channelName의 현재 방을 생성하고 `isAvailableJoinCreator = false`로 설정한다. `flushAndClear()` 후 `viewerId == creatorId`, `isViewerCreator = true`, 성별 제한과 일치하는 유효 성별로 조회해 방 ID를 단정한다.
|
||||||
|
- [x] **GREEN:** 아래 focused test로 소유자 조회와 기존 타인 제외 동작이 함께 통과하는지 확인한다.
|
||||||
|
- [x] **REFACTOR/기록:** 기존 fixture를 재사용하고 이 Task 아래에 명령과 검증 결과를 누적한다. 예상 밖 실패는 원인을 판정한 뒤 production 수정이 필요하면 먼저 Task 범위를 갱신한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests '*DefaultCreatorChannelLiveQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected:** exit 0. 소유자에게 제한 방이 반환되고 다른 크리에이터에게는 기존대로 제외된다.
|
||||||
|
|
||||||
|
**검증 기록 (2026-09-17):** production 조건은 변경하지 않고 `DefaultCreatorChannelLiveQueryRepositoryTest.shouldReturnRestrictedCurrentLiveForChannelOwner`를 추가했다. 활성·비성인·유효 channelName·`isAvailableJoinCreator = false`·`MALE_ONLY` 방을 실제 H2에 저장하고 `viewerId == creatorId`, `isViewerCreator = true`, `Gender.MALE`로 조회해 생성한 방 ID를 단정했다. `./gradlew test --rerun-tasks --tests '*DefaultCreatorChannelLiveQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest'`가 기존 다른 크리에이터 제외 테스트와 함께 `BUILD SUCCESSFUL`로 종료했다. `git diff --check`도 통과했다.
|
||||||
|
|
||||||
|
### Phase 3 Gate
|
||||||
|
|
||||||
|
**Goal 실행 `P3-GATE`:** 후속 두 항목과 기존 목록·입장 정책의 회귀 검증을 완료한다.
|
||||||
|
|
||||||
|
- **시작 조건:** `P3-T1`, `P3-T2` 완료.
|
||||||
|
- **완료 증거:** 아래 명령 exit 0, 토큰 생성 전 제한 검사 수동 대조, 소유자 조회 assertion 확인, PRD·리뷰·계획의 상태 및 검증 기록 갱신.
|
||||||
|
- **범위 밖:** 실제 RTC 접속을 검증하지 않고 검증했다고 기록하는 행위, 무관한 리팩터링.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew test --tests '*LiveRoomServiceTest' --tests '*LiveRoomServiceAdultVisibilityPolicyTest' --tests '*DefaultCreatorChannelLiveQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest' --tests '*DefaultHomeRecommendationQueryRepositoryTest'
|
||||||
|
./gradlew ktlintCheck
|
||||||
|
./gradlew tasks --all
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] 위 명령을 실행하고 각 Task의 완료 증거를 대조한다.
|
||||||
|
- [x] 실제 HTTP/RTC 연결 검증 여부와 환경 한계를 별도로 기록한다. 서비스 테스트 통과를 실제 RTC 접속 검증으로 간주하지 않는다.
|
||||||
|
- [x] 전체 회귀는 공통 인증·예외·설정까지 변경하거나 focused test로 영향 범위를 판단할 수 없는 경우 또는 사용자 요청 시 실행한다. 생략 시 이유와 위 대체 회귀 명령을 Progress에 남긴다.
|
||||||
|
- [x] `reviews/현재변경사항-review.md`에 수정 후 검증을 누적하고, PRD의 후속 성공 기준 및 Phase 3 상태를 실제 결과에 맞춰 갱신한다.
|
||||||
|
|
||||||
|
## 실행 순서와 의존성
|
||||||
|
|
||||||
|
| 순서 | Goal | 선행조건 | 병행 가능 | 차단 시 다음 행동 |
|
||||||
|
|---:|---|---|---|---|
|
||||||
|
| 1 | `P1-T1` | PRD 확정 | 아니요 | 호출부/port signature 대조 후 문서 갱신 |
|
||||||
|
| 2 | `P1-T2` | PRD 확정 | 예, `P1-T1`과 파일 충돌 없음 | service test 작성 방식 확정 |
|
||||||
|
| 3 | `P1-GATE` | `P1-T1`, `P1-T2` | 아니요 | 실패 소유 Task로 회귀 수정 |
|
||||||
|
| 4 | `P2-T1` | `P1-GATE` | 아니요 | 오류 message key 결정 후 문서 갱신 |
|
||||||
|
| 5 | `P2-GATE` | `P2-T1` | 아니요 | 실패 소유 Task로 회귀 수정 |
|
||||||
|
| 6 | `P3-T1` | `P2-GATE`, `REV-001` | 기본 순차 | 토큰 생성 전 거절 재현과 PRD 정합성 확인 |
|
||||||
|
| 7 | `P3-T2` | `REV-002` | `P3-T1`과 파일 독립 | 기존 fixture·현재 방 조건 확인 |
|
||||||
|
| 8 | `P3-GATE` | `P3-T1`, `P3-T2` | 아니요 | 실패 소유 Task로 회귀 수정 |
|
||||||
|
|
||||||
|
```text
|
||||||
|
P1-T1 ─┐
|
||||||
|
├→ P1-GATE → P2-T1 → P2-GATE
|
||||||
|
P1-T2 ─┘
|
||||||
|
|
||||||
|
P2-GATE → P3-T1 ─┐
|
||||||
|
P3-T2 ─┴→ P3-GATE
|
||||||
|
```
|
||||||
|
|
||||||
|
## 변경 금지 항목
|
||||||
|
|
||||||
|
- 공개 API 응답 DTO를 변경하지 않는다.
|
||||||
|
- `isAvailableJoinCreator`의 DB schema나 기본값을 변경하지 않는다.
|
||||||
|
- 기존 성인/성별/차단/강퇴/비공개/결제 조건을 완화하지 않는다.
|
||||||
|
- 테스트를 삭제·skip·완화하지 않는다.
|
||||||
|
- `as any`, 타입 억제, 불필요한 새 abstraction을 만들지 않는다.
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
- 2026-09-17: PRD와 계획 문서 작성. 구현은 아직 시작하지 않음.
|
||||||
|
- 2026-09-17: v2 홈 추천/온에어 라이브의 `genderRestriction` 조회 필터와 `/live/room/enter` 성별 제한 회귀 검증을 PRD/계획에 반영. 구현은 아직 시작하지 않음.
|
||||||
|
- 2026-09-17: `P1-T1` RED 확인. 신규 인자 추가 전 `compileTestKotlin`에서 `isViewerCreator`/`effectiveViewerGender` 미정의로 실패했고, 시그니처 연결 후 repository focused test 79건 중 크리에이터 제한 1건과 성별 제한 2건이 assertion 실패했다. facade 전달 전 `HomeRecommendationControllerTest.shouldLogHomeRecommendationPageFailure`도 신규 인자 불일치로 실패했다.
|
||||||
|
- 2026-09-17: `P1-T1` GREEN 확인. `./gradlew test --rerun-tasks --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*HomeRecommendationQueryServiceTest' --tests '*HomeRecommendationFacadeTest' --tests '*HomeOnAirLiveFacadeTest'`가 `BUILD SUCCESSFUL`로 종료했다. 컴파일 경고는 기존 deprecated API 및 기존 테스트 경고이며 신규 실패는 없다.
|
||||||
|
- 2026-09-17: `P1-T1` REFACTOR 확인. 추가 추상화 없이 기존 QueryDSL 패턴을 재사용했고 `./gradlew ktlintCheck`가 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P1-T2` RED 확인. 다른 ID의 `MemberRole.CREATOR` viewer 테스트에서 `currentLiveIsViewerCreator`가 기대값 `true`, 실제값 `false`로 실패했다.
|
||||||
|
- 2026-09-17: `P1-T2` GREEN 확인. `CreatorChannelLiveQueryService`가 `viewer.role == MemberRole.CREATOR`를 전달하도록 최소 수정한 뒤 `./gradlew test --rerun-tasks --tests '*CreatorChannelLiveQueryServiceTest'`가 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P1-T2` REFACTOR 확인. 별도 추상화 없이 기존 역할 enum을 사용했고 `./gradlew ktlintCheck`가 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P1-GATE` 자동 검증. `./gradlew test --rerun-tasks --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*HomeRecommendationQueryServiceTest' --tests '*HomeRecommendationFacadeTest' --tests '*HomeOnAirLiveFacadeTest' --tests '*CreatorChannelLiveQueryServiceTest'`와 `./gradlew ktlintCheck`가 모두 `BUILD SUCCESSFUL`로 종료했다. 출력된 deprecated API 및 unchecked cast 경고는 변경 범위 밖의 기존 경고다.
|
||||||
|
- 2026-09-17: `P1-GATE` 수동 대조. `HomeFollowingQueryService`와 `CreatorChannelHomeQueryService`는 변경하지 않았고, 홈 추천·라이브·온에어 facade 세 경로가 조회자 역할과 유효 성별을 전달함을 확인했다.
|
||||||
|
- 2026-09-17: `P1-GATE` 리뷰 승인. Phase 1 전체 변경 범위의 PRD/계획 위반과 기존 필터 약화가 없음을 확인했으며 Blocker는 0건이다.
|
||||||
|
- 2026-09-17: `P2-T1` RED 확인. 다른 크리에이터의 제한 방 입장 테스트가 `SodaException`을 기대했지만 예외가 발생하지 않아 실패했다.
|
||||||
|
- 2026-09-17: `P2-T1` GREEN 확인. `LiveRoomService.enterLive()`의 결제 및 room-info 접근 전에 역할·방 생성자·`isAvailableJoinCreator` 조건을 추가한 뒤 `./gradlew test --rerun-tasks --tests '*LiveRoomServiceTest'`가 5건 모두 통과하며 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P2-T1` REFACTOR 확인. 방 생성자, 일반 사용자, `Gender.NONE`, 성별 불일치 회귀 테스트와 차단 경로의 무부수효과 검증을 유지했고 `./gradlew ktlintCheck`가 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P2-GATE` 자동 검증. `./gradlew test --rerun-tasks --tests '*DefaultHomeRecommendationQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest' --tests '*LiveRoomServiceTest'`, `./gradlew ktlintCheck`, `./gradlew test --rerun-tasks`가 모두 `BUILD SUCCESSFUL`로 종료했다.
|
||||||
|
- 2026-09-17: `P2-GATE` 최종 리뷰 승인. 구현·테스트·문서의 PRD/계획 일치와 API/스키마 유지 여부를 재검토했으며 Blocker는 0건이다.
|
||||||
|
- 2026-09-17: 현재 변경사항 후속 리뷰. focused 140건 및 직접 영향 회귀 42건, 총 182건 통과. `./gradlew ktlintCheck tasks --all` 성공(ktlint UP-TO-DATE). 소스 수정 없이 목록·입장 경계 중심의 검토이므로 전체 회귀는 재실행하지 않았다. 명시된 구현 범위는 적합하나, 기존 `/live/room/info/{id}` 토큰 발급 경계의 제한 누락과 채널 소유자 조회 테스트 보완점을 `reviews/현재변경사항-review.md`에 기록했다. 실제 HTTP/RTC 연결은 미검증이며, 제품 전체의 입장 차단 완결성 승인은 보류한다. 기존 완료 기록은 유지한다.
|
||||||
|
- 2026-09-17: 사용자 요청에 따라 `REV-001`을 토큰 발급 제한 보강 `P3-T1`, `REV-002`를 채널 소유자 조회 테스트 `P3-T2`로 구체화하고 `P3-GATE`를 추가했다. 파일·조건·예외 key·회귀 시나리오·명령·완료 증거를 명시했으며 기존 Phase 1~2 완료 기록은 유지했다. `./gradlew tasks --all`은 exit 0, `BUILD SUCCESSFUL`; `git diff --check`도 통과했다. 이번 변경은 계획 문서만 갱신하므로 코드 테스트는 실행하지 않았다. 후속 구현은 미착수이며 다음 Goal은 `P3-T1`이다.
|
||||||
|
- 2026-09-17: `P3-T1` 시작 전 문서 정합성 완료. PRD의 관련 review, 문제·목표·사용자 흐름, `LCR-003`, API 계약, 보안 기준과 미완료 성공 기준에 `/live/room/info/{id}` 토큰 발급 경계 및 `REV-002` 회귀 검증을 반영했다.
|
||||||
|
- 2026-09-17: `P3-GATE` 자동 검증. `./gradlew test --rerun-tasks --tests '*LiveRoomServiceTest' --tests '*LiveRoomServiceAdultVisibilityPolicyTest' --tests '*DefaultCreatorChannelLiveQueryRepositoryTest' --tests '*CreatorChannelLiveQueryServiceTest' --tests '*DefaultHomeRecommendationQueryRepositoryTest'`가 119건, 실패·오류·skip 0으로 `BUILD SUCCESSFUL` 종료했다. `./gradlew ktlintCheck`, `./gradlew tasks --all`, `git diff --check`도 모두 성공했다.
|
||||||
|
- 2026-09-17: 토큰 발급 경계는 보안 관련 변경이므로 조건부 전체 회귀를 실행했다. `./gradlew test --rerun-tasks`가 `BUILD SUCCESSFUL`로 종료했다. 실제 HTTP 인증/직렬화 및 Agora RTC/RTM 연결은 환경상 실행하지 않았고, 실제 서비스 메서드 호출과 token builder 무호출 assertion 및 H2 repository 조회를 대체 실행 증거로 사용했다.
|
||||||
|
- 2026-09-17: `P3-GATE` 최종 reviewer gate 승인. Phase 3 구현·테스트·PRD·계획·후속 리뷰 해소 기록을 전체 diff 기준으로 검토했으며 Blocker는 0건이다.
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# 라이브 크리에이터 입장 제한 PRD
|
||||||
|
|
||||||
|
## 문서 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| 문서 상태 | Phase 1~3 구현 및 검증 완료 |
|
||||||
|
| 작성일 | `2026-09-17` |
|
||||||
|
| 최종 수정일 | `2026-09-17` |
|
||||||
|
| 대상 제품 | 라이브 방 목록 노출 및 입장 제한 |
|
||||||
|
| 작성자·결정권자 | Sisyphus / 사용자 |
|
||||||
|
| 관련 API Contract | 기존 API 응답 스키마 변경 없음 |
|
||||||
|
| 관련 구현 계획 | `docs/20260917_라이브_크리에이터_입장제한/plan-task.md` |
|
||||||
|
| 관련 review | `reviews/현재변경사항-review.md` (`REV-001`, `REV-002`) |
|
||||||
|
|
||||||
|
## 1. Overview
|
||||||
|
|
||||||
|
라이브 방 생성자가 `isAvailableJoinCreator = false`로 설정한 방은 다른 크리에이터 계정에게 노출되거나 입장 가능하면 안 된다. 또한 `genderRestriction`으로 입장 가능 성별이 제한된 방은 조회 단계에서도 입장 가능한 사용자에게만 노출돼야 한다. 기존 `/live/room` 계열 조회는 대부분 이 정책을 적용하지만, v2 홈 추천/온에어 라이브와 일부 크리에이터 채널 라이브 경로에서 정책 누락 가능성이 확인됐다. 후속 리뷰에서 `/live/room/info/{id}`가 같은 제한을 확인하지 않고 RTC/RTM 토큰을 발급하는 경로도 확인됐다.
|
||||||
|
|
||||||
|
## 2. Problem Statement
|
||||||
|
|
||||||
|
- `v2` 홈 추천/온에어 라이브 조회는 `findLiveRecommendations()`에서 조회자의 크리에이터 여부와 유효 성별을 받지 않아 `isAvailableJoinCreator = false`인 방도 크리에이터에게 노출될 수 있고, 성별 제한 방도 입장 불가능한 사용자에게 노출될 수 있다.
|
||||||
|
- `v2` 크리에이터 채널 라이브 탭은 조회자가 크리에이터인지가 아니라 조회자가 해당 채널 주인인지로만 `isViewerCreator`를 계산해, 다른 크리에이터에게 제한 방이 노출될 수 있다.
|
||||||
|
- `/live/room/enter`는 성인, 비공개, 차단, 강퇴, 성별, 정원, 결제 조건은 확인하지만 크리에이터 입장 제한을 최종 차단하지 않는다.
|
||||||
|
- `/live/room/info/{id}`는 방 정보와 상호 차단만 확인한 뒤 RTC/RTM 토큰을 생성하므로 `/enter`에서 거절된 사용자도 토큰 발급을 요청할 수 있다.
|
||||||
|
|
||||||
|
문제를 해결했다는 판단은 다른 크리에이터가 `isAvailableJoinCreator = false` 방을 리스트에서 보지 못하고, 성별 제한에 맞지 않는 사용자가 제한 방을 리스트에서 보지 못하며, 직접 `enter` 요청을 보내도 차단되는 것으로 한다.
|
||||||
|
|
||||||
|
## 3. Goals
|
||||||
|
|
||||||
|
- 다른 크리에이터 계정은 `isAvailableJoinCreator = false` 라이브 방을 문제 의심 경로에서 볼 수 없다.
|
||||||
|
- 다른 크리에이터 계정은 `isAvailableJoinCreator = false` 라이브 방에 직접 입장할 수 없다.
|
||||||
|
- 입장 가능 성별이 제한된 라이브 방은 성별이 맞는 사용자 또는 방 생성자 본인에게만 조회된다.
|
||||||
|
- 입장 가능 성별이 제한된 라이브 방은 성별이 맞는 사용자 또는 방 생성자 본인만 입장할 수 있다.
|
||||||
|
- 방을 만든 크리에이터 본인은 예외로 계속 조회 및 입장 가능하다.
|
||||||
|
- 제한 사용자는 `/live/room/info/{id}`에서 RTC/RTM/v2v 토큰을 발급받을 수 없다.
|
||||||
|
- 기존 응답 DTO와 공개 API 스키마는 변경하지 않는다.
|
||||||
|
|
||||||
|
## 4. Non-Goals
|
||||||
|
|
||||||
|
- `isAvailableJoinCreator` 필드명, 의미, DB 스키마 변경은 하지 않는다.
|
||||||
|
- 일반 유저, 관리자, 봇, 에이전트의 기존 노출/입장 정책은 변경하지 않는다.
|
||||||
|
- 기존 성인/성별/차단/강퇴/비공개/결제 정책은 재설계하지 않는다.
|
||||||
|
- `Gender.NONE` 사용자의 기존 정책은 변경하지 않는다. 기존 `Member.canEnter()`와 리스트 조건처럼 성별 미설정 사용자는 성별 제한을 통과한다.
|
||||||
|
- 새 API endpoint나 새 응답 필드는 만들지 않는다.
|
||||||
|
|
||||||
|
## 5. Target Users and Permissions
|
||||||
|
|
||||||
|
| 사용자 | 목표 | 정책 |
|
||||||
|
|---|---|---|
|
||||||
|
| 방 생성 크리에이터 | 본인이 만든 방 조회 및 입장 | `isAvailableJoinCreator = false`여도 허용 |
|
||||||
|
| 다른 크리에이터 | 크리에이터 입장 제한 방 접근 방지 | `isAvailableJoinCreator = false`면 목록 미노출 및 입장 차단 |
|
||||||
|
| 일반 유저 | 기존 라이브 조회 및 입장 | `genderRestriction`에 맞으면 허용, `Gender.NONE`은 기존처럼 허용 |
|
||||||
|
|
||||||
|
## 6. 핵심 사용자 흐름
|
||||||
|
|
||||||
|
1. 일반 유저 또는 방 생성자가 `isAvailableJoinCreator = false` 방을 조회하면 기존 정책에 따라 표시된다.
|
||||||
|
2. 성별 제한에 맞지 않는 사용자가 `genderRestriction` 제한 방을 v2 홈 추천/온에어 라이브에서 조회하면 표시되지 않는다.
|
||||||
|
3. 다른 크리에이터가 같은 방을 v2 홈 추천/온에어 라이브 또는 크리에이터 채널 라이브 경로에서 조회하면 표시되지 않는다.
|
||||||
|
4. 성별 제한에 맞지 않는 사용자가 방 ID를 알고 `/live/room/enter`를 호출하면 입장이 거부된다.
|
||||||
|
5. 다른 크리에이터가 방 ID를 알고 `/live/room/enter`를 호출하면 입장이 거부된다.
|
||||||
|
6. 방 생성 크리에이터가 본인 방에 입장하면 기존처럼 허용된다.
|
||||||
|
7. `/enter`에서 거절되는 다른 크리에이터 또는 성별 불일치 사용자가 `/live/room/info/{id}`를 호출하면 토큰 생성 전에 같은 정책으로 거절된다.
|
||||||
|
8. 방 생성자, 성별이 맞는 일반 사용자, 크리에이터 입장이 허용된 방의 다른 크리에이터는 기존처럼 방 정보와 토큰을 받는다.
|
||||||
|
|
||||||
|
## 7. 기능 요구사항
|
||||||
|
|
||||||
|
| ID | 상태 | 요구사항 | 수용 기준 | 계약/Goal 연결 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `LCR-001` | 확정 | v2 홈 추천/온에어 라이브 조회에서 다른 크리에이터에게 `isAvailableJoinCreator = false` 방을 숨기고, 성별 제한에 맞지 않는 사용자에게 `genderRestriction` 제한 방을 숨긴다. | `HomeRecommendationQueryService.findLiveRecommendations()` 호출 경로가 조회자 크리에이터 여부와 유효 성별을 전달한다. repository는 `liveRoom.isAvailableJoinCreator.isTrue.or(liveRoom.member.id.eq(memberId))`와 `Gender.MALE -> ALL/MALE_ONLY`, `Gender.FEMALE -> ALL/FEMALE_ONLY`, `Gender.NONE/null -> 필터 없음` 조건을 적용한다. | `P1-T1` |
|
||||||
|
| `LCR-002` | 확정 | v2 크리에이터 채널 라이브 탭에서 다른 크리에이터에게 제한 방을 숨긴다. | `CreatorChannelLiveQueryService`가 `viewer.role == MemberRole.CREATOR` 기준으로 조회자 크리에이터 여부를 전달한다. 방 생성자 본인은 예외다. | `P1-T2` |
|
||||||
|
| `LCR-003` | 확정 | `/live/room/enter`와 `/live/room/info/{id}`에서 다른 크리에이터의 제한 방 입장·토큰 발급과 성별 제한에 맞지 않는 접근을 차단한다. | 다른 크리에이터 제한은 `live.room.not_found`, 성별 제한은 `live.room.gender_restricted`로 결제·입장 상태 변경 또는 RTC/RTM/v2v 토큰 생성 전에 거절한다. 생성자는 예외이며 성별 판정은 `Member.canEnter()`를 재사용한다. | `P2-T1`, `P3-T1` |
|
||||||
|
| `LCR-004` | 확정 | 기존 정상 경로는 유지한다. | 일반 유저와 방 생성자 본인의 조회/입장 동작은 유지되고 기존 테스트 또는 신규 회귀 테스트로 확인된다. | `P2-GATE` |
|
||||||
|
|
||||||
|
## 8. API 계약
|
||||||
|
|
||||||
|
- 변경되는 공개 request/response 필드는 없다.
|
||||||
|
- `/live/room/enter`는 기존 오류 envelope를 사용한다.
|
||||||
|
- `/live/room/info/{id}`도 기존 오류 envelope와 message key를 재사용하며 응답 필드는 변경하지 않는다.
|
||||||
|
- 신규 message key 추가 여부는 구현 단계에서 기존 `live.room.gender_restricted`, `common.error.invalid_request`, `live.room.not_found` 패턴을 확인해 결정한다. 새 key가 필요하면 메시지 리소스와 테스트를 함께 갱신한다.
|
||||||
|
|
||||||
|
## 9. 보안과 데이터 취급
|
||||||
|
|
||||||
|
- 숨김 정책은 클라이언트 표시만으로 끝내지 않고 서버 입장 경계에서 한 번 더 차단한다.
|
||||||
|
- 제한 방 존재 여부를 다른 크리에이터에게 과도하게 노출하지 않는 오류 메시지를 우선한다.
|
||||||
|
- 민감정보, 결제 정보, password, token은 로그나 테스트 fixture에 기록하지 않는다.
|
||||||
|
- 제한 검사는 RTC, RTM, v2v 토큰 생성 호출보다 먼저 수행한다.
|
||||||
|
|
||||||
|
## 10. 성공 기준
|
||||||
|
|
||||||
|
- [x] 다른 크리에이터는 `isAvailableJoinCreator = false` 방을 v2 홈 추천/온에어 라이브에서 받지 않는다. (`LCR-001`)
|
||||||
|
- [x] 성별 제한에 맞지 않는 사용자는 `genderRestriction` 제한 방을 v2 홈 추천/온에어 라이브에서 받지 않는다. (`LCR-001`)
|
||||||
|
- [x] 다른 크리에이터는 `isAvailableJoinCreator = false` 방을 v2 크리에이터 채널 라이브 탭에서 받지 않는다. (`LCR-002`)
|
||||||
|
- [x] 다른 크리에이터는 `isAvailableJoinCreator = false` 방에 직접 입장할 수 없다. (`LCR-003`)
|
||||||
|
- [x] 성별 제한에 맞지 않는 사용자는 제한 방에 직접 입장할 수 없다. (`LCR-003`)
|
||||||
|
- [x] 방 생성 크리에이터 본인은 본인 방을 조회하고 입장할 수 있다. (`LCR-001`~`LCR-003`)
|
||||||
|
- [x] 공개 API 스키마 변경 없이 focused test와 영향 범위 회귀가 통과한다.
|
||||||
|
- [x] 다른 크리에이터와 성별 불일치 사용자는 `/live/room/info/{id}`에서 토큰 생성 전에 거절되고 토큰 생성기가 호출되지 않는다. (`LCR-003`)
|
||||||
|
- [x] 방 생성자, 성별이 맞는 일반 사용자, 크리에이터 입장이 허용된 방의 다른 크리에이터, `Gender.NONE` 사용자는 기존처럼 방 정보와 토큰을 받는다. (`LCR-003`, `LCR-004`)
|
||||||
|
- [x] 채널 소유자 본인은 `isAvailableJoinCreator = false`인 현재 방을 repository 조회에서 받는다. (`LCR-002`, `LCR-004`)
|
||||||
|
|
||||||
|
## 11. Decision Log
|
||||||
|
|
||||||
|
| 일시 | 결정 | 근거 |
|
||||||
|
|---|---|---|
|
||||||
|
| 2026-09-17 | `isAvailableJoinCreator = false`는 다른 크리에이터만 제한하고 방 생성자 본인은 예외로 둔다. | 기존 QueryDSL 조건들이 `liveRoom.member.id.eq(viewerId)` 예외를 두는 패턴과 사용자 선택 A |
|
||||||
|
| 2026-09-17 | 이 작업은 문서가 필요한 구현 작업으로 분류한다. | 저장소 `AGENTS.md`와 `docs/agent-guides/작업절차.md`가 모든 구현 작업 전 PRD와 plan-task를 요구 |
|
||||||
|
| 2026-09-17 | v2 홈 추천/온에어 라이브에도 기존 조회 경로와 같은 `genderRestriction` 필터를 적용한다. | `/live/room`, 팔로잉, 크리에이터 채널 조회 경로가 이미 성별 조건을 조회 단계에서 적용하고 있으며 사용자가 문서 반영을 요청 |
|
||||||
|
| 2026-09-17 | `/live/room/enter`의 성별 제한은 기존 `Member.canEnter()` 정책을 유지한다. | 현재 `LiveRoomService.enterLive()`가 이미 `room.member.id != member.id && !member.canEnter(room.genderRestriction)`을 차단하고, `Gender.NONE`은 `canEnter()`에서 허용됨 |
|
||||||
|
| 2026-09-17 | `/live/room/info/{id}`에서도 `/enter`와 같은 크리에이터·성별 제한을 토큰 생성 전에 적용한다. | `REV-001`에서 `/enter` 거절 후에도 RTC/RTM/v2v 토큰 생성 경로가 열려 있음을 코드로 확인 |
|
||||||
|
| 2026-09-17 | 채널 소유자의 제한 방 조회는 production 변경 없이 H2 repository 회귀 테스트로 고정한다. | `REV-002`와 기존 `creatorJoinLiveCondition()`의 소유자 예외 |
|
||||||
|
|
||||||
|
## 12. 열린 질문
|
||||||
|
|
||||||
|
- 없음. 구현 중 기존 메시지 key 재사용이 부적절하다고 확인되면 plan-task 범위를 먼저 갱신한다.
|
||||||
Reference in New Issue
Block a user