라이브 - 방 만들기, 태그 등록, 태그 조회 API 추가
This commit is contained in:
parent
f1610af6f6
commit
036107d103
|
@ -8,10 +8,13 @@ import kr.co.vividnext.sodalive.can.charge.QCharge.charge
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
|
||||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||||
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
|
import kr.co.vividnext.sodalive.can.payment.QPayment.payment
|
||||||
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||||
import kr.co.vividnext.sodalive.can.use.UseCan
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -23,6 +26,7 @@ interface CanQueryRepository {
|
||||||
fun findAllByStatus(status: CanStatus): List<CanResponse>
|
fun findAllByStatus(status: CanStatus): List<CanResponse>
|
||||||
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
|
||||||
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
|
||||||
|
fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -93,4 +97,18 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
||||||
.orderBy(charge.id.desc())
|
.orderBy(charge.id.desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isExistPaidLiveRoom(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))
|
||||||
|
)
|
||||||
|
.orderBy(useCan.id.desc())
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package kr.co.vividnext.sodalive.extensions
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
fun String.convertLocalDateTime(format: String): LocalDateTime {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern(format)
|
||||||
|
return LocalDateTime.parse(this, dateTimeFormatter)
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import com.querydsl.core.types.Projections
|
||||||
import com.querydsl.core.types.dsl.Expressions
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
|
import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
|
||||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
@ -63,7 +62,6 @@ class LiveRecommendRepository(private val queryFactory: JPAQueryFactory) {
|
||||||
.where(
|
.where(
|
||||||
where
|
where
|
||||||
.and(liveRoom.isActive.isTrue)
|
.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.type.ne(LiveRoomType.SECRET))
|
|
||||||
.and(liveRoom.channelName.isNotNull)
|
.and(liveRoom.channelName.isNotNull)
|
||||||
.and(liveRoom.channelName.isNotEmpty)
|
.and(liveRoom.channelName.isNotEmpty)
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
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
|
||||||
|
import javax.persistence.OneToOne
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class LiveReservation(
|
||||||
|
var isActive: Boolean = true
|
||||||
|
) : BaseEntity() {
|
||||||
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
|
var member: Member? = null
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "room_id", nullable = true)
|
||||||
|
var room: LiveRoom? = null
|
||||||
|
set(value) {
|
||||||
|
value?.reservations!!.add(this)
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
data class CreateLiveRoomResponse(
|
||||||
|
val id: Long?,
|
||||||
|
val channelName: String?
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
data class CreateSudaRoomRequest(
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
val coverImageUrl: String? = null,
|
||||||
|
val isAdult: Boolean,
|
||||||
|
val tags: List<String>,
|
||||||
|
val numberOfPeople: Int,
|
||||||
|
val beginDateTimeString: String? = null,
|
||||||
|
val price: Int = 0,
|
||||||
|
val timezone: String,
|
||||||
|
val type: LiveRoomType = LiveRoomType.OPEN,
|
||||||
|
val password: String? = null
|
||||||
|
)
|
|
@ -2,8 +2,10 @@ package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.can.use.UseCan
|
import kr.co.vividnext.sodalive.can.use.UseCan
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
import javax.persistence.CascadeType
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.EnumType
|
import javax.persistence.EnumType
|
||||||
|
@ -33,9 +35,15 @@ data class LiveRoom(
|
||||||
@JoinColumn(name = "member_id", nullable = false)
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
var member: Member? = null
|
var member: Member? = null
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "room", cascade = [CascadeType.ALL])
|
||||||
|
var tags: MutableList<LiveRoomTag> = mutableListOf()
|
||||||
|
|
||||||
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||||
var useCan: MutableList<UseCan> = mutableListOf()
|
var useCan: MutableList<UseCan> = mutableListOf()
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||||
|
var reservations: MutableList<LiveReservation> = mutableListOf()
|
||||||
|
|
||||||
var channelName: String? = null
|
var channelName: String? = null
|
||||||
var isActive: Boolean = true
|
var isActive: Boolean = true
|
||||||
}
|
}
|
||||||
|
@ -45,10 +53,7 @@ enum class LiveRoomType {
|
||||||
OPEN,
|
OPEN,
|
||||||
|
|
||||||
// 비공개
|
// 비공개
|
||||||
PRIVATE,
|
PRIVATE
|
||||||
|
|
||||||
// 비밀방
|
|
||||||
SECRET
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LiveRoomStatus {
|
enum class LiveRoomStatus {
|
||||||
|
|
|
@ -6,9 +6,13 @@ import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
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.PostMapping
|
||||||
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.RequestPart
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/live/room")
|
@RequestMapping("/live/room")
|
||||||
|
@ -26,4 +30,28 @@ class LiveRoomController(private val service: LiveRoomService) {
|
||||||
|
|
||||||
ApiResponse.ok(service.getRoomList(dateString, status, pageable, member, timezone))
|
ApiResponse.ok(service.getRoomList(dateString, status, pageable, member, timezone))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
fun createLiveRoom(
|
||||||
|
@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.createLiveRoom(coverImage, requestString, member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail/{id}")
|
||||||
|
fun getRoomDetail(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@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("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import com.querydsl.core.types.Predicate
|
||||||
import com.querydsl.core.types.dsl.CaseBuilder
|
import com.querydsl.core.types.dsl.CaseBuilder
|
||||||
import com.querydsl.core.types.dsl.Expressions
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
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.live.room.QLiveRoom.liveRoom
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import kr.co.vividnext.sodalive.member.QMember
|
import kr.co.vividnext.sodalive.member.QMember
|
||||||
|
@ -29,6 +31,9 @@ interface LiveRoomQueryRepository {
|
||||||
timezone: String,
|
timezone: String,
|
||||||
isAdult: Boolean
|
isAdult: Boolean
|
||||||
): List<LiveRoom>
|
): List<LiveRoom>
|
||||||
|
|
||||||
|
fun getLiveRoom(id: Long): LiveRoom?
|
||||||
|
fun getReservationList(roomId: Long): List<LiveReservation>
|
||||||
}
|
}
|
||||||
|
|
||||||
class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
|
class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
|
||||||
|
@ -79,7 +84,6 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
|
||||||
|
|
||||||
where = where.and(liveRoom.isActive.isTrue)
|
where = where.and(liveRoom.isActive.isTrue)
|
||||||
.and(liveRoom.member.isNotNull)
|
.and(liveRoom.member.isNotNull)
|
||||||
.and(liveRoom.type.ne(LiveRoomType.SECRET))
|
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(liveRoom)
|
.selectFrom(liveRoom)
|
||||||
|
@ -98,6 +102,27 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLiveRoom(id: Long): LiveRoom? {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(liveRoom)
|
||||||
|
.where(
|
||||||
|
liveRoom.id.eq(id)
|
||||||
|
.and(liveRoom.isActive.isTrue)
|
||||||
|
)
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
private fun orderByFieldAccountId(
|
private fun orderByFieldAccountId(
|
||||||
memberId: Long,
|
memberId: Long,
|
||||||
status: LiveRoomStatus,
|
status: LiveRoomStatus,
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
package kr.co.vividnext.sodalive.live.room
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
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.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||||
|
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.LiveRoomInfoRedisRepository
|
import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
|
||||||
|
import kr.co.vividnext.sodalive.live.tag.LiveTagRepository
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@ -14,8 +27,15 @@ class LiveRoomService(
|
||||||
private val repository: LiveRoomRepository,
|
private val repository: LiveRoomRepository,
|
||||||
private val roomInfoRepository: LiveRoomInfoRedisRepository,
|
private val roomInfoRepository: LiveRoomInfoRedisRepository,
|
||||||
|
|
||||||
|
private val tagRepository: LiveTagRepository,
|
||||||
|
private val canRepository: CanRepository,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val coverImageBucket: String,
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val coverImageHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
fun getRoomList(
|
fun getRoomList(
|
||||||
dateString: String?,
|
dateString: String?,
|
||||||
|
@ -61,7 +81,7 @@ class LiveRoomService(
|
||||||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
||||||
it.coverImage!!
|
it.coverImage!!
|
||||||
} else {
|
} else {
|
||||||
"$coverImageHost/${it.coverImage!!}"
|
"$cloudFrontHost/${it.coverImage!!}"
|
||||||
},
|
},
|
||||||
isReservation = false,
|
isReservation = false,
|
||||||
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
||||||
|
@ -69,4 +89,182 @@ class LiveRoomService(
|
||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createLiveRoom(coverImage: MultipartFile?, requestString: String, member: Member): CreateLiveRoomResponse {
|
||||||
|
val request = objectMapper.readValue(requestString, CreateSudaRoomRequest::class.java)
|
||||||
|
if (request.coverImageUrl == null && coverImage == null) {
|
||||||
|
throw SodaException("커버이미지를 선택해 주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
val beginDateTime = if (request.beginDateTimeString != null) {
|
||||||
|
request.beginDateTimeString.convertLocalDateTime("yyyy-MM-dd HH:mm")
|
||||||
|
.atZone(ZoneId.of(request.timezone))
|
||||||
|
.withZoneSameInstant(ZoneId.of("UTC"))
|
||||||
|
.toLocalDateTime()
|
||||||
|
} else {
|
||||||
|
now
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.beginDateTimeString != null &&
|
||||||
|
beginDateTime < now.plusMinutes(30)
|
||||||
|
) {
|
||||||
|
throw SodaException("현재시각 기준, 30분 이후부터 설정가능합니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.type == LiveRoomType.PRIVATE &&
|
||||||
|
(request.password == null || request.password.length != 6)
|
||||||
|
) {
|
||||||
|
throw SodaException("방 입장 비밀번호 6자리를 입력해 주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val room = LiveRoom(
|
||||||
|
title = request.title,
|
||||||
|
notice = request.content,
|
||||||
|
beginDateTime = beginDateTime,
|
||||||
|
numberOfPeople = request.numberOfPeople,
|
||||||
|
isAdult = request.isAdult,
|
||||||
|
price = request.price,
|
||||||
|
type = request.type,
|
||||||
|
password = request.password
|
||||||
|
)
|
||||||
|
room.member = member
|
||||||
|
|
||||||
|
if (request.beginDateTimeString == null) {
|
||||||
|
room.channelName = "SODA_LIVE_CHANNEL_" +
|
||||||
|
"${member.id!!}_${beginDateTime.year}_${beginDateTime.month}_${beginDateTime.dayOfMonth}_" +
|
||||||
|
"${beginDateTime.hour}_${beginDateTime.minute}"
|
||||||
|
}
|
||||||
|
|
||||||
|
request.tags.forEach {
|
||||||
|
val tag = tagRepository.findByTag(it)
|
||||||
|
if (tag != null) {
|
||||||
|
room.tags.add(LiveRoomTag(room, tag))
|
||||||
|
|
||||||
|
if (tag.tag.contains("음담패설")) {
|
||||||
|
room.isAdult = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val createdRoom = repository.save(room)
|
||||||
|
// 이미지 업로드
|
||||||
|
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 = "live_room_cover/${room.id}/$coverImageFileName",
|
||||||
|
metadata = metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
room.coverImage = coverImagePath
|
||||||
|
room.bgImage = coverImagePath
|
||||||
|
} else {
|
||||||
|
room.coverImage = request.coverImageUrl
|
||||||
|
room.bgImage = request.coverImageUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateLiveRoomResponse(createdRoom.id, createdRoom.channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRoomDetail(roomId: Long, member: Member, timezone: String): GetRoomDetailResponse {
|
||||||
|
val room = repository.getLiveRoom(id = roomId)
|
||||||
|
?: throw SodaException("이미 종료된 방입니다")
|
||||||
|
|
||||||
|
if (room.isAdult && member.auth == null) {
|
||||||
|
throw SodaException("본인인증이 필요한 서비스 입니다.\n요즘라이브 마이페이지에서 본인인증 후 다시 이용해 주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val beginDateTime = room.beginDateTime
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
|
val response = GetRoomDetailResponse(
|
||||||
|
roomId = roomId,
|
||||||
|
title = room.title,
|
||||||
|
content = room.notice,
|
||||||
|
price = room.price,
|
||||||
|
tags = room.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
||||||
|
numberOfParticipantsTotal = room.numberOfPeople,
|
||||||
|
numberOfParticipants = 0,
|
||||||
|
channelName = room.channelName,
|
||||||
|
beginDateTime = beginDateTime.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
|
||||||
|
isPaid = false,
|
||||||
|
isPrivateRoom = room.type == LiveRoomType.PRIVATE,
|
||||||
|
password = room.password
|
||||||
|
)
|
||||||
|
response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost)
|
||||||
|
|
||||||
|
if (!room.channelName.isNullOrBlank()) {
|
||||||
|
val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
|
||||||
|
|
||||||
|
if (roomInfo != null) {
|
||||||
|
response.isPaid = canRepository.isExistPaidLiveRoom(
|
||||||
|
memberId = member.id!!,
|
||||||
|
roomId = roomId
|
||||||
|
) != null
|
||||||
|
|
||||||
|
val users = roomInfo.speakerList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
GetRoomDetailUser(
|
||||||
|
it.id,
|
||||||
|
it.nickname,
|
||||||
|
"$cloudFrontHost/${it.profileImage}"
|
||||||
|
)
|
||||||
|
}.toMutableList()
|
||||||
|
|
||||||
|
users.addAll(
|
||||||
|
roomInfo.listenerList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
GetRoomDetailUser(
|
||||||
|
it.id,
|
||||||
|
it.nickname,
|
||||||
|
"$cloudFrontHost/${it.profileImage}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
users.addAll(
|
||||||
|
roomInfo.managerList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
GetRoomDetailUser(
|
||||||
|
it.id,
|
||||||
|
it.nickname,
|
||||||
|
"$cloudFrontHost/${it.profileImage}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response.participatingUsers = users
|
||||||
|
response.numberOfParticipants = users.size
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val reservationList = repository.getReservationList(roomId)
|
||||||
|
response.participatingUsers = reservationList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
if (it.member!!.id!! == member.id!!) {
|
||||||
|
response.isPaid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
GetRoomDetailUser(it.member!!, cloudFrontHost)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
response.numberOfParticipants = reservationList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.room
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import kr.co.vividnext.sodalive.live.tag.LiveTag
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.FetchType
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
class LiveRoomTag(
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "live_room_id", nullable = false)
|
||||||
|
var room: LiveRoom,
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "live_tag_id", nullable = false)
|
||||||
|
var tag: LiveTag
|
||||||
|
) : BaseEntity()
|
|
@ -0,0 +1,66 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.room.detail
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
|
||||||
|
data class GetRoomDetailResponse(
|
||||||
|
val roomId: Long,
|
||||||
|
val price: Int,
|
||||||
|
val title: String,
|
||||||
|
val content: String,
|
||||||
|
var isPaid: Boolean,
|
||||||
|
val isPrivateRoom: Boolean,
|
||||||
|
val password: String?,
|
||||||
|
val tags: List<String>,
|
||||||
|
val channelName: String?,
|
||||||
|
val beginDateTime: String,
|
||||||
|
var numberOfParticipants: Int,
|
||||||
|
val numberOfParticipantsTotal: Int
|
||||||
|
) {
|
||||||
|
var manager: GetRoomDetailManager? = null
|
||||||
|
var participatingUsers: List<GetRoomDetailUser> = listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GetRoomDetailManager(
|
||||||
|
val id: Long,
|
||||||
|
val nickname: String,
|
||||||
|
val introduce: String,
|
||||||
|
val youtubeUrl: String?,
|
||||||
|
val instagramUrl: String?,
|
||||||
|
val websiteUrl: String?,
|
||||||
|
val blogUrl: String?,
|
||||||
|
val profileImageUrl: String,
|
||||||
|
val isCreator: Boolean
|
||||||
|
) {
|
||||||
|
constructor(member: Member, cloudFrontHost: String) : this(
|
||||||
|
id = member.id!!,
|
||||||
|
nickname = member.nickname,
|
||||||
|
introduce = member.introduce,
|
||||||
|
youtubeUrl = member.youtubeUrl,
|
||||||
|
instagramUrl = member.instagramUrl,
|
||||||
|
websiteUrl = member.websiteUrl,
|
||||||
|
blogUrl = member.blogUrl,
|
||||||
|
profileImageUrl = if (member.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${member.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
isCreator = member.role == MemberRole.CREATOR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GetRoomDetailUser(
|
||||||
|
val id: Long,
|
||||||
|
val nickname: String,
|
||||||
|
val profileImageUrl: String
|
||||||
|
) {
|
||||||
|
constructor(member: Member, cloudFrontHost: String) : this(
|
||||||
|
member.id!!,
|
||||||
|
member.nickname,
|
||||||
|
profileImageUrl = if (member.profileImage != null) {
|
||||||
|
"$cloudFrontHost/${member.profileImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
data class CreateLiveTagRequest(val tag: String)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class GetLiveTagResponse @QueryProjection constructor(
|
||||||
|
val id: Long,
|
||||||
|
val tag: String,
|
||||||
|
val image: String
|
||||||
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import javax.persistence.Column
|
||||||
|
import javax.persistence.Entity
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class LiveTag(
|
||||||
|
@Column(unique = true, nullable = false)
|
||||||
|
var tag: String,
|
||||||
|
@Column(nullable = true)
|
||||||
|
var image: String? = null,
|
||||||
|
@Column(nullable = false)
|
||||||
|
var isActive: Boolean = true,
|
||||||
|
@Column(nullable = false)
|
||||||
|
var orders: Int = 1
|
||||||
|
) : BaseEntity()
|
|
@ -0,0 +1,35 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
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.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/live/tag")
|
||||||
|
class LiveTagController(private val service: LiveTagService) {
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
fun enrollmentLiveTag(
|
||||||
|
@RequestPart("image") image: MultipartFile,
|
||||||
|
@RequestPart("request") requestString: String
|
||||||
|
) = ApiResponse.ok(service.enrollmentLiveTag(image, requestString), "등록되었습니다.")
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun getTags(
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) {
|
||||||
|
throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResponse.ok(service.getTags(member))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.live.tag.QLiveTag.liveTag
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface LiveTagRepository : JpaRepository<LiveTag, Long>, LiveTagQueryRepository {
|
||||||
|
fun findByTag(it: String): LiveTag?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LiveTagQueryRepository {
|
||||||
|
fun getTags(role: MemberRole, isAdult: Boolean, cloudFrontHost: String): List<GetLiveTagResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class LiveTagQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveTagQueryRepository {
|
||||||
|
override fun getTags(role: MemberRole, isAdult: Boolean, cloudFrontHost: String): List<GetLiveTagResponse> {
|
||||||
|
var where = liveTag.isActive.isTrue
|
||||||
|
|
||||||
|
if (role != MemberRole.ADMIN && !isAdult) {
|
||||||
|
where = where.and(liveTag.tag.notIn("음담패설", "EDPS"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetLiveTagResponse(
|
||||||
|
liveTag.id,
|
||||||
|
liveTag.tag,
|
||||||
|
liveTag.image.prepend("/").prepend(cloudFrontHost)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(liveTag)
|
||||||
|
.where(where)
|
||||||
|
.orderBy(liveTag.orders.asc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.tag
|
||||||
|
|
||||||
|
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.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class LiveTagService(
|
||||||
|
private val repository: LiveTagRepository,
|
||||||
|
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val coverImageBucket: String,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) {
|
||||||
|
fun enrollmentLiveTag(image: MultipartFile, requestString: String) {
|
||||||
|
val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java)
|
||||||
|
tagExistCheck(request)
|
||||||
|
|
||||||
|
val tag = repository.save(LiveTag(request.tag))
|
||||||
|
|
||||||
|
val metadata = ObjectMetadata()
|
||||||
|
metadata.contentLength = image.size
|
||||||
|
|
||||||
|
val tagImageFileName = generateFileName(prefix = "${tag.id}-")
|
||||||
|
val tagImagePath = s3Uploader.upload(
|
||||||
|
inputStream = image.inputStream,
|
||||||
|
bucket = coverImageBucket,
|
||||||
|
filePath = "live_cover/${tag.id}/$tagImageFileName",
|
||||||
|
metadata = metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
tag.image = tagImagePath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTags(member: Member): List<GetLiveTagResponse> {
|
||||||
|
return repository.getTags(role = member.role, isAdult = member.auth != null, cloudFrontHost = cloudFrontHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tagExistCheck(request: CreateLiveTagRequest) {
|
||||||
|
repository.findByTag(request.tag)?.let { throw SodaException("이미 등록된 태그 입니다.") }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue