라이브 - 방 만들기, 태그 등록, 태그 조회 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.PaymentStatus
|
||||
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.UseCan
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
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.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
@ -23,6 +26,7 @@ interface CanQueryRepository {
|
|||
fun findAllByStatus(status: CanStatus): List<CanResponse>
|
||||
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?
|
||||
}
|
||||
|
||||
@Repository
|
||||
|
@ -93,4 +97,18 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
|
|||
.orderBy(charge.id.desc())
|
||||
.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.jpa.impl.JPAQueryFactory
|
||||
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.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
|
@ -63,7 +62,6 @@ class LiveRecommendRepository(private val queryFactory: JPAQueryFactory) {
|
|||
.where(
|
||||
where
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
.and(liveRoom.type.ne(LiveRoomType.SECRET))
|
||||
.and(liveRoom.channelName.isNotNull)
|
||||
.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.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.live.reservation.LiveReservation
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.CascadeType
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.EnumType
|
||||
|
@ -33,9 +35,15 @@ data class LiveRoom(
|
|||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member? = null
|
||||
|
||||
@OneToMany(mappedBy = "room", cascade = [CascadeType.ALL])
|
||||
var tags: MutableList<LiveRoomTag> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||
var useCan: MutableList<UseCan> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
|
||||
var reservations: MutableList<LiveReservation> = mutableListOf()
|
||||
|
||||
var channelName: String? = null
|
||||
var isActive: Boolean = true
|
||||
}
|
||||
|
@ -45,10 +53,7 @@ enum class LiveRoomType {
|
|||
OPEN,
|
||||
|
||||
// 비공개
|
||||
PRIVATE,
|
||||
|
||||
// 비밀방
|
||||
SECRET
|
||||
PRIVATE
|
||||
}
|
||||
|
||||
enum class LiveRoomStatus {
|
||||
|
|
|
@ -6,9 +6,13 @@ 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.RequestMapping
|
||||
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.multipart.MultipartFile
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/live/room")
|
||||
|
@ -26,4 +30,28 @@ class LiveRoomController(private val service: LiveRoomService) {
|
|||
|
||||
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.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
|
||||
|
@ -29,6 +31,9 @@ interface LiveRoomQueryRepository {
|
|||
timezone: String,
|
||||
isAdult: Boolean
|
||||
): List<LiveRoom>
|
||||
|
||||
fun getLiveRoom(id: Long): LiveRoom?
|
||||
fun getReservationList(roomId: Long): List<LiveReservation>
|
||||
}
|
||||
|
||||
class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
|
||||
|
@ -79,7 +84,6 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
|
|||
|
||||
where = where.and(liveRoom.isActive.isTrue)
|
||||
.and(liveRoom.member.isNotNull)
|
||||
.and(liveRoom.type.ne(LiveRoomType.SECRET))
|
||||
|
||||
return queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
|
@ -98,6 +102,27 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
|
|||
.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(
|
||||
memberId: Long,
|
||||
status: LiveRoomStatus,
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
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.tag.LiveTagRepository
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
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.web.multipart.MultipartFile
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
@ -14,8 +27,15 @@ class LiveRoomService(
|
|||
private val repository: LiveRoomRepository,
|
||||
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}")
|
||||
private val coverImageHost: String
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
fun getRoomList(
|
||||
dateString: String?,
|
||||
|
@ -61,7 +81,7 @@ class LiveRoomService(
|
|||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
||||
it.coverImage!!
|
||||
} else {
|
||||
"$coverImageHost/${it.coverImage!!}"
|
||||
"$cloudFrontHost/${it.coverImage!!}"
|
||||
},
|
||||
isReservation = false,
|
||||
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
||||
|
@ -69,4 +89,182 @@ class LiveRoomService(
|
|||
}
|
||||
.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