Compare commits
2 Commits
9545ab0789
...
f393c7630e
Author | SHA1 | Date |
---|---|---|
|
f393c7630e | |
|
197cca1f1b |
|
@ -27,6 +27,7 @@ interface CanQueryRepository {
|
|||
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
||||
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
||||
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
||||
fun getCanUsedForLiveRoomNotRefund(memberId: Long, roomId: Long): UseCan?
|
||||
}
|
||||
|
||||
@Repository
|
||||
|
@ -111,4 +112,19 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||
.orderBy(useCan.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun getCanUsedForLiveRoomNotRefund(memberId: Long, roomId: Long): UseCan? {
|
||||
return queryFactory
|
||||
.selectFrom(useCan)
|
||||
.innerJoin(useCan.member, member)
|
||||
.innerJoin(useCan.room, liveRoom)
|
||||
.where(
|
||||
member.id.eq(memberId)
|
||||
.and(liveRoom.id.eq(roomId))
|
||||
.and(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
.and(useCan.isRefund.isFalse)
|
||||
)
|
||||
.orderBy(useCan.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,78 @@
|
|||
package kr.co.vividnext.sodalive.can.charge
|
||||
|
||||
import com.querydsl.core.types.dsl.BooleanExpression
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.charge.QCharge.charge
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface ChargeRepository : JpaRepository<Charge, Long>
|
||||
interface ChargeRepository : JpaRepository<Charge, Long>, ChargeQueryRepository
|
||||
|
||||
interface ChargeQueryRepository {
|
||||
fun getOldestChargeWhereRewardCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
||||
fun getOldestChargeWhereChargeCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
|
||||
}
|
||||
|
||||
class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
|
||||
override fun getOldestChargeWhereRewardCanGreaterThan0(
|
||||
chargeId: Long,
|
||||
memberId: Long,
|
||||
container: String
|
||||
): Charge? {
|
||||
return queryFactory
|
||||
.selectFrom(charge)
|
||||
.innerJoin(charge.member, member)
|
||||
.leftJoin(charge.payment, payment)
|
||||
.where(
|
||||
member.id.eq(memberId)
|
||||
.and(charge.rewardCan.gt(0))
|
||||
.and(charge.id.gt(chargeId))
|
||||
.and(payment.status.eq(PaymentStatus.COMPLETE))
|
||||
.and(getPaymentGatewayCondition(container))
|
||||
)
|
||||
.orderBy(charge.id.asc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun getOldestChargeWhereChargeCanGreaterThan0(
|
||||
chargeId: Long,
|
||||
memberId: Long,
|
||||
container: String
|
||||
): Charge? {
|
||||
return queryFactory
|
||||
.selectFrom(charge)
|
||||
.innerJoin(charge.member, member)
|
||||
.leftJoin(charge.payment, payment)
|
||||
.where(
|
||||
member.id.eq(memberId)
|
||||
.and(charge.chargeCan.gt(0))
|
||||
.and(charge.id.gt(chargeId))
|
||||
.and(payment.status.eq(PaymentStatus.COMPLETE))
|
||||
.and(getPaymentGatewayCondition(container))
|
||||
)
|
||||
.orderBy(charge.id.asc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
|
||||
val paymentGatewayCondition = when (container) {
|
||||
"aos" -> {
|
||||
payment.paymentGateway.eq(PaymentGateway.PG)
|
||||
.or(payment.paymentGateway.eq(PaymentGateway.GOOGLE_IAP))
|
||||
}
|
||||
|
||||
"ios" -> {
|
||||
payment.paymentGateway.eq(PaymentGateway.PG)
|
||||
.or(payment.paymentGateway.eq(PaymentGateway.APPLE_IAP))
|
||||
}
|
||||
|
||||
else -> payment.paymentGateway.eq(PaymentGateway.PG)
|
||||
}
|
||||
return paymentGatewayCondition
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
package kr.co.vividnext.sodalive.can.payment
|
||||
|
||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.can.use.SpentCan
|
||||
import kr.co.vividnext.sodalive.can.use.TotalSpentCan
|
||||
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculate
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus
|
||||
import kr.co.vividnext.sodalive.can.use.UseCanRepository
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
class CanPaymentService(
|
||||
private val memberRepository: MemberRepository,
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val useCanRepository: UseCanRepository,
|
||||
private val useCanCalculateRepository: UseCanCalculateRepository
|
||||
) {
|
||||
@Transactional
|
||||
fun spendCan(
|
||||
memberId: Long,
|
||||
needCan: Int,
|
||||
canUsage: CanUsage,
|
||||
liveRoom: LiveRoom? = null,
|
||||
container: String
|
||||
) {
|
||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
||||
val useRewardCan = spendRewardCan(memberId, needCan, container)
|
||||
val useChargeCan = if (needCan - useRewardCan.total > 0) {
|
||||
spendChargeCan(memberId, needCan = needCan - useRewardCan.total, container = container)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
|
||||
throw SodaException(
|
||||
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " +
|
||||
"캔이 부족합니다. 충전 후 이용해 주세요."
|
||||
)
|
||||
}
|
||||
|
||||
if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
|
||||
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
||||
}
|
||||
|
||||
val useCan = UseCan(
|
||||
canUsage = canUsage,
|
||||
can = useChargeCan?.total ?: 0,
|
||||
rewardCan = useRewardCan.total
|
||||
)
|
||||
|
||||
var recipientId: Long? = null
|
||||
if (canUsage == CanUsage.LIVE && liveRoom != null) {
|
||||
recipientId = liveRoom.member!!.id!!
|
||||
useCan.room = liveRoom
|
||||
useCan.member = member
|
||||
} else if (canUsage == CanUsage.CHANGE_NICKNAME) {
|
||||
useCan.member = member
|
||||
} else if (canUsage == CanUsage.DONATION && liveRoom != null) {
|
||||
recipientId = liveRoom.member!!.id!!
|
||||
useCan.room = liveRoom
|
||||
useCan.member = member
|
||||
} else {
|
||||
throw SodaException("잘못된 요청입니다.")
|
||||
}
|
||||
|
||||
useCanRepository.save(useCan)
|
||||
|
||||
setUseCoinCalculate(recipientId, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.PG)
|
||||
setUseCoinCalculate(
|
||||
recipientId,
|
||||
useRewardCan,
|
||||
useChargeCan,
|
||||
useCan,
|
||||
paymentGateway = PaymentGateway.GOOGLE_IAP
|
||||
)
|
||||
setUseCoinCalculate(
|
||||
recipientId,
|
||||
useRewardCan,
|
||||
useChargeCan,
|
||||
useCan,
|
||||
paymentGateway = PaymentGateway.APPLE_IAP
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUseCoinCalculate(
|
||||
recipientId: Long?,
|
||||
useRewardCan: TotalSpentCan,
|
||||
useChargeCan: TotalSpentCan?,
|
||||
useCan: UseCan,
|
||||
paymentGateway: PaymentGateway
|
||||
) {
|
||||
val totalSpentRewardCan = useRewardCan.spentCans
|
||||
.filter { it.paymentGateway == paymentGateway }
|
||||
.fold(0) { sum, spentCans -> sum + spentCans.can }
|
||||
|
||||
val useCanCalculate = if (useChargeCan != null) {
|
||||
val totalSpentChargeCan = useChargeCan.spentCans
|
||||
.filter { it.paymentGateway == paymentGateway }
|
||||
.fold(0) { sum, spentCans -> sum + spentCans.can }
|
||||
|
||||
UseCanCalculate(
|
||||
can = totalSpentChargeCan + totalSpentRewardCan,
|
||||
paymentGateway = paymentGateway,
|
||||
status = UseCanCalculateStatus.RECEIVED
|
||||
)
|
||||
} else {
|
||||
UseCanCalculate(
|
||||
can = totalSpentRewardCan,
|
||||
paymentGateway = paymentGateway,
|
||||
status = UseCanCalculateStatus.RECEIVED
|
||||
)
|
||||
}
|
||||
|
||||
if (useCanCalculate.can > 0) {
|
||||
useCanCalculate.useCan = useCan
|
||||
useCanCalculate.recipientCreatorId = recipientId
|
||||
useCanCalculateRepository.save(useCanCalculate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spendRewardCan(memberId: Long, needCan: Int, container: String): TotalSpentCan {
|
||||
return if (needCan > 0) {
|
||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
||||
|
||||
val spentCans = mutableListOf<SpentCan>()
|
||||
var chargeId = 0L
|
||||
var total = 0
|
||||
|
||||
while (needCan - total > 0) {
|
||||
val remainingNeedCan = needCan - total
|
||||
val charge = chargeRepository.getOldestChargeWhereRewardCanGreaterThan0(chargeId, memberId, container)
|
||||
?: break
|
||||
|
||||
if (charge.rewardCan >= remainingNeedCan) {
|
||||
charge.rewardCan -= remainingNeedCan
|
||||
|
||||
when (charge.payment!!.paymentGateway) {
|
||||
PaymentGateway.PG -> member.pgRewardCan -= remainingNeedCan
|
||||
PaymentGateway.APPLE_IAP -> member.appleRewardCan -= remainingNeedCan
|
||||
PaymentGateway.GOOGLE_IAP -> member.pgRewardCan -= remainingNeedCan
|
||||
}
|
||||
|
||||
total += remainingNeedCan
|
||||
|
||||
spentCans.add(
|
||||
SpentCan(
|
||||
paymentGateway = charge.payment!!.paymentGateway,
|
||||
can = remainingNeedCan
|
||||
)
|
||||
)
|
||||
} else {
|
||||
total += charge.rewardCan
|
||||
spentCans.add(
|
||||
SpentCan(
|
||||
paymentGateway = charge.payment!!.paymentGateway,
|
||||
can = charge.rewardCan
|
||||
)
|
||||
)
|
||||
|
||||
when (charge.payment!!.paymentGateway) {
|
||||
PaymentGateway.PG -> member.pgRewardCan -= remainingNeedCan
|
||||
PaymentGateway.APPLE_IAP -> member.appleRewardCan -= remainingNeedCan
|
||||
PaymentGateway.GOOGLE_IAP -> member.pgRewardCan -= remainingNeedCan
|
||||
}
|
||||
|
||||
charge.rewardCan = 0
|
||||
}
|
||||
|
||||
chargeId = charge.id!!
|
||||
}
|
||||
|
||||
TotalSpentCan(spentCans, total)
|
||||
} else {
|
||||
TotalSpentCan(total = 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spendChargeCan(memberId: Long, needCan: Int, container: String): TotalSpentCan {
|
||||
return if (needCan > 0) {
|
||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
||||
|
||||
val spentCans = mutableListOf<SpentCan>()
|
||||
var chargeId = 0L
|
||||
var total = 0
|
||||
|
||||
while (needCan - total > 0) {
|
||||
val remainingNeedCan = needCan - total
|
||||
val charge = chargeRepository.getOldestChargeWhereChargeCanGreaterThan0(chargeId, memberId, container)
|
||||
?: break
|
||||
|
||||
if (charge.rewardCan >= remainingNeedCan) {
|
||||
charge.rewardCan -= remainingNeedCan
|
||||
|
||||
when (charge.payment!!.paymentGateway) {
|
||||
PaymentGateway.PG -> member.pgRewardCan -= remainingNeedCan
|
||||
PaymentGateway.APPLE_IAP -> member.appleRewardCan -= remainingNeedCan
|
||||
PaymentGateway.GOOGLE_IAP -> member.pgRewardCan -= remainingNeedCan
|
||||
}
|
||||
|
||||
total += remainingNeedCan
|
||||
|
||||
spentCans.add(
|
||||
SpentCan(
|
||||
paymentGateway = charge.payment!!.paymentGateway,
|
||||
can = remainingNeedCan
|
||||
)
|
||||
)
|
||||
} else {
|
||||
total += charge.rewardCan
|
||||
spentCans.add(
|
||||
SpentCan(
|
||||
paymentGateway = charge.payment!!.paymentGateway,
|
||||
can = charge.rewardCan
|
||||
)
|
||||
)
|
||||
|
||||
when (charge.payment!!.paymentGateway) {
|
||||
PaymentGateway.PG -> member.pgRewardCan -= remainingNeedCan
|
||||
PaymentGateway.APPLE_IAP -> member.appleRewardCan -= remainingNeedCan
|
||||
PaymentGateway.GOOGLE_IAP -> member.pgRewardCan -= remainingNeedCan
|
||||
}
|
||||
|
||||
charge.rewardCan = 0
|
||||
}
|
||||
|
||||
chargeId = charge.id!!
|
||||
}
|
||||
|
||||
TotalSpentCan(spentCans, total)
|
||||
} else {
|
||||
TotalSpentCan(total = 0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package kr.co.vividnext.sodalive.can.use
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||
|
||||
data class TotalSpentCan(
|
||||
val spentCans: MutableList<SpentCan> = mutableListOf(),
|
||||
val total: Int
|
||||
) {
|
||||
fun verify(): Boolean {
|
||||
val sumSpentCans = spentCans.fold(0) { sum, spentCan -> sum + spentCan.can }
|
||||
return total == sumSpentCans
|
||||
}
|
||||
}
|
||||
|
||||
data class SpentCan(
|
||||
val paymentGateway: PaymentGateway,
|
||||
val can: Int
|
||||
)
|
|
@ -30,6 +30,8 @@ data class UseCanCalculate(
|
|||
value?.useCanCalculates?.add(this)
|
||||
field = value
|
||||
}
|
||||
|
||||
var recipientCreatorId: Long? = null
|
||||
}
|
||||
|
||||
enum class UseCanCalculateStatus {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.can.use
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface UseCanCalculateRepository : JpaRepository<UseCanCalculate, Long> {
|
||||
fun findByUseCanIdAndStatus(
|
||||
useCanId: Long,
|
||||
status: UseCanCalculateStatus = UseCanCalculateStatus.RECEIVED
|
||||
): List<UseCanCalculate>
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.can.use
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface UseCanRepository : JpaRepository<UseCan, Long>
|
|
@ -0,0 +1,24 @@
|
|||
package kr.co.vividnext.sodalive.live.reservation
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/live/reservation")
|
||||
class LiveReservationController(private val service: LiveReservationService) {
|
||||
@PostMapping
|
||||
fun makeReservation(
|
||||
@RequestBody request: MakeLiveReservationRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.makeReservation(request, member.id!!))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package kr.co.vividnext.sodalive.live.reservation
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface LiveReservationRepository : JpaRepository<LiveReservation, Long>, LiveReservationQueryRepository
|
||||
|
||||
interface LiveReservationQueryRepository {
|
||||
fun getReservationList(roomId: Long): List<LiveReservation>
|
||||
|
||||
fun cancelReservation(roomId: Long)
|
||||
|
||||
fun getReservationBookerList(roomId: Long): List<Member>
|
||||
|
||||
fun isExistsReservation(roomId: Long, memberId: Long): Boolean
|
||||
}
|
||||
|
||||
@Repository
|
||||
class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveReservationQueryRepository {
|
||||
override fun getReservationList(roomId: Long): List<LiveReservation> {
|
||||
return queryFactory
|
||||
.selectFrom(liveReservation)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.where(
|
||||
liveRoom.id.eq(roomId)
|
||||
.and(liveReservation.isActive.isTrue)
|
||||
)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun cancelReservation(roomId: Long) {
|
||||
queryFactory
|
||||
.update(liveReservation)
|
||||
.set(liveReservation.isActive, false)
|
||||
.where(liveReservation.room.id.eq(roomId))
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun getReservationBookerList(roomId: Long): List<Member> {
|
||||
return queryFactory
|
||||
.select(member)
|
||||
.from(liveReservation)
|
||||
.innerJoin(liveReservation.member, member)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.where(
|
||||
liveRoom.id.eq(roomId)
|
||||
.and(liveReservation.isActive.isTrue)
|
||||
)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
override fun isExistsReservation(roomId: Long, memberId: Long): Boolean {
|
||||
return queryFactory
|
||||
.selectFrom(liveReservation)
|
||||
.innerJoin(liveReservation.member, member)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.where(
|
||||
liveReservation.isActive.isTrue
|
||||
.and(liveReservation.room.id.eq(roomId))
|
||||
.and(liveReservation.member.id.eq(memberId))
|
||||
)
|
||||
.fetchFirst() != null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package kr.co.vividnext.sodalive.live.reservation
|
||||
|
||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
class LiveReservationService(
|
||||
private val repository: LiveReservationRepository,
|
||||
private val liveRoomRepository: LiveRoomRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val canPaymentService: CanPaymentService
|
||||
) {
|
||||
fun makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse {
|
||||
val room = liveRoomRepository.findByIdOrNull(id = request.roomId)
|
||||
?: throw SodaException(message = "잘못된 요청입니다.\n다시 시도해 주세요.")
|
||||
|
||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||
?: throw SodaException(message = "로그인 정보를 확인해주세요.")
|
||||
|
||||
if (
|
||||
room.member!!.id!! != memberId &&
|
||||
room.type == LiveRoomType.PRIVATE &&
|
||||
(request.password == null || request.password != room.password)
|
||||
) {
|
||||
throw SodaException("비밀번호가 일치하지 않습니다.\n다시 확인 후 입력해주세요.")
|
||||
}
|
||||
|
||||
if (repository.isExistsReservation(roomId = request.roomId, memberId = memberId)) {
|
||||
throw SodaException("이미 예약한 라이브 입니다.")
|
||||
}
|
||||
|
||||
val haveCan = member.getChargeCan(request.container) + member.getRewardCan(request.container)
|
||||
if (haveCan < room.price) {
|
||||
throw SodaException("${room.price - haveCan}캔이 부족합니다. 충전 후 이용해 주세요.")
|
||||
}
|
||||
|
||||
if (room.price > 0) {
|
||||
canPaymentService.spendCan(
|
||||
memberId = member.id!!,
|
||||
needCan = room.price,
|
||||
canUsage = CanUsage.LIVE,
|
||||
liveRoom = room,
|
||||
container = request.container
|
||||
)
|
||||
}
|
||||
|
||||
val reservation = LiveReservation()
|
||||
reservation.room = room
|
||||
reservation.member = member
|
||||
repository.save(reservation)
|
||||
|
||||
val beginDateTime = room.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(request.timezone))
|
||||
|
||||
return MakeLiveReservationResponse(
|
||||
reservationId = reservation.id!!,
|
||||
nickname = room.member!!.nickname,
|
||||
title = room.title,
|
||||
beginDateString = beginDateTime.format(DateTimeFormatter.ofPattern("yyyy년 M월 d일 (E), a hh:mm")),
|
||||
price = if (room.price > 0) {
|
||||
"${room.price} 캔"
|
||||
} else {
|
||||
"무료"
|
||||
},
|
||||
haveCan = haveCan,
|
||||
useCan = room.price,
|
||||
remainingCan = haveCan - room.price
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.live.reservation
|
||||
|
||||
data class MakeLiveReservationRequest(
|
||||
val roomId: Long,
|
||||
val container: String,
|
||||
val timezone: String = "Asia/Seoul",
|
||||
val password: String? = null
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
package kr.co.vividnext.sodalive.live.reservation
|
||||
|
||||
data class MakeLiveReservationResponse(
|
||||
val reservationId: Long,
|
||||
val nickname: String,
|
||||
val title: String,
|
||||
val beginDateString: String,
|
||||
val price: String,
|
||||
val haveCan: Int,
|
||||
val useCan: Int,
|
||||
val remainingCan: Int
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.live.room
|
||||
|
||||
data class EditLiveRoomInfoRequest(
|
||||
val title: String?,
|
||||
val notice: String?,
|
||||
val numberOfPeople: Int?,
|
||||
val beginDateTimeString: String?,
|
||||
val timezone: String?
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.live.room
|
||||
|
||||
data class EnterOrQuitLiveRoomRequest(
|
||||
val roomId: Long,
|
||||
val container: String,
|
||||
val password: String? = null
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kr.co.vividnext.sodalive.live.room
|
||||
|
||||
data class GetRecentRoomInfoResponse(
|
||||
val title: String,
|
||||
val notice: String,
|
||||
var coverImageUrl: String,
|
||||
val coverImagePath: String,
|
||||
val numberOfPeople: Int
|
||||
)
|
|
@ -3,6 +3,8 @@ package kr.co.vividnext.sodalive.live.room
|
|||
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.LiveRoomCancel
|
||||
import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.CascadeType
|
||||
|
@ -44,6 +46,11 @@ data class LiveRoom(
|
|||
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||
var reservations: MutableList<LiveReservation> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||
var visits: MutableList<LiveRoomVisit> = mutableListOf()
|
||||
|
||||
@OneToOne(mappedBy = "room")
|
||||
var cancel: LiveRoomCancel? = null
|
||||
var channelName: String? = null
|
||||
var isActive: Boolean = true
|
||||
}
|
||||
|
|
|
@ -2,12 +2,15 @@ package kr.co.vividnext.sodalive.live.room
|
|||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
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.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RequestPart
|
||||
|
@ -48,10 +51,59 @@ class LiveRoomController(private val service: LiveRoomService) {
|
|||
@RequestParam timezone: String,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member != null) {
|
||||
ApiResponse.ok(service.getRoomDetail(id, member, timezone))
|
||||
} else {
|
||||
throw SodaException("로그인 정보를 확인해주세요.")
|
||||
}
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.getRoomDetail(id, member, timezone))
|
||||
}
|
||||
|
||||
@PostMapping("/enter")
|
||||
fun enterLive(
|
||||
@RequestBody request: EnterOrQuitLiveRoomRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.enterLive(request, member))
|
||||
}
|
||||
|
||||
@PutMapping("/start")
|
||||
fun startLive(
|
||||
@RequestBody request: StartLiveRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.startLive(request, member))
|
||||
}
|
||||
|
||||
@PutMapping("/cancel")
|
||||
fun cancelLive(
|
||||
@RequestBody request: CancelLiveRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.cancelLive(request, member))
|
||||
}
|
||||
|
||||
@GetMapping("/recent-room-info")
|
||||
fun getRecentRoomInfo(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.getRecentRoomInfo(member))
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
fun editLiveRoomInfo(
|
||||
@PathVariable("id") roomId: Long,
|
||||
@RequestPart("coverImage") coverImage: MultipartFile?,
|
||||
@RequestPart("request") requestString: String?,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.editLiveRoomInfo(roomId, coverImage, requestString, member))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ package kr.co.vividnext.sodalive.live.room
|
|||
|
||||
import com.querydsl.core.types.OrderSpecifier
|
||||
import com.querydsl.core.types.Predicate
|
||||
import com.querydsl.core.types.Projections
|
||||
import com.querydsl.core.types.dsl.CaseBuilder
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||
import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.QMember
|
||||
|
@ -33,7 +32,8 @@ interface LiveRoomQueryRepository {
|
|||
): List<LiveRoom>
|
||||
|
||||
fun getLiveRoom(id: Long): LiveRoom?
|
||||
fun getReservationList(roomId: Long): List<LiveReservation>
|
||||
fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom?
|
||||
fun getRecentRoomInfo(memberId: Long, cloudFrontHost: String): GetRecentRoomInfoResponse?
|
||||
}
|
||||
|
||||
class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
|
||||
|
@ -112,15 +112,35 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
|
|||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun getReservationList(roomId: Long): List<LiveReservation> {
|
||||
override fun getLiveRoomAndAccountId(roomId: Long, memberId: Long): LiveRoom? {
|
||||
return queryFactory
|
||||
.selectFrom(liveReservation)
|
||||
.innerJoin(liveReservation.room, liveRoom)
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.where(
|
||||
liveRoom.id.eq(roomId)
|
||||
.and(liveReservation.isActive.isTrue)
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
.and(member.id.eq(memberId))
|
||||
)
|
||||
.fetch()
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun getRecentRoomInfo(memberId: Long, cloudFrontHost: String): GetRecentRoomInfoResponse? {
|
||||
return queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
GetRecentRoomInfoResponse::class.java,
|
||||
liveRoom.title,
|
||||
liveRoom.notice,
|
||||
liveRoom.coverImage.prepend("/").prepend(cloudFrontHost),
|
||||
liveRoom.coverImage,
|
||||
liveRoom.numberOfPeople
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
.where(liveRoom.member.id.eq(memberId))
|
||||
.orderBy(liveRoom.id.desc())
|
||||
.limit(1)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
private fun orderByFieldAccountId(
|
||||
|
|
|
@ -4,29 +4,55 @@ import com.amazonaws.services.s3.model.ObjectMetadata
|
|||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||
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.extensions.convertLocalDateTime
|
||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservationRepository
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.CancelLiveRequest
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.LiveRoomCancel
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.LiveRoomCancelRepository
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailManager
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
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.visit.LiveRoomVisitService
|
||||
import kr.co.vividnext.sodalive.live.tag.LiveTagRepository
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class LiveRoomService(
|
||||
private val repository: LiveRoomRepository,
|
||||
private val roomInfoRepository: LiveRoomInfoRedisRepository,
|
||||
private val roomCancelRepository: LiveRoomCancelRepository,
|
||||
|
||||
private val useCanCalculateRepository: UseCanCalculateRepository,
|
||||
private val reservationRepository: LiveReservationRepository,
|
||||
private val roomVisitService: LiveRoomVisitService,
|
||||
private val canPaymentService: CanPaymentService,
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val memberRepository: MemberRepository,
|
||||
private val tagRepository: LiveTagRepository,
|
||||
private val canRepository: CanRepository,
|
||||
private val objectMapper: ObjectMapper,
|
||||
|
@ -90,6 +116,7 @@ class LiveRoomService(
|
|||
.toList()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun createLiveRoom(coverImage: MultipartFile?, requestString: String, member: Member): CreateLiveRoomResponse {
|
||||
val request = objectMapper.readValue(requestString, CreateSudaRoomRequest::class.java)
|
||||
if (request.coverImageUrl == null && coverImage == null) {
|
||||
|
@ -191,7 +218,7 @@ class LiveRoomService(
|
|||
val response = GetRoomDetailResponse(
|
||||
roomId = roomId,
|
||||
title = room.title,
|
||||
content = room.notice,
|
||||
notice = room.notice,
|
||||
price = room.price,
|
||||
tags = room.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
||||
numberOfParticipantsTotal = room.numberOfPeople,
|
||||
|
@ -251,7 +278,7 @@ class LiveRoomService(
|
|||
response.numberOfParticipants = users.size
|
||||
}
|
||||
} else {
|
||||
val reservationList = repository.getReservationList(roomId)
|
||||
val reservationList = reservationRepository.getReservationList(roomId)
|
||||
response.participatingUsers = reservationList
|
||||
.asSequence()
|
||||
.map {
|
||||
|
@ -267,4 +294,201 @@ class LiveRoomService(
|
|||
|
||||
return response
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun startLive(request: StartLiveRequest, member: Member) {
|
||||
val room = repository.getLiveRoomAndAccountId(request.roomId, member.id!!)
|
||||
?: throw SodaException("해당하는 라이브가 없습니다.")
|
||||
|
||||
val dateTime = LocalDateTime.now()
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(request.timezone))
|
||||
.toLocalDateTime()
|
||||
|
||||
val beginDateTime = room.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(request.timezone))
|
||||
.toLocalDateTime()
|
||||
|
||||
if (dateTime.plusMinutes(10).isBefore(beginDateTime)) {
|
||||
val startAvailableDateTimeString = beginDateTime.minusMinutes(10).format(
|
||||
DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")
|
||||
)
|
||||
throw SodaException("$startAvailableDateTimeString 이후에 시작할 수 있습니다.")
|
||||
}
|
||||
|
||||
room.channelName = "SODA_LIVE_CHANNEL_" +
|
||||
"${member.id}_${dateTime.year}_${dateTime.month}_${dateTime.dayOfMonth}_" +
|
||||
"${dateTime.hour}_${dateTime.minute}"
|
||||
}
|
||||
|
||||
fun cancelLive(request: CancelLiveRequest, member: Member) {
|
||||
val room = repository.getLiveRoomAndAccountId(request.roomId, member.id!!)
|
||||
?: throw SodaException("해당하는 라이브가 없습니다.")
|
||||
|
||||
if (request.reason.isBlank()) {
|
||||
throw SodaException("취소사유를 입력해 주세요.")
|
||||
}
|
||||
room.isActive = false
|
||||
|
||||
val roomCancel = LiveRoomCancel(request.reason)
|
||||
roomCancel.room = room
|
||||
roomCancelRepository.save(roomCancel)
|
||||
|
||||
// 유료방인 경우 환불처리
|
||||
if (room.price > 0) {
|
||||
val bookerList = reservationRepository.getReservationBookerList(roomId = room.id!!)
|
||||
for (booker in bookerList) {
|
||||
val useCan = canRepository.getCanUsedForLiveRoomNotRefund(memberId = booker.id!!, roomId = room.id!!)
|
||||
?: continue
|
||||
useCan.isRefund = true
|
||||
|
||||
val useCanCalculate = useCanCalculateRepository.findByUseCanIdAndStatus(useCanId = useCan.id!!)
|
||||
useCanCalculate.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.PG -> booker.pgRewardCan += charge.rewardCan
|
||||
PaymentGateway.GOOGLE_IAP -> booker.googleRewardCan += charge.rewardCan
|
||||
PaymentGateway.APPLE_IAP -> booker.appleRewardCan += charge.rewardCan
|
||||
}
|
||||
charge.member = booker
|
||||
|
||||
val payment = Payment(
|
||||
status = PaymentStatus.COMPLETE,
|
||||
paymentGateway = it.paymentGateway
|
||||
)
|
||||
payment.method = "환불"
|
||||
charge.payment = payment
|
||||
|
||||
chargeRepository.save(charge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reservationRepository.cancelReservation(roomId = room.id!!)
|
||||
}
|
||||
|
||||
fun enterLive(request: EnterOrQuitLiveRoomRequest, member: Member) {
|
||||
val room = repository.getLiveRoom(id = request.roomId)
|
||||
?: throw SodaException("해당하는 라이브가 없습니다.")
|
||||
|
||||
if (
|
||||
room.member!!.id!! != member.id!! &&
|
||||
room.type == LiveRoomType.PRIVATE &&
|
||||
(request.password == null || request.password != room.password)
|
||||
) {
|
||||
throw SodaException("비밀번호가 일치하지 않습니다.\n다시 확인 후 입력해주세요.")
|
||||
}
|
||||
|
||||
var roomInfo = roomInfoRepository.findByIdOrNull(request.roomId)
|
||||
if (roomInfo == null) {
|
||||
roomInfo = roomInfoRepository.save(LiveRoomInfo(roomId = request.roomId))
|
||||
}
|
||||
|
||||
if (roomInfo.speakerCount + roomInfo.listenerCount + roomInfo.managerCount >= room.numberOfPeople) {
|
||||
throw SodaException("방이 가득찼습니다.")
|
||||
}
|
||||
|
||||
if (
|
||||
room.price > 0 &&
|
||||
room.member!!.id!! != member.id!! &&
|
||||
canRepository.isExistPaidLiveRoom(memberId = member.id!!, roomId = request.roomId) == null
|
||||
) {
|
||||
val findMember = memberRepository.findByIdOrNull(id = member.id!!)
|
||||
?: throw SodaException("로그인 정보를 확인해 주세요.")
|
||||
|
||||
val totalCan = findMember.getChargeCan(request.container) + findMember.getRewardCan(request.container)
|
||||
if (totalCan < room.price) {
|
||||
throw SodaException("${room.price - totalCan}캔이 부족합니다. 충전 후 이용해 주세요.")
|
||||
}
|
||||
|
||||
canPaymentService.spendCan(
|
||||
memberId = member.id!!,
|
||||
needCan = room.price,
|
||||
canUsage = CanUsage.LIVE,
|
||||
liveRoom = room,
|
||||
container = request.container
|
||||
)
|
||||
}
|
||||
|
||||
roomInfo.removeListener(member)
|
||||
roomInfo.removeSpeaker(member)
|
||||
roomInfo.removeManager(member)
|
||||
|
||||
if (room.member!!.id == member.id) {
|
||||
roomInfo.addSpeaker(member)
|
||||
} else {
|
||||
roomInfo.addListener(member)
|
||||
}
|
||||
|
||||
roomInfoRepository.save(roomInfo)
|
||||
roomVisitService.roomVisit(room, member)
|
||||
}
|
||||
|
||||
fun getRecentRoomInfo(member: Member): GetRecentRoomInfoResponse {
|
||||
return repository.getRecentRoomInfo(memberId = member.id!!, cloudFrontHost = cloudFrontHost)
|
||||
?: throw SodaException("최근 데이터가 없습니다.")
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun editLiveRoomInfo(roomId: Long, coverImage: MultipartFile?, requestString: String?, member: Member) {
|
||||
val room = repository.getLiveRoom(roomId)
|
||||
if (member.id == null || room?.member?.id != member.id!!) {
|
||||
throw SodaException("잘못된 요청입니다.")
|
||||
}
|
||||
|
||||
if (coverImage == null && requestString == null) {
|
||||
throw SodaException("변경사항이 없습니다.")
|
||||
}
|
||||
|
||||
if (requestString != null) {
|
||||
val request = objectMapper.readValue(requestString, EditLiveRoomInfoRequest::class.java)
|
||||
|
||||
if (request.title != null) {
|
||||
room.title = request.title
|
||||
}
|
||||
|
||||
if (request.notice != null) {
|
||||
room.notice = request.notice
|
||||
}
|
||||
|
||||
if (request.numberOfPeople != null) {
|
||||
room.numberOfPeople = request.numberOfPeople
|
||||
}
|
||||
|
||||
if (request.beginDateTimeString != null && request.timezone != null) {
|
||||
room.beginDateTime = request.beginDateTimeString.convertLocalDateTime("yyyy-MM-dd HH:mm")
|
||||
.atZone(ZoneId.of(request.timezone))
|
||||
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||
.toLocalDateTime()
|
||||
}
|
||||
}
|
||||
|
||||
if (coverImage != null) {
|
||||
val metadata = ObjectMetadata()
|
||||
metadata.contentLength = coverImage.size
|
||||
|
||||
// 커버 이미지 파일명 생성
|
||||
val coverImageFileName = generateFileName(prefix = "${room.id}-cover")
|
||||
|
||||
// 커버 이미지 업로드
|
||||
val coverImagePath = s3Uploader.upload(
|
||||
inputStream = coverImage.inputStream,
|
||||
bucket = coverImageBucket,
|
||||
filePath = "suda_room_cover/${room.id}/$coverImageFileName",
|
||||
metadata = metadata
|
||||
)
|
||||
|
||||
room.bgImage = coverImagePath
|
||||
|
||||
if (room.channelName == null) {
|
||||
room.coverImage = coverImagePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.live.room
|
||||
|
||||
import java.util.TimeZone
|
||||
|
||||
data class StartLiveRequest(
|
||||
val roomId: Long,
|
||||
val timezone: String = TimeZone.getDefault().id
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
package kr.co.vividnext.sodalive.live.room.cancel
|
||||
|
||||
data class CancelLiveRequest(val roomId: Long, val reason: String)
|
|
@ -0,0 +1,21 @@
|
|||
package kr.co.vividnext.sodalive.live.room.cancel
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.OneToOne
|
||||
|
||||
@Entity
|
||||
data class LiveRoomCancel(
|
||||
val reason: String
|
||||
) : BaseEntity() {
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "room_id", nullable = false)
|
||||
var room: LiveRoom? = null
|
||||
set(value) {
|
||||
value?.cancel = this
|
||||
field = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.live.room.cancel
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface LiveRoomCancelRepository : JpaRepository<LiveRoomCancel, Long>
|
|
@ -7,7 +7,7 @@ data class GetRoomDetailResponse(
|
|||
val roomId: Long,
|
||||
val price: Int,
|
||||
val title: String,
|
||||
val content: String,
|
||||
val notice: String,
|
||||
var isPaid: Boolean,
|
||||
val isPrivateRoom: Boolean,
|
||||
val password: String?,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.live.room.visit
|
||||
|
||||
data class GetRecentVisitRoomMemberResponse(
|
||||
val userId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
package kr.co.vividnext.sodalive.live.room.visit
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
|
||||
@Entity
|
||||
class LiveRoomVisit : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member? = null
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "room_id", nullable = false)
|
||||
var room: LiveRoom? = null
|
||||
set(value) {
|
||||
value?.visits?.add(this)
|
||||
field = value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package kr.co.vividnext.sodalive.live.room.visit
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface LiveRoomVisitRepository : JpaRepository<LiveRoomVisit, Long>, LiveRoomVisitQueryRepository
|
||||
|
||||
interface LiveRoomVisitQueryRepository {
|
||||
fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit?
|
||||
fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit?
|
||||
}
|
||||
|
||||
@Repository
|
||||
class LiveRoomVisitQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomVisitQueryRepository {
|
||||
override fun findByRoomIdAndMemberId(roomId: Long, memberId: Long): LiveRoomVisit? {
|
||||
return queryFactory
|
||||
.selectFrom(liveRoomVisit)
|
||||
.innerJoin(liveRoomVisit.room, liveRoom)
|
||||
.innerJoin(liveRoomVisit.member, member)
|
||||
.where(
|
||||
liveRoom.id.eq(roomId)
|
||||
.and(member.id.eq(memberId))
|
||||
)
|
||||
.orderBy(liveRoomVisit.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun findFirstByMemberIdOrderByUpdatedAtDesc(memberId: Long): LiveRoomVisit? {
|
||||
return queryFactory
|
||||
.selectFrom(liveRoomVisit)
|
||||
.innerJoin(liveRoomVisit.room, liveRoom)
|
||||
.where(member.id.eq(memberId))
|
||||
.orderBy(liveRoomVisit.updatedAt.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package kr.co.vividnext.sodalive.live.room.visit
|
||||
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class LiveRoomVisitService(private val repository: LiveRoomVisitRepository) {
|
||||
@Transactional
|
||||
fun roomVisit(room: LiveRoom, member: Member) {
|
||||
var roomVisit = repository.findByRoomIdAndMemberId(room.id!!, member.id!!)
|
||||
if (roomVisit == null) {
|
||||
roomVisit = LiveRoomVisit()
|
||||
roomVisit.member = member
|
||||
roomVisit.room = room
|
||||
} else {
|
||||
roomVisit.updatedAt = LocalDateTime.now()
|
||||
}
|
||||
|
||||
repository.save(roomVisit)
|
||||
}
|
||||
}
|
|
@ -53,9 +53,9 @@ data class Member(
|
|||
|
||||
// 화폐
|
||||
private var pgChargeCan: Int = 0
|
||||
private var pgRewardCan: Int = 0
|
||||
var pgRewardCan: Int = 0
|
||||
private var googleChargeCan: Int = 0
|
||||
private var googleRewardCan: Int = 0
|
||||
var googleRewardCan: Int = 0
|
||||
var appleChargeCan: Int = 0
|
||||
var appleRewardCan: Int = 0
|
||||
|
||||
|
|
Loading…
Reference in New Issue