feat(live-room): 라이브 룸 채팅 얼림 상태 저장/조회 기능 추가
- `LiveRoomInfo`에 `isChatFrozen` 필드(기본 false) 추가하여 Redis에 상태 저장 가능 - `GetRoomInfoResponse`에 `isChatFrozen` 노출 및 `LiveRoomService.getRoomInfo` 매핑 반영 - 요청 DTO `SetChatFreezeRequest(roomId, isChatFrozen)` 추가 - `PUT /live/room/info/set/chat-freeze` 엔드포인트 추가(크리에이터 권한 검증 포함)
This commit is contained in:
32
docs/20260319_라이브룸채팅얼림상태저장및조회.md
Normal file
32
docs/20260319_라이브룸채팅얼림상태저장및조회.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 라이브 룸 채팅 얼림 상태 저장/조회 추가
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [x] 데이터 모델(LiveRoomInfo)에 `isChatFrozen` 필드(Boolean, 기본 false) 추가
|
||||||
|
- [x] 요청 DTO `SetChatFreezeRequest(roomId, isChatFrozen)` 추가
|
||||||
|
- [x] 서비스 `setChatFreeze` 구현(권한: 크리에이터만)
|
||||||
|
- [x] 컨트롤러 `PUT /live/room/info/set/chat-freeze` 엔드포인트 추가
|
||||||
|
- [x] `GetRoomInfoResponse`에 `isChatFrozen`(Boolean, 기본 false) 추가 및 조회 응답 포함
|
||||||
|
- [x] 단위 테스트는 불필요 판단으로 제거(수동 테스트 가이드로 대체)
|
||||||
|
- [x] `./gradlew build`로 컴파일 확인
|
||||||
|
- [x] `./gradlew ktlintCheck` 실행 및 포맷 확인
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
### 1차 구현
|
||||||
|
- 무엇을: 채팅 얼림 상태 저장/조회 기능 구현
|
||||||
|
- 왜: 라이브 룸 채팅 제어 기능 제공을 위해
|
||||||
|
- 어떻게:
|
||||||
|
- 빌드/테스트 명령 실행: `./gradlew clean build` 성공, `./gradlew ktlintCheck` 예정
|
||||||
|
- API 수동 점검 예정: `PUT /live/room/info/set/chat-freeze` 요청 본문 `{ "roomId": 1, "isChatFrozen": true }` → 200 OK, 이후 `GET /live/room/info/{id}` 응답에 `isChatFrozen: true` 포함 확인
|
||||||
|
|
||||||
|
### 수동 테스트 방법
|
||||||
|
- 사전조건: 방 생성 및 시작되어 Redis에 `LiveRoomInfo`가 존재해야 함
|
||||||
|
- 1) 채팅 얼림 설정
|
||||||
|
- 요청: `PUT /live/room/info/set/chat-freeze`
|
||||||
|
- 헤더: `Authorization: Bearer <creator_token>`
|
||||||
|
- 바디: `{ "roomId": <roomId>, "isChatFrozen": true }`
|
||||||
|
- 기대: 200 OK, 본문은 `ApiResponse.ok` 규격
|
||||||
|
- 2) 룸 정보 조회에서 반영 확인
|
||||||
|
- 요청: `GET /live/room/info/{roomId}`
|
||||||
|
- 기대: 응답 JSON 내 `isChatFrozen: true`
|
||||||
|
- 3) 해제 시나리오 재검증
|
||||||
|
- `isChatFrozen`을 false로 요청 후 조회 시 `false` 확인
|
||||||
@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.common.SodaException
|
|||||||
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
|
import kr.co.vividnext.sodalive.live.room.donation.DeleteLiveRoomDonationMessage
|
||||||
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
|
import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationRequest
|
||||||
|
import kr.co.vividnext.sodalive.live.room.info.SetChatFreezeRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
import kr.co.vividnext.sodalive.live.room.like.LiveRoomLikeHeartRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService
|
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
@@ -204,6 +205,16 @@ class LiveRoomController(
|
|||||||
ApiResponse.ok(service.setManager(request, member))
|
ApiResponse.ok(service.setManager(request, member))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/info/set/chat-freeze")
|
||||||
|
fun setChatFreeze(
|
||||||
|
@RequestBody request: SetChatFreezeRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.setChatFreeze(request, member))
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/donation")
|
@PostMapping("/donation")
|
||||||
fun donation(
|
fun donation(
|
||||||
@RequestBody request: LiveRoomDonationRequest,
|
@RequestBody request: LiveRoomDonationRequest,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import kr.co.vividnext.sodalive.live.room.info.GetRoomInfoResponse
|
|||||||
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfo
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfo
|
||||||
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
|
||||||
import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember
|
||||||
|
import kr.co.vividnext.sodalive.live.room.info.SetChatFreezeRequest
|
||||||
import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService
|
import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService
|
||||||
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListResponse
|
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartListResponse
|
||||||
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartTotalResponse
|
import kr.co.vividnext.sodalive.live.room.like.GetLiveRoomHeartTotalResponse
|
||||||
@@ -1052,10 +1053,29 @@ class LiveRoomService(
|
|||||||
creatorLanguageCode = creatorLanguageCode,
|
creatorLanguageCode = creatorLanguageCode,
|
||||||
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
||||||
password = room.password,
|
password = room.password,
|
||||||
isActiveRoulette = isActiveRoulette
|
isActiveRoulette = isActiveRoulette,
|
||||||
|
isChatFrozen = roomInfo.isChatFrozen
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setChatFreeze(request: SetChatFreezeRequest, member: Member) {
|
||||||
|
val lock = getOrCreateLock(memberId = member.id!!)
|
||||||
|
lock.write {
|
||||||
|
val room = repository.findByIdOrNull(request.roomId)
|
||||||
|
?: throw SodaException(messageKey = "live.room.not_found")
|
||||||
|
|
||||||
|
if (room.member!!.id!! != member.id!!) {
|
||||||
|
throw SodaException(messageKey = "common.error.access_denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
val roomInfo = roomInfoRepository.findByIdOrNull(request.roomId)
|
||||||
|
?: throw SodaException(messageKey = "live.room.info_not_found")
|
||||||
|
|
||||||
|
roomInfo.isChatFrozen = request.isChatFrozen
|
||||||
|
roomInfoRepository.save(roomInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getDonationMessageList(roomId: Long, member: Member): List<LiveRoomDonationMessage> {
|
fun getDonationMessageList(roomId: Long, member: Member): List<LiveRoomDonationMessage> {
|
||||||
val liveRoomCreatorId = repository.getLiveRoomCreatorId(roomId)
|
val liveRoomCreatorId = repository.getLiveRoomCreatorId(roomId)
|
||||||
?: throw SodaException(messageKey = "live.room.info_not_found")
|
?: throw SodaException(messageKey = "live.room.info_not_found")
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ data class GetRoomInfoResponse(
|
|||||||
val creatorLanguageCode: String?,
|
val creatorLanguageCode: String?,
|
||||||
val isPrivateRoom: Boolean = false,
|
val isPrivateRoom: Boolean = false,
|
||||||
val password: String? = null,
|
val password: String? = null,
|
||||||
val isActiveRoulette: Boolean = false
|
val isActiveRoulette: Boolean = false,
|
||||||
|
val isChatFrozen: Boolean = false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ data class LiveRoomInfo(
|
|||||||
managerCount = managerList.size
|
managerCount = managerList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 채팅 얼림 상태 (기본값: 해제)
|
||||||
|
var isChatFrozen: Boolean = false
|
||||||
|
|
||||||
fun addDonationMessage(memberId: Long, nickname: String, isSecret: Boolean, can: Int, donationMessage: String) {
|
fun addDonationMessage(memberId: Long, nickname: String, isSecret: Boolean, can: Int, donationMessage: String) {
|
||||||
val donationMessageSet = donationMessageList.toMutableSet()
|
val donationMessageSet = donationMessageList.toMutableSet()
|
||||||
donationMessageSet.add(
|
donationMessageSet.add(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package kr.co.vividnext.sodalive.live.room.info
|
||||||
|
|
||||||
|
data class SetChatFreezeRequest(
|
||||||
|
val roomId: Long,
|
||||||
|
val isChatFrozen: Boolean
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user