Compare commits

..

No commits in common. "1c0dc82d448a2eed2d74e8e22d8b6b7aaa17676d" and "c1e325aadff98a111c1b092f0cc644201dfaba3f" have entirely different histories.

7 changed files with 115 additions and 69 deletions

View File

@ -18,8 +18,8 @@ import javax.persistence.OneToMany
import javax.persistence.OneToOne import javax.persistence.OneToOne
import javax.persistence.Table import javax.persistence.Table
enum class PurchaseOption { enum class AudioContentType {
BOTH, BUY_ONLY, RENT_ONLY INDIVIDUAL, BUNDLE
} }
enum class SortType { enum class SortType {
@ -37,7 +37,7 @@ data class AudioContent(
val limited: Int? = null, val limited: Int? = null,
var remaining: Int? = null, var remaining: Int? = null,
@Enumerated(value = EnumType.STRING) @Enumerated(value = EnumType.STRING)
val purchaseOption: PurchaseOption = PurchaseOption.BOTH, val type: AudioContentType = AudioContentType.INDIVIDUAL,
val isGeneratePreview: Boolean = true, val isGeneratePreview: Boolean = true,
var isOnlyRental: Boolean = false, var isOnlyRental: Boolean = false,
var isAdult: Boolean = false, var isAdult: Boolean = false,
@ -63,4 +63,7 @@ data class AudioContent(
@OneToMany(mappedBy = "audioContent", cascade = [CascadeType.ALL]) @OneToMany(mappedBy = "audioContent", cascade = [CascadeType.ALL])
val audioContentHashTags: MutableList<AudioContentHashTag> = mutableListOf() val audioContentHashTags: MutableList<AudioContentHashTag> = mutableListOf()
@OneToMany(mappedBy = "child", cascade = [CascadeType.ALL])
var children: MutableList<BundleAudioContent> = mutableListOf()
} }

View File

@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.content
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.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
import kr.co.vividnext.sodalive.content.QBundleAudioContent.bundleAudioContent
import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent
import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment
import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike
@ -31,6 +32,7 @@ interface AudioContentRepository : JpaRepository<AudioContent, Long>, AudioConte
interface AudioContentQueryRepository { interface AudioContentQueryRepository {
fun findByIdAndActive(contentId: Long): AudioContent? fun findByIdAndActive(contentId: Long): AudioContent?
fun findByIdAndCreatorId(contentId: Long, creatorId: Long): AudioContent? fun findByIdAndCreatorId(contentId: Long, creatorId: Long): AudioContent?
fun findBundleByContentId(contentId: Long): List<AudioContent>
fun findByCreatorId( fun findByCreatorId(
creatorId: Long, creatorId: Long,
coverImageHost: String, coverImageHost: String,
@ -145,6 +147,18 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
.fetchOne() .fetchOne()
} }
// 해당 컨텐츠가 속한 묶음(번들) 상품 리스트 검색
override fun findBundleByContentId(contentId: Long): List<AudioContent> {
return queryFactory
.select(bundleAudioContent.parent)
.from(bundleAudioContent)
.where(
bundleAudioContent.child.id.eq(contentId)
.and(bundleAudioContent.child.isActive.isTrue)
)
.fetch()
}
override fun findByCreatorId( override fun findByCreatorId(
creatorId: Long, creatorId: Long,
coverImageHost: String, coverImageHost: String,

View File

@ -160,28 +160,25 @@ class AudioContentService(
} }
// contentFile 체크 // contentFile 체크
if (contentFile == null) { if (contentFile == null && request.type == AudioContentType.INDIVIDUAL) {
throw SodaException("콘텐츠를 선택해 주세요.") throw SodaException("콘텐츠를 선택해 주세요.")
} }
if (request.type == AudioContentType.BUNDLE && request.childIds == null) {
throw SodaException("묶음상품의 하위상품을 선택해 주세요.")
}
// 테마 체크 // 테마 체크
val theme = themeQueryRepository.findThemeByIdAndActive(id = request.themeId) val theme = themeQueryRepository.findThemeByIdAndActive(id = request.themeId)
?: throw SodaException("잘못된 테마입니다. 다시 선택해 주세요.") ?: throw SodaException("잘못된 테마입니다. 다시 선택해 주세요.")
if (request.price in 1..4) throw SodaException("콘텐츠의 최소금액은 5캔 입니다.") if (request.price in 1..4) throw SodaException("콘텐츠의 최소금액은 5캔 입니다.")
val isOnlyRental = if (request.limited != null && request.limited > 0) {
false
} else if (request.purchaseOption == PurchaseOption.RENT_ONLY) {
true
} else {
request.isOnlyRental
}
// DB에 값 추가 // DB에 값 추가
val audioContent = AudioContent( val audioContent = AudioContent(
title = request.title, title = request.title,
detail = request.detail, detail = request.detail,
type = request.type,
price = if (request.price > 0) { price = if (request.price > 0) {
request.price request.price
} else { } else {
@ -191,13 +188,18 @@ class AudioContentService(
limited = request.limited, limited = request.limited,
remaining = request.limited, remaining = request.limited,
isAdult = request.isAdult, isAdult = request.isAdult,
purchaseOption = request.purchaseOption, isGeneratePreview = if (request.type == AudioContentType.INDIVIDUAL) {
isGeneratePreview = request.isGeneratePreview, request.isGeneratePreview
isOnlyRental = isOnlyRental, } else {
false
},
isOnlyRental = if (request.limited != null && request.limited > 0) false else request.isOnlyRental,
isCommentAvailable = request.isCommentAvailable isCommentAvailable = request.isCommentAvailable
) )
audioContent.theme = theme audioContent.theme = theme
audioContent.member = member audioContent.member = member
audioContent.isActive = request.type == AudioContentType.BUNDLE
repository.save(audioContent) repository.save(audioContent)
// 태그 분리, #추가, 등록 // 태그 분리, #추가, 등록
@ -244,6 +246,7 @@ class AudioContentService(
audioContent.coverImage = coverImagePath audioContent.coverImage = coverImagePath
if (contentFile != null && request.type == AudioContentType.INDIVIDUAL) {
// 콘텐츠 파일명 생성 // 콘텐츠 파일명 생성
val contentFileName = generateFileName(prefix = "${audioContent.id}-content") val contentFileName = generateFileName(prefix = "${audioContent.id}-content")
@ -272,6 +275,19 @@ class AudioContentService(
) )
audioContent.content = contentPath audioContent.content = contentPath
}
if (request.childIds != null && request.type == AudioContentType.BUNDLE) {
for (childId in request.childIds) {
val childContent = repository.findByIdAndActive(childId)
?: continue
val bundleAudioContent = BundleAudioContent()
bundleAudioContent.parent = audioContent
bundleAudioContent.child = childContent
audioContent.children.add(bundleAudioContent)
}
}
return CreateAudioContentResponse(contentId = audioContent.id!!) return CreateAudioContentResponse(contentId = audioContent.id!!)
} }
@ -422,6 +438,9 @@ class AudioContentService(
} }
fun getDetail(id: Long, member: Member, timezone: String): GetAudioContentDetailResponse { fun getDetail(id: Long, member: Member, timezone: String): GetAudioContentDetailResponse {
// 묶음 콘텐츠 조회
val bundleAudioContentList = repository.findBundleByContentId(contentId = id)
// 오디오 콘텐츠 조회 (content_id, 제목, 내용, 테마, 태그, 19여부, 이미지, 콘텐츠 PATH) // 오디오 콘텐츠 조회 (content_id, 제목, 내용, 테마, 태그, 19여부, 이미지, 콘텐츠 PATH)
val audioContent = repository.findByIdOrNull(id) val audioContent = repository.findByIdOrNull(id)
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.") ?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
@ -436,16 +455,23 @@ class AudioContentService(
memberId = member.id!! memberId = member.id!!
) )
// 구매 여부 확인
val isExistsBundleAudioContent = bundleAudioContentList
.map { orderRepository.isExistOrdered(memberId = member.id!!, contentId = it.id!!) }
.contains(true)
val (isExistsAudioContent, orderType) = orderRepository.isExistOrderedAndOrderType( val (isExistsAudioContent, orderType) = orderRepository.isExistOrderedAndOrderType(
memberId = member.id!!, memberId = member.id!!,
contentId = audioContent.id!! contentId = audioContent.id!!
) )
val existOrdered = isExistsBundleAudioContent || isExistsAudioContent
// 차단된 사용자 체크 // 차단된 사용자 체크
val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = creatorId) val isBlocked = blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
if (isBlocked && !isExistsAudioContent) throw SodaException("${creator.nickname}님의 요청으로 콘텐츠 접근이 제한됩니다.") if (isBlocked && !existOrdered) throw SodaException("${creator.nickname}님의 요청으로 콘텐츠 접근이 제한됩니다.")
val orderSequence = if (isExistsAudioContent) { val orderSequence = if (existOrdered) {
limitedEditionOrderRepository.getOrderSequence( limitedEditionOrderRepository.getOrderSequence(
contentId = audioContent.id!!, contentId = audioContent.id!!,
memberId = member.id!! memberId = member.id!!
@ -455,7 +481,7 @@ class AudioContentService(
} }
if ( if (
!isExistsAudioContent && !existOrdered &&
!audioContent.isActive && !audioContent.isActive &&
audioContent.releaseDate != null && audioContent.releaseDate != null &&
audioContent.releaseDate!! < LocalDateTime.now() audioContent.releaseDate!! < LocalDateTime.now()
@ -510,6 +536,7 @@ class AudioContentService(
audioContentCloudFront.generateSignedURL( audioContentCloudFront.generateSignedURL(
resourcePath = if ( resourcePath = if (
isExistsAudioContent || isExistsAudioContent ||
isExistsBundleAudioContent ||
audioContent.member!!.id!! == member.id!! || audioContent.member!!.id!! == member.id!! ||
audioContent.price <= 0 audioContent.price <= 0
) { ) {
@ -577,18 +604,6 @@ class AudioContentService(
false false
} }
val isOnlyRental = if (audioContent.purchaseOption == PurchaseOption.RENT_ONLY) {
true
} else {
audioContent.isOnlyRental
}
val purchaseOption = if (audioContent.isOnlyRental) {
PurchaseOption.RENT_ONLY
} else {
audioContent.purchaseOption
}
return GetAudioContentDetailResponse( return GetAudioContentDetailResponse(
contentId = audioContent.id!!, contentId = audioContent.id!!,
title = audioContent.title, title = audioContent.title,
@ -606,9 +621,8 @@ class AudioContentService(
isActivePreview = audioContent.isGeneratePreview, isActivePreview = audioContent.isGeneratePreview,
isAdult = audioContent.isAdult, isAdult = audioContent.isAdult,
isMosaic = audioContent.isAdult && member.auth == null, isMosaic = audioContent.isAdult && member.auth == null,
isOnlyRental = isOnlyRental, isOnlyRental = audioContent.isOnlyRental,
existOrdered = isExistsAudioContent, existOrdered = existOrdered,
purchaseOption = purchaseOption,
orderType = orderType, orderType = orderType,
remainingTime = remainingTime, remainingTime = remainingTime,
creatorOtherContentList = creatorOtherContentList, creatorOtherContentList = creatorOtherContentList,

View File

@ -0,0 +1,22 @@
package kr.co.vividnext.sodalive.content
import kr.co.vividnext.sodalive.common.BaseEntity
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
import javax.persistence.Table
@Entity
@Table(name = "bundle_content")
data class BundleAudioContent(
var isActive: Boolean = true
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_content_id", nullable = false)
var parent: AudioContent? = null
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "child_content_id", nullable = false)
var child: AudioContent? = null
}

View File

@ -5,7 +5,6 @@ data class CreateAudioContentRequest(
val detail: String, val detail: String,
val tags: String, val tags: String,
val price: Int, val price: Int,
val purchaseOption: PurchaseOption = PurchaseOption.BOTH,
val limited: Int? = null, val limited: Int? = null,
val timezone: String = "Asia/Seoul", val timezone: String = "Asia/Seoul",
val releaseDate: String? = null, val releaseDate: String? = null,
@ -14,6 +13,8 @@ data class CreateAudioContentRequest(
val isGeneratePreview: Boolean = false, val isGeneratePreview: Boolean = false,
val isOnlyRental: Boolean = false, val isOnlyRental: Boolean = false,
val isCommentAvailable: Boolean = false, val isCommentAvailable: Boolean = false,
val type: AudioContentType = AudioContentType.INDIVIDUAL,
val childIds: List<Long>? = null,
val previewStartTime: String? = null, val previewStartTime: String? = null,
val previewEndTime: String? = null val previewEndTime: String? = null
) )

View File

@ -23,7 +23,6 @@ data class GetAudioContentDetailResponse(
val isMosaic: Boolean, val isMosaic: Boolean,
val isOnlyRental: Boolean, val isOnlyRental: Boolean,
val existOrdered: Boolean, val existOrdered: Boolean,
val purchaseOption: PurchaseOption,
val orderType: OrderType?, val orderType: OrderType?,
val remainingTime: String?, val remainingTime: String?,
val creatorOtherContentList: List<OtherContentResponse>, val creatorOtherContentList: List<OtherContentResponse>,

View File

@ -5,7 +5,6 @@ import kr.co.vividnext.sodalive.can.use.CanUsage
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.AudioContent import kr.co.vividnext.sodalive.content.AudioContent
import kr.co.vividnext.sodalive.content.AudioContentRepository import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.PurchaseOption
import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository
import kr.co.vividnext.sodalive.content.like.AudioContentLikeRepository import kr.co.vividnext.sodalive.content.like.AudioContentLikeRepository
import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem
@ -32,7 +31,7 @@ class OrderService(
fun order(contentId: Long, orderType: OrderType, container: String, member: Member) { fun order(contentId: Long, orderType: OrderType, container: String, member: Member) {
val content = audioContentRepository.findByIdAndActive(contentId) val content = audioContentRepository.findByIdAndActive(contentId)
?: throw SodaException("잘못된 콘텐츠 입니다\n다시 시도해 주세요.") ?: throw SodaException("잘못된 콘텐츠 입니다\n다시 시도해 주세요.")
validateOrder(memberId = member.id!!, content = content, orderType = orderType) validateOrder(memberId = member.id!!, content = content)
val order = if (content.limited != null && content.remaining != null) { val order = if (content.limited != null && content.remaining != null) {
if (content.remaining!! <= 0) throw SodaException("해당 콘텐츠가 매진되었습니다.") if (content.remaining!! <= 0) throw SodaException("해당 콘텐츠가 매진되었습니다.")
@ -76,17 +75,11 @@ class OrderService(
return order return order
} }
private fun validateOrder(memberId: Long, content: AudioContent, orderType: OrderType) { private fun validateOrder(memberId: Long, content: AudioContent) {
if (memberId == content.member!!.id!!) throw SodaException("자신이 올린 콘텐츠는 구매할 수 없습니다.") if (memberId == content.member!!.id!!) throw SodaException("자신이 올린 콘텐츠는 구매할 수 없습니다.")
if (repository.isExistOrdered(memberId = memberId, contentId = content.id!!)) {
val existOrdered = repository.isExistOrdered(memberId = memberId, contentId = content.id!!) throw SodaException("이미 구매한 콘텐츠 입니다.")
if (existOrdered) throw SodaException("이미 구매한 콘텐츠 입니다.") }
val isOnlyRental = content.purchaseOption == PurchaseOption.RENT_ONLY || content.isOnlyRental
if (isOnlyRental && orderType == OrderType.KEEP) throw SodaException("대여만 가능한 콘텐츠 입니다.")
val isOnlyBuy = content.purchaseOption == PurchaseOption.BUY_ONLY && orderType == OrderType.RENTAL
if (isOnlyBuy) throw SodaException("소장만 가능한 콘텐츠 입니다.\n앱 업데이트 후 구매해 주세요.")
} }
fun getAudioContentOrderList( fun getAudioContentOrderList(