룰렛 방식 수정 #178
@@ -95,6 +95,18 @@ data class LiveRoomInfo(
 | 
				
			|||||||
        donationMessageList = donationMessageSet.toList()
 | 
					        donationMessageList = donationMessageSet.toList()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun addRouletteMessage(nickname: String, donationMessage: String) {
 | 
				
			||||||
 | 
					        val donationMessageSet = donationMessageList.toMutableSet()
 | 
				
			||||||
 | 
					        donationMessageSet.add(
 | 
				
			||||||
 | 
					            LiveRoomDonationMessage(
 | 
				
			||||||
 | 
					                nickname = nickname,
 | 
				
			||||||
 | 
					                canMessage = "",
 | 
				
			||||||
 | 
					                donationMessage = donationMessage
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        donationMessageList = donationMessageSet.toList()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun removeDonationMessage(uuid: String) {
 | 
					    fun removeDonationMessage(uuid: String) {
 | 
				
			||||||
        (donationMessageList as MutableList).removeIf { it.uuid == uuid }
 | 
					        (donationMessageList as MutableList).removeIf { it.uuid == uuid }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,3 +14,9 @@ data class NewRoulette(
 | 
				
			|||||||
    var isActive: Boolean,
 | 
					    var isActive: Boolean,
 | 
				
			||||||
    var items: List<RouletteItem> = mutableListOf()
 | 
					    var items: List<RouletteItem> = mutableListOf()
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class RouletteItem(
 | 
				
			||||||
 | 
					    val title: String,
 | 
				
			||||||
 | 
					    val weight: Int,
 | 
				
			||||||
 | 
					    val percentage: Float = 0.00F
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,6 @@ class NewRouletteService(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private val canRepository: CanRepository,
 | 
					    private val canRepository: CanRepository,
 | 
				
			||||||
    private val repository: NewRouletteRepository,
 | 
					    private val repository: NewRouletteRepository,
 | 
				
			||||||
    private val oldRepository: RouletteRepository,
 | 
					 | 
				
			||||||
    private val roomRepository: LiveRoomRepository,
 | 
					    private val roomRepository: LiveRoomRepository,
 | 
				
			||||||
    private val memberRepository: MemberRepository,
 | 
					    private val memberRepository: MemberRepository,
 | 
				
			||||||
    private val chargeRepository: ChargeRepository,
 | 
					    private val chargeRepository: ChargeRepository,
 | 
				
			||||||
@@ -36,27 +35,9 @@ class NewRouletteService(
 | 
				
			|||||||
    fun getAllRoulette(creatorId: Long, memberId: Long): List<GetNewRouletteResponse> {
 | 
					    fun getAllRoulette(creatorId: Long, memberId: Long): List<GetNewRouletteResponse> {
 | 
				
			||||||
        if (creatorId != memberId) throw SodaException("잘못된 요청입니다.")
 | 
					        if (creatorId != memberId) throw SodaException("잘못된 요청입니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var rouletteList = repository.findByCreatorId(creatorId)
 | 
					        val rouletteList = repository.findByCreatorId(creatorId)
 | 
				
			||||||
        if (rouletteList.isEmpty()) {
 | 
					 | 
				
			||||||
            val roulette = oldRepository.findByIdOrNull(creatorId)
 | 
					 | 
				
			||||||
            if (roulette != null) {
 | 
					 | 
				
			||||||
                repository.save(
 | 
					 | 
				
			||||||
                    NewRoulette(
 | 
					 | 
				
			||||||
                        id = idGenerator.generateId(SEQUENCE_NAME),
 | 
					 | 
				
			||||||
                        creatorId = creatorId,
 | 
					 | 
				
			||||||
                        can = roulette.can,
 | 
					 | 
				
			||||||
                        isActive = false,
 | 
					 | 
				
			||||||
                        items = roulette.items
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                rouletteList = repository.findByCreatorId(creatorId)
 | 
					 | 
				
			||||||
                oldRepository.deleteById(roulette.creatorId)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return rouletteList.sortedBy { it.id }
 | 
					        return rouletteList.sortedBy { it.id }
 | 
				
			||||||
            .asSequence()
 | 
					 | 
				
			||||||
            .map {
 | 
					            .map {
 | 
				
			||||||
                GetNewRouletteResponse(
 | 
					                GetNewRouletteResponse(
 | 
				
			||||||
                    it.id,
 | 
					                    it.id,
 | 
				
			||||||
@@ -65,7 +46,6 @@ class NewRouletteService(
 | 
				
			|||||||
                    it.items
 | 
					                    it.items
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .toList()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun createRoulette(memberId: Long, request: CreateNewRouletteRequest): Boolean {
 | 
					    fun createRoulette(memberId: Long, request: CreateNewRouletteRequest): Boolean {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.live.roulette
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.data.annotation.Id
 | 
					 | 
				
			||||||
import org.springframework.data.redis.core.RedisHash
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@RedisHash("roulette")
 | 
					 | 
				
			||||||
data class Roulette(
 | 
					 | 
				
			||||||
    @Id
 | 
					 | 
				
			||||||
    val creatorId: Long,
 | 
					 | 
				
			||||||
    var can: Int,
 | 
					 | 
				
			||||||
    var isActive: Boolean,
 | 
					 | 
				
			||||||
    var items: List<RouletteItem> = mutableListOf()
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
data class RouletteItem(
 | 
					 | 
				
			||||||
    val title: String,
 | 
					 | 
				
			||||||
    val weight: Int
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.live.roulette
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.data.repository.CrudRepository
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Repository
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Repository
 | 
					 | 
				
			||||||
interface RouletteRepository : CrudRepository<Roulette, Long>
 | 
					 | 
				
			||||||
@@ -1,147 +0,0 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.live.roulette
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.CanRepository
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.charge.Charge
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.Payment
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.common.SodaException
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.member.Member
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.member.MemberRepository
 | 
					 | 
				
			||||||
import kr.co.vividnext.sodalive.member.MemberRole
 | 
					 | 
				
			||||||
import org.springframework.data.repository.findByIdOrNull
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Service
 | 
					 | 
				
			||||||
import org.springframework.transaction.annotation.Transactional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Service
 | 
					 | 
				
			||||||
class RouletteService(
 | 
					 | 
				
			||||||
    private val canPaymentService: CanPaymentService,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val canRepository: CanRepository,
 | 
					 | 
				
			||||||
    private val repository: RouletteRepository,
 | 
					 | 
				
			||||||
    private val chargeRepository: ChargeRepository,
 | 
					 | 
				
			||||||
    private val roomRepository: LiveRoomRepository,
 | 
					 | 
				
			||||||
    private val memberRepository: MemberRepository,
 | 
					 | 
				
			||||||
    private val useCanCalculateRepository: UseCanCalculateRepository
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    fun createOrUpdateRoulette(memberId: Long, request: CreateOrUpdateRouletteRequest): Boolean {
 | 
					 | 
				
			||||||
        rouletteValidate(request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var roulette = repository.findByIdOrNull(id = memberId)
 | 
					 | 
				
			||||||
        if (roulette != null) {
 | 
					 | 
				
			||||||
            roulette.can = request.can
 | 
					 | 
				
			||||||
            roulette.isActive = request.isActive
 | 
					 | 
				
			||||||
            roulette.items = request.items
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            roulette = Roulette(
 | 
					 | 
				
			||||||
                creatorId = memberId,
 | 
					 | 
				
			||||||
                can = request.can,
 | 
					 | 
				
			||||||
                isActive = request.isActive,
 | 
					 | 
				
			||||||
                items = request.items
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        repository.save(roulette)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return request.isActive
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun rouletteValidate(request: CreateOrUpdateRouletteRequest) {
 | 
					 | 
				
			||||||
        if (request.can < 5) {
 | 
					 | 
				
			||||||
            throw SodaException("룰렛 금액은 최소 5캔 입니다.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (request.items.size < 2 || request.items.size > 10) {
 | 
					 | 
				
			||||||
            throw SodaException("룰렛 옵션은 최소 2개, 최대 10개까지 설정할 수 있습니다.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun getRoulette(creatorId: Long, memberId: Long): GetRouletteResponse? {
 | 
					 | 
				
			||||||
        val roulette = repository.findByIdOrNull(id = creatorId)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (creatorId != memberId && (roulette == null || !roulette.isActive || roulette.items.isEmpty())) {
 | 
					 | 
				
			||||||
            throw SodaException("룰렛을 사용할 수 없습니다.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return GetRouletteResponse(
 | 
					 | 
				
			||||||
            can = roulette?.can ?: 0,
 | 
					 | 
				
			||||||
            isActive = roulette?.isActive ?: false,
 | 
					 | 
				
			||||||
            items = roulette?.items ?: listOf()
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Transactional
 | 
					 | 
				
			||||||
    fun spinRoulette(request: SpinRouletteRequest, memberId: Long): GetRouletteResponse {
 | 
					 | 
				
			||||||
        // STEP 1 - 라이브 정보 가져오기
 | 
					 | 
				
			||||||
        val room = roomRepository.findByIdOrNull(request.roomId)
 | 
					 | 
				
			||||||
            ?: throw SodaException("해당하는 라이브가 없습니다.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val host = room.member ?: throw SodaException("잘못된 요청입니다.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (host.role != MemberRole.CREATOR) {
 | 
					 | 
				
			||||||
            throw SodaException("비비드넥스트와 계약한\n크리에이터의 룰렛만 사용하실 수 있습니다.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // STEP 2 - 룰렛 데이터 가져오기
 | 
					 | 
				
			||||||
        val roulette = repository.findByIdOrNull(id = host.id!!)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (roulette == null || !roulette.isActive || roulette.items.isEmpty()) {
 | 
					 | 
				
			||||||
            throw SodaException("룰렛을 사용할 수 없습니다.")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // STEP 3 - 캔 사용
 | 
					 | 
				
			||||||
        canPaymentService.spendCan(
 | 
					 | 
				
			||||||
            memberId = memberId,
 | 
					 | 
				
			||||||
            needCan = roulette.can,
 | 
					 | 
				
			||||||
            canUsage = CanUsage.SPIN_ROULETTE,
 | 
					 | 
				
			||||||
            liveRoom = room,
 | 
					 | 
				
			||||||
            container = request.container
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return GetRouletteResponse(can = roulette.can, isActive = roulette.isActive, items = roulette.items)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Transactional
 | 
					 | 
				
			||||||
    fun refundDonation(roomId: Long, member: Member) {
 | 
					 | 
				
			||||||
        val donator = memberRepository.findByIdOrNull(member.id)
 | 
					 | 
				
			||||||
            ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
 | 
					 | 
				
			||||||
            memberId = member.id!!,
 | 
					 | 
				
			||||||
            roomId = roomId,
 | 
					 | 
				
			||||||
            canUsage = CanUsage.SPIN_ROULETTE
 | 
					 | 
				
			||||||
        ) ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
 | 
					 | 
				
			||||||
        useCan.isRefund = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
 | 
					 | 
				
			||||||
        useCanCalculates.forEach {
 | 
					 | 
				
			||||||
            it.status = UseCanCalculateStatus.REFUND
 | 
					 | 
				
			||||||
            val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
 | 
					 | 
				
			||||||
            charge.title = "${it.can} 캔"
 | 
					 | 
				
			||||||
            charge.useCan = useCan
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            when (it.paymentGateway) {
 | 
					 | 
				
			||||||
                PaymentGateway.GOOGLE_IAP -> donator.googleRewardCan += charge.rewardCan
 | 
					 | 
				
			||||||
                PaymentGateway.APPLE_IAP -> donator.appleRewardCan += charge.rewardCan
 | 
					 | 
				
			||||||
                else -> donator.pgRewardCan += charge.rewardCan
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            charge.member = donator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            val payment = Payment(
 | 
					 | 
				
			||||||
                status = PaymentStatus.COMPLETE,
 | 
					 | 
				
			||||||
                paymentGateway = it.paymentGateway
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            payment.method = "룰렛 환불"
 | 
					 | 
				
			||||||
            charge.payment = payment
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            chargeRepository.save(charge)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RouletteItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class CreateRouletteRequestV2(
 | 
				
			||||||
 | 
					    val can: Int,
 | 
				
			||||||
 | 
					    val isActive: Boolean,
 | 
				
			||||||
 | 
					    val items: List<RouletteItem>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RouletteItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class GetRouletteResponseV2(
 | 
				
			||||||
 | 
					    val id: Long,
 | 
				
			||||||
 | 
					    val can: Int,
 | 
				
			||||||
 | 
					    val isActive: Boolean,
 | 
				
			||||||
 | 
					    val items: List<RouletteItem>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,51 +1,78 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.live.roulette
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.ApiResponse
 | 
					import kr.co.vividnext.sodalive.common.ApiResponse
 | 
				
			||||||
import kr.co.vividnext.sodalive.common.SodaException
 | 
					import kr.co.vividnext.sodalive.common.SodaException
 | 
				
			||||||
import kr.co.vividnext.sodalive.member.Member
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
import kr.co.vividnext.sodalive.member.MemberRole
 | 
					import kr.co.vividnext.sodalive.member.MemberRole
 | 
				
			||||||
 | 
					import org.springframework.security.access.prepost.PreAuthorize
 | 
				
			||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
 | 
					import org.springframework.security.core.annotation.AuthenticationPrincipal
 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping
 | 
					import org.springframework.web.bind.annotation.GetMapping
 | 
				
			||||||
import org.springframework.web.bind.annotation.PathVariable
 | 
					import org.springframework.web.bind.annotation.PathVariable
 | 
				
			||||||
import org.springframework.web.bind.annotation.PostMapping
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PutMapping
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestBody
 | 
					import org.springframework.web.bind.annotation.RequestBody
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestParam
 | 
					import org.springframework.web.bind.annotation.RequestParam
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
@RequestMapping("/roulette")
 | 
					@RequestMapping("/v2/roulette")
 | 
				
			||||||
class RouletteController(private val service: RouletteService) {
 | 
					class RouletteController(private val service: RouletteService) {
 | 
				
			||||||
 | 
					    @GetMapping("/creator")
 | 
				
			||||||
 | 
					    @PreAuthorize("hasRole('CREATOR')")
 | 
				
			||||||
 | 
					    fun getAllRoulette(
 | 
				
			||||||
 | 
					        @RequestParam creatorId: Long,
 | 
				
			||||||
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
 | 
					    ) = run {
 | 
				
			||||||
 | 
					        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApiResponse.ok(service.getAllRoulette(creatorId = creatorId, memberId = member.id!!))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostMapping
 | 
					    @PostMapping
 | 
				
			||||||
    fun createOrUpdateRoulette(
 | 
					    @PreAuthorize("hasRole('CREATOR')")
 | 
				
			||||||
        @RequestBody request: CreateOrUpdateRouletteRequest,
 | 
					    fun createNewRoulette(
 | 
				
			||||||
 | 
					        @RequestBody request: CreateRouletteRequestV2,
 | 
				
			||||||
        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
    ) = run {
 | 
					    ) = run {
 | 
				
			||||||
        if (member == null || member.role != MemberRole.CREATOR) {
 | 
					        if (member == null || member.role != MemberRole.CREATOR) {
 | 
				
			||||||
            throw SodaException("로그인 정보를 확인해주세요.")
 | 
					            throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ApiResponse.ok(service.createOrUpdateRoulette(memberId = member.id!!, request = request))
 | 
					        ApiResponse.ok(service.createRoulette(memberId = member.id!!, request = request))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @PutMapping
 | 
				
			||||||
 | 
					    @PreAuthorize("hasRole('CREATOR')")
 | 
				
			||||||
 | 
					    fun updateNewRoulette(
 | 
				
			||||||
 | 
					        @RequestBody request: UpdateRouletteRequestV2,
 | 
				
			||||||
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
 | 
					    ) = run {
 | 
				
			||||||
 | 
					        if (member == null || member.role != MemberRole.CREATOR) {
 | 
				
			||||||
 | 
					            throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApiResponse.ok(service.updateRoulette(memberId = member.id!!, request = request))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping
 | 
				
			||||||
    fun getRoulette(
 | 
					    fun getRoulette(
 | 
				
			||||||
        @RequestParam creatorId: Long,
 | 
					        @RequestParam creatorId: Long,
 | 
				
			||||||
        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
    ): ApiResponse<GetRouletteResponse> {
 | 
					    ) = run {
 | 
				
			||||||
        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
					        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!))
 | 
					        ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostMapping("/spin")
 | 
					    @PostMapping("/spin")
 | 
				
			||||||
    fun spinRoulette(
 | 
					    fun spinRoulette(
 | 
				
			||||||
        @RequestBody request: SpinRouletteRequest,
 | 
					        @RequestBody request: SpinRouletteRequestV2,
 | 
				
			||||||
        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
					        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
 | 
				
			||||||
    ) = run {
 | 
					    ) = run {
 | 
				
			||||||
        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
					        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ApiResponse.ok(service.spinRoulette(request = request, memberId = member.id!!))
 | 
					        ApiResponse.ok(service.spinRoulette(request = request, member = member))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostMapping("/refund/{id}")
 | 
					    @PostMapping("/refund/{id}")
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.NewRoulette
 | 
				
			||||||
 | 
					import org.springframework.data.repository.CrudRepository
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Repository
 | 
				
			||||||
 | 
					interface RouletteRepository : CrudRepository<NewRoulette, Long> {
 | 
				
			||||||
 | 
					    fun findByCreatorId(creatorId: Long): List<NewRoulette>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,252 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.CanRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.Charge
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.ChargeRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.charge.ChargeStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.CanPaymentService
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.Payment
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.use.CanUsage
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.common.SodaException
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.NewRoulette
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RedisIdGenerator
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RouletteItem
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.Member
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.MemberRepository
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.member.MemberRole
 | 
				
			||||||
 | 
					import org.springframework.data.repository.findByIdOrNull
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					import org.springframework.transaction.annotation.Transactional
 | 
				
			||||||
 | 
					import java.util.concurrent.locks.ReentrantReadWriteLock
 | 
				
			||||||
 | 
					import kotlin.concurrent.write
 | 
				
			||||||
 | 
					import kotlin.random.Random
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class RouletteService(
 | 
				
			||||||
 | 
					    private val idGenerator: RedisIdGenerator,
 | 
				
			||||||
 | 
					    private val canPaymentService: CanPaymentService,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val canRepository: CanRepository,
 | 
				
			||||||
 | 
					    private val repository: RouletteRepository,
 | 
				
			||||||
 | 
					    private val roomRepository: LiveRoomRepository,
 | 
				
			||||||
 | 
					    private val memberRepository: MemberRepository,
 | 
				
			||||||
 | 
					    private val chargeRepository: ChargeRepository,
 | 
				
			||||||
 | 
					    private val roomInfoRepository: LiveRoomInfoRedisRepository,
 | 
				
			||||||
 | 
					    private val useCanCalculateRepository: UseCanCalculateRepository
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    private val tokenLocks: MutableMap<Long, ReentrantReadWriteLock> = mutableMapOf()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getAllRoulette(creatorId: Long, memberId: Long): List<GetRouletteResponseV2> {
 | 
				
			||||||
 | 
					        if (creatorId != memberId) throw SodaException("잘못된 요청입니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return repository.findByCreatorId(creatorId)
 | 
				
			||||||
 | 
					            .sortedBy { it.id }
 | 
				
			||||||
 | 
					            .map {
 | 
				
			||||||
 | 
					                GetRouletteResponseV2(
 | 
				
			||||||
 | 
					                    id = it.id,
 | 
				
			||||||
 | 
					                    can = it.can,
 | 
				
			||||||
 | 
					                    isActive = it.isActive,
 | 
				
			||||||
 | 
					                    items = it.items
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun createRoulette(memberId: Long, request: CreateRouletteRequestV2): Boolean {
 | 
				
			||||||
 | 
					        rouletteValidate(can = request.can, items = request.items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val roulette = NewRoulette(
 | 
				
			||||||
 | 
					            id = idGenerator.generateId(SEQUENCE_NAME),
 | 
				
			||||||
 | 
					            creatorId = memberId,
 | 
				
			||||||
 | 
					            can = request.can,
 | 
				
			||||||
 | 
					            isActive = request.isActive,
 | 
				
			||||||
 | 
					            items = request.items
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        repository.save(roulette)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var activeRoulette = false
 | 
				
			||||||
 | 
					        val rouletteList = repository.findByCreatorId(creatorId = memberId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rouletteList.forEach {
 | 
				
			||||||
 | 
					            if (request.isActive || it.isActive) {
 | 
				
			||||||
 | 
					                activeRoulette = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return activeRoulette
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun updateRoulette(memberId: Long, request: UpdateRouletteRequestV2): Boolean {
 | 
				
			||||||
 | 
					        rouletteValidate(can = request.can, items = request.items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val rouletteList = repository.findByCreatorId(creatorId = memberId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (rouletteList.isEmpty()) {
 | 
				
			||||||
 | 
					            throw SodaException("잘못된 요청입니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var activeRoulette = false
 | 
				
			||||||
 | 
					        rouletteList.forEach {
 | 
				
			||||||
 | 
					            if (request.isActive || it.isActive) {
 | 
				
			||||||
 | 
					                activeRoulette = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (it.id == request.id) {
 | 
				
			||||||
 | 
					                it.can = request.can
 | 
				
			||||||
 | 
					                it.items = request.items
 | 
				
			||||||
 | 
					                it.isActive = request.isActive
 | 
				
			||||||
 | 
					                repository.save(it)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return activeRoulette
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun getRoulette(creatorId: Long, memberId: Long): List<GetRouletteResponseV2> {
 | 
				
			||||||
 | 
					        val rouletteList = repository.findByCreatorId(creatorId = creatorId)
 | 
				
			||||||
 | 
					            .filter { it.isActive }
 | 
				
			||||||
 | 
					            .filter { it.items.isNotEmpty() }
 | 
				
			||||||
 | 
					            .map { GetRouletteResponseV2(it.id, it.can, it.isActive, it.items) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (rouletteList.isEmpty()) {
 | 
				
			||||||
 | 
					            throw SodaException("룰렛을 사용할 수 없습니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return rouletteList
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Transactional
 | 
				
			||||||
 | 
					    fun spinRoulette(request: SpinRouletteRequestV2, member: Member): SpinRouletteResponse {
 | 
				
			||||||
 | 
					        // STEP 1 - 라이브 정보 가져오기
 | 
				
			||||||
 | 
					        val room = roomRepository.findByIdOrNull(request.roomId)
 | 
				
			||||||
 | 
					            ?: throw SodaException("해당하는 라이브가 없습니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val host = room.member ?: throw SodaException("잘못된 요청입니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (host.role != MemberRole.CREATOR) {
 | 
				
			||||||
 | 
					            throw SodaException("비비드넥스트와 계약한\n크리에이터의 룰렛만 사용하실 수 있습니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // STEP 2 - 룰렛 데이터 가져오기
 | 
				
			||||||
 | 
					        val roulette = repository.findByIdOrNull(id = request.rouletteId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (roulette == null || roulette.items.isEmpty()) {
 | 
				
			||||||
 | 
					            throw SodaException("룰렛을 사용할 수 없습니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // STEP 3 - 캔 사용
 | 
				
			||||||
 | 
					        canPaymentService.spendCan(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            needCan = roulette.can,
 | 
				
			||||||
 | 
					            canUsage = CanUsage.SPIN_ROULETTE,
 | 
				
			||||||
 | 
					            liveRoom = room,
 | 
				
			||||||
 | 
					            container = request.container
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val itemList = roulette.items
 | 
				
			||||||
 | 
					        val result = randomSelectRouletteItem(itemList = itemList)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val lock = getOrCreateLock(memberId = member.id!!)
 | 
				
			||||||
 | 
					        lock.write {
 | 
				
			||||||
 | 
					            val roomInfo = roomInfoRepository.findByIdOrNull(room.id!!)
 | 
				
			||||||
 | 
					                ?: throw SodaException("해당하는 라이브의 정보가 없습니다.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            roomInfo.addRouletteMessage(
 | 
				
			||||||
 | 
					                nickname = member.nickname,
 | 
				
			||||||
 | 
					                donationMessage = "[$result] 당첨!"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            roomInfoRepository.save(roomInfo)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return SpinRouletteResponse(can = roulette.can, result = result, items = itemList)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Transactional
 | 
				
			||||||
 | 
					    fun refundDonation(roomId: Long, member: Member) {
 | 
				
			||||||
 | 
					        val donator = memberRepository.findByIdOrNull(member.id)
 | 
				
			||||||
 | 
					            ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
 | 
				
			||||||
 | 
					            memberId = member.id!!,
 | 
				
			||||||
 | 
					            roomId = roomId,
 | 
				
			||||||
 | 
					            canUsage = CanUsage.SPIN_ROULETTE
 | 
				
			||||||
 | 
					        ) ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
 | 
				
			||||||
 | 
					        useCan.isRefund = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
 | 
				
			||||||
 | 
					        useCanCalculates.forEach {
 | 
				
			||||||
 | 
					            it.status = UseCanCalculateStatus.REFUND
 | 
				
			||||||
 | 
					            val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
 | 
				
			||||||
 | 
					            charge.title = "${it.can} 캔"
 | 
				
			||||||
 | 
					            charge.useCan = useCan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            when (it.paymentGateway) {
 | 
				
			||||||
 | 
					                PaymentGateway.GOOGLE_IAP -> donator.googleRewardCan += charge.rewardCan
 | 
				
			||||||
 | 
					                PaymentGateway.APPLE_IAP -> donator.appleRewardCan += charge.rewardCan
 | 
				
			||||||
 | 
					                else -> donator.pgRewardCan += charge.rewardCan
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            charge.member = donator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val payment = Payment(
 | 
				
			||||||
 | 
					                status = PaymentStatus.COMPLETE,
 | 
				
			||||||
 | 
					                paymentGateway = it.paymentGateway
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            payment.method = "룰렛 환불"
 | 
				
			||||||
 | 
					            charge.payment = payment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            chargeRepository.save(charge)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun randomSelectRouletteItem(itemList: List<RouletteItem>): String {
 | 
				
			||||||
 | 
					        val cumulativeProbabilities = calculateCumulativeProbabilities(itemList)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val randomValue = Random.nextFloat()
 | 
				
			||||||
 | 
					        for (i in itemList.indices) {
 | 
				
			||||||
 | 
					            if (randomValue <= cumulativeProbabilities[i]) {
 | 
				
			||||||
 | 
					                return itemList[i].title
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return itemList.random().title
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun calculateCumulativeProbabilities(items: List<RouletteItem>): List<Float> {
 | 
				
			||||||
 | 
					        val totalPercent = items.map { it.percentage }.sum()
 | 
				
			||||||
 | 
					        var cumulativeProbability = 0f
 | 
				
			||||||
 | 
					        return items.map {
 | 
				
			||||||
 | 
					            cumulativeProbability += it.percentage / totalPercent
 | 
				
			||||||
 | 
					            cumulativeProbability
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun rouletteValidate(can: Int, items: List<RouletteItem>) {
 | 
				
			||||||
 | 
					        if (can < 5) {
 | 
				
			||||||
 | 
					            throw SodaException("룰렛 금액은 최소 5캔 입니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (items.size < 2 || items.size > 10) {
 | 
				
			||||||
 | 
					            throw SodaException("룰렛 옵션은 최소 2개, 최대 10개까지 설정할 수 있습니다.")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val totalPercentage = items.map { it.percentage }.sum()
 | 
				
			||||||
 | 
					        if (totalPercentage != 100f) {
 | 
				
			||||||
 | 
					            throw SodaException("확률이 100%가 아닙니다")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getOrCreateLock(memberId: Long): ReentrantReadWriteLock {
 | 
				
			||||||
 | 
					        return tokenLocks.computeIfAbsent(memberId) { ReentrantReadWriteLock() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        const val SEQUENCE_NAME = "newRoulette:sequence"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class SpinRouletteRequestV2(
 | 
				
			||||||
 | 
					    val roomId: Long,
 | 
				
			||||||
 | 
					    val rouletteId: Long,
 | 
				
			||||||
 | 
					    val container: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RouletteItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class SpinRouletteResponse(
 | 
				
			||||||
 | 
					    val can: Int,
 | 
				
			||||||
 | 
					    val result: String,
 | 
				
			||||||
 | 
					    val items: List<RouletteItem>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package kr.co.vividnext.sodalive.live.roulette.v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kr.co.vividnext.sodalive.live.roulette.RouletteItem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class UpdateRouletteRequestV2(
 | 
				
			||||||
 | 
					    val id: Long,
 | 
				
			||||||
 | 
					    val can: Int,
 | 
				
			||||||
 | 
					    val isActive: Boolean,
 | 
				
			||||||
 | 
					    val items: List<RouletteItem>
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
		Reference in New Issue
	
	Block a user