라이브 - 방 만들기, 태그 등록, 태그 조회 API 추가

This commit is contained in:
Klaus 2023-07-31 02:04:32 +09:00
parent f1610af6f6
commit 036107d103
18 changed files with 579 additions and 9 deletions

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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)
)

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.live.room
data class CreateLiveRoomResponse(
val id: Long?,
val channelName: String?
)

View File

@ -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
)

View File

@ -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 {

View File

@ -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("로그인 정보를 확인해주세요.")
}
}
}

View File

@ -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,

View File

@ -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
}
}

View File

@ -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()

View File

@ -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"
}
)
}

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.live.tag
data class CreateLiveTagRequest(val tag: String)

View File

@ -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
)

View File

@ -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()

View File

@ -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))
}
}

View File

@ -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()
}
}

View File

@ -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("이미 등록된 태그 입니다.") }
}
}