fix(chat): AI 캐릭터 성인 접근 판정을 국가별 정책에 맞춘다
This commit is contained in:
@@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -16,7 +17,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RequestMapping("/api/chat/quota")
|
||||
class ChatQuotaController(
|
||||
private val chatQuotaService: ChatQuotaService,
|
||||
private val canPaymentService: CanPaymentService
|
||||
private val canPaymentService: CanPaymentService,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService
|
||||
) {
|
||||
|
||||
data class ChatQuotaStatusResponse(
|
||||
@@ -33,7 +35,7 @@ class ChatQuotaController(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
): ApiResponse<ChatQuotaStatusResponse> = run {
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (!resolveIsAdultAccessible(member)) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val s = chatQuotaService.getStatus(member.id!!)
|
||||
ApiResponse.ok(ChatQuotaStatusResponse(s.totalRemaining, s.nextRechargeAtEpochMillis))
|
||||
@@ -45,10 +47,9 @@ class ChatQuotaController(
|
||||
@RequestBody request: ChatQuotaPurchaseRequest
|
||||
): ApiResponse<ChatQuotaStatusResponse> = run {
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (!resolveIsAdultAccessible(member)) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (request.container.isBlank()) throw SodaException(messageKey = "chat.quota.container_required")
|
||||
|
||||
// 30캔 차감 처리 (결제 기록 남김)
|
||||
canPaymentService.spendCan(
|
||||
memberId = member.id!!,
|
||||
needCan = 30,
|
||||
@@ -56,8 +57,15 @@ class ChatQuotaController(
|
||||
container = request.container
|
||||
)
|
||||
|
||||
// 글로벌 유료 개념 제거됨: 구매 성공 시에도 글로벌 쿼터 증액 없음
|
||||
val s = chatQuotaService.getStatus(member.id!!)
|
||||
ApiResponse.ok(ChatQuotaStatusResponse(s.totalRemaining, s.nextRechargeAtEpochMillis))
|
||||
}
|
||||
|
||||
private fun resolveIsAdultAccessible(member: Member?): Boolean {
|
||||
if (member == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return memberContentPreferenceService.getStoredPreference(member).isAdult
|
||||
}
|
||||
}
|
||||
|
||||
+13
-10
@@ -7,6 +7,7 @@ import kr.co.vividnext.sodalive.chat.room.repository.ChatRoomRepository
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
@@ -21,7 +22,8 @@ class ChatRoomQuotaController(
|
||||
private val chatRoomRepository: ChatRoomRepository,
|
||||
private val participantRepository: ChatParticipantRepository,
|
||||
private val chatRoomQuotaService: ChatRoomQuotaService,
|
||||
private val chatQuotaService: ChatQuotaService
|
||||
private val chatQuotaService: ChatQuotaService,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService
|
||||
) {
|
||||
|
||||
data class PurchaseRoomQuotaRequest(
|
||||
@@ -53,17 +55,15 @@ class ChatRoomQuotaController(
|
||||
@RequestBody req: PurchaseRoomQuotaRequest
|
||||
): ApiResponse<PurchaseRoomQuotaResponse> = run {
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (!resolveIsAdultAccessible(member)) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (req.container.isBlank()) throw SodaException(messageKey = "chat.room.quota.invalid_access")
|
||||
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
|
||||
// 내 참여 여부 확인
|
||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException(messageKey = "chat.room.quota.invalid_access")
|
||||
|
||||
// 캐릭터 참여자 확인(유효한 AI 캐릭터 방인지 체크 및 characterId 기본값 보조)
|
||||
val characterParticipant = participantRepository
|
||||
.findByChatRoomAndParticipantTypeAndIsActiveTrue(room, ParticipantType.CHARACTER)
|
||||
?: throw SodaException(messageKey = "chat.room.quota.not_ai_room")
|
||||
@@ -74,7 +74,6 @@ class ChatRoomQuotaController(
|
||||
val characterId = character.id
|
||||
?: throw SodaException(messageKey = "chat.room.quota.character_required")
|
||||
|
||||
// 서비스에서 결제 포함하여 처리
|
||||
val status = chatRoomQuotaService.purchase(
|
||||
memberId = member.id!!,
|
||||
chatRoomId = chatRoomId,
|
||||
@@ -99,24 +98,20 @@ class ChatRoomQuotaController(
|
||||
@PathVariable chatRoomId: Long
|
||||
): ApiResponse<RoomQuotaStatusResponse> = run {
|
||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||
if (member.auth == null) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
if (!resolveIsAdultAccessible(member)) throw SodaException(messageKey = "common.error.adult_verification_required")
|
||||
|
||||
val room = chatRoomRepository.findByIdAndIsActiveTrue(chatRoomId)
|
||||
?: throw SodaException(messageKey = "chat.error.room_not_found")
|
||||
// 내 참여 여부 확인
|
||||
participantRepository.findByChatRoomAndMemberAndIsActiveTrue(room, member)
|
||||
?: throw SodaException(messageKey = "chat.room.quota.invalid_access")
|
||||
// 캐릭터 확인
|
||||
val characterParticipant = participantRepository
|
||||
.findByChatRoomAndParticipantTypeAndIsActiveTrue(room, ParticipantType.CHARACTER)
|
||||
?: throw SodaException(messageKey = "chat.room.quota.not_ai_room")
|
||||
val character = characterParticipant.character
|
||||
?: throw SodaException(messageKey = "chat.room.quota.not_ai_room")
|
||||
|
||||
// 글로벌 Lazy refill
|
||||
val globalStatus = chatQuotaService.getStatus(member.id!!)
|
||||
|
||||
// 룸 Lazy refill 상태
|
||||
val roomStatus = chatRoomQuotaService.applyRefillOnEnterAndGetStatus(
|
||||
memberId = member.id!!,
|
||||
chatRoomId = chatRoomId,
|
||||
@@ -136,4 +131,12 @@ class ChatRoomQuotaController(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveIsAdultAccessible(member: Member?): Boolean {
|
||||
if (member == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return memberContentPreferenceService.getStoredPreference(member).isAdult
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user