Merge pull request '예약 업로드' (#113) from test into main

Reviewed-on: #113
This commit is contained in:
klaus 2024-01-10 16:59:51 +00:00
commit b82fdfb2c8
12 changed files with 167 additions and 157 deletions

View File

@ -5,6 +5,7 @@ import kr.co.vividnext.sodalive.content.hashtag.AudioContentHashTag
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
import kr.co.vividnext.sodalive.member.Member
import java.time.LocalDateTime
import javax.persistence.CascadeType
import javax.persistence.Column
import javax.persistence.Entity
@ -32,6 +33,7 @@ data class AudioContent(
@Column(columnDefinition = "TEXT", nullable = false)
var detail: String,
val price: Int = 0,
var releaseDate: LocalDateTime? = null,
@Enumerated(value = EnumType.STRING)
val type: AudioContentType = AudioContentType.INDIVIDUAL,
val isGeneratePreview: Boolean = true,

View File

@ -25,7 +25,10 @@ import java.time.temporal.TemporalAdjusters
@RestController
@RequestMapping("/audio-content")
class AudioContentController(private val service: AudioContentService) {
class AudioContentController(
private val service: AudioContentService,
private val repository: AudioContentRepository
) {
@PostMapping
@PreAuthorize("hasRole('CREATOR')")
fun createAudioContent(
@ -190,4 +193,16 @@ class AudioContentController(private val service: AudioContentService) {
)
)
}
@PostMapping("/release")
@PreAuthorize("hasRole('BOT')")
fun releaseContent() = run {
val contentIdList = repository.getNotReleaseContentId()
for (contentId in contentIdList) {
service.releaseContent(contentId)
}
ApiResponse.ok(null)
}
}

View File

@ -56,6 +56,7 @@ interface AudioContentQueryRepository {
fun findByTheme(
cloudfrontHost: String,
memberId: Long,
theme: String = "",
isAdult: Boolean = false,
offset: Long = 0,
@ -64,13 +65,14 @@ interface AudioContentQueryRepository {
fun findByThemeFor2Weeks(
cloudfrontHost: String,
memberId: Long,
theme: String = "",
isAdult: Boolean = false,
offset: Long = 0,
limit: Long = 20
): List<GetAudioContentMainItem>
fun totalCountNewContentFor2Weeks(theme: String, isAdult: Boolean): Int
fun totalCountNewContentFor2Weeks(theme: String, memberId: Long, isAdult: Boolean): Int
fun getNewContentUploadCreatorList(
cloudfrontHost: String,
@ -96,6 +98,8 @@ interface AudioContentQueryRepository {
): List<GetAudioContentRankingItem>
fun getAudioContentCurationList(isAdult: Boolean, offset: Long, limit: Long): List<AudioContentCuration>
fun getNotReleaseContentId(): List<Long>
}
@Repository
@ -146,8 +150,11 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
SortType.PRICE_LOW -> audioContent.price.asc()
}
var where = audioContent.isActive.isTrue
.and(audioContent.member.id.eq(creatorId))
var where = audioContent.member.id.eq(creatorId)
.and(
audioContent.isActive.isTrue
.or(audioContent.releaseDate.isNotNull.and(audioContent.duration.isNotNull))
)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
@ -166,8 +173,11 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
creatorId: Long,
isAdult: Boolean
): Int {
var where = audioContent.isActive.isTrue
.and(audioContent.member.id.eq(creatorId))
var where = audioContent.member.id.eq(creatorId)
.and(
audioContent.isActive.isTrue
.or(audioContent.releaseDate.isNotNull.and(audioContent.duration.isNotNull))
)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
@ -242,12 +252,18 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
override fun findByTheme(
cloudfrontHost: String,
memberId: Long,
theme: String,
isAdult: Boolean,
offset: Long,
limit: Long
): List<GetAudioContentMainItem> {
var where = audioContent.isActive.isTrue
.and(
audioContent.releaseDate.isNull
.or(audioContent.releaseDate.loe(LocalDateTime.now()))
.or(audioContent.member.id.eq(memberId))
)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
@ -278,9 +294,14 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
.fetch()
}
override fun totalCountNewContentFor2Weeks(theme: String, isAdult: Boolean): Int {
override fun totalCountNewContentFor2Weeks(theme: String, memberId: Long, isAdult: Boolean): Int {
var where = audioContent.isActive.isTrue
.and(audioContent.createdAt.goe(LocalDateTime.now().minusWeeks(2)))
.and(
audioContent.releaseDate.isNull
.or(audioContent.releaseDate.loe(LocalDateTime.now()))
.or(audioContent.member.id.eq(memberId))
)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
@ -302,6 +323,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
override fun findByThemeFor2Weeks(
cloudfrontHost: String,
memberId: Long,
theme: String,
isAdult: Boolean,
offset: Long,
@ -309,6 +331,11 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
): List<GetAudioContentMainItem> {
var where = audioContent.isActive.isTrue
.and(audioContent.createdAt.goe(LocalDateTime.now().minusWeeks(2)))
.and(
audioContent.releaseDate.isNull
.or(audioContent.releaseDate.loe(LocalDateTime.now()))
.or(audioContent.member.id.eq(memberId))
)
if (!isAdult) {
where = where.and(audioContent.isAdult.isFalse)
@ -562,4 +589,16 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
.orderBy(audioContentCuration.orders.asc())
.fetch()
}
override fun getNotReleaseContentId(): List<Long> {
val where = audioContent.isActive.isFalse
.and(audioContent.releaseDate.isNotNull)
.and(audioContent.releaseDate.loe(LocalDateTime.now()))
return queryFactory
.select(audioContent.id)
.from(audioContent)
.where(where)
.fetch()
}
}

View File

@ -18,6 +18,7 @@ import kr.co.vividnext.sodalive.content.order.OrderRepository
import kr.co.vividnext.sodalive.content.order.OrderType
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.member.Member
@ -125,6 +126,7 @@ class AudioContentService(
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
audioContent.isActive = false
audioContent.releaseDate = null
}
@Transactional
@ -143,6 +145,15 @@ class AudioContentService(
// 미리듣기 시간 체크
validatePreviewTime(request.previewStartTime, request.previewEndTime)
val releaseDate = if (request.releaseDate != null) {
request.releaseDate.convertLocalDateTime("yyyy-MM-dd HH:mm")
.atZone(ZoneId.of(request.timezone))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
} else {
null
}
// contentFile 체크
if (contentFile == null && request.type == AudioContentType.INDIVIDUAL) {
throw SodaException("콘텐츠를 선택해 주세요.")
@ -168,6 +179,7 @@ class AudioContentService(
} else {
0
},
releaseDate = releaseDate,
isAdult = request.isAdult,
isGeneratePreview = if (request.type == AudioContentType.INDIVIDUAL) {
request.isGeneratePreview
@ -331,7 +343,6 @@ class AudioContentService(
val audioContent = repository.findByIdOrNull(contentId)
?: throw SodaException("잘못된 요청입니다.")
audioContent.isActive = true
audioContent.content = content
audioContent.duration = duration
@ -346,6 +357,42 @@ class AudioContentService(
)
)
if (audioContent.releaseDate == null) {
audioContent.isActive = true
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
isAuth = audioContent.isAdult,
contentId = contentId,
creatorId = audioContent.member!!.id,
container = "ios"
)
)
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
title = audioContent.member!!.nickname,
message = "콘텐츠를 업로드 하였습니다. - ${audioContent.title}",
isAuth = audioContent.isAdult,
contentId = contentId,
creatorId = audioContent.member!!.id,
container = "aos"
)
)
}
}
@Transactional
fun releaseContent(contentId: Long) {
val audioContent = repository.findByIdOrNull(contentId)
?: throw SodaException("잘못된 요청입니다.")
audioContent.isActive = true
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.UPLOAD_CONTENT,
@ -401,7 +448,12 @@ class AudioContentService(
contentId = audioContent.id!!
)
if (!isExistsAudioContent && !isExistsBundleAudioContent && !audioContent.isActive) {
if (
!isExistsAudioContent &&
!isExistsBundleAudioContent &&
!audioContent.isActive &&
audioContent.releaseDate == null
) {
throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
}
@ -425,7 +477,25 @@ class AudioContentService(
0
}
val audioContentUrl = audioContentCloudFront.generateSignedURL(
val releaseDate = if (
audioContent.releaseDate != null &&
audioContent.releaseDate!! >= LocalDateTime.now()
) {
audioContent.releaseDate!!
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분 오픈예정"))
} else {
null
}
val audioContentUrl = if (
audioContent.releaseDate == null ||
audioContent.releaseDate!! <= LocalDateTime.now() ||
creatorId == member.id!!
) {
audioContentCloudFront.generateSignedURL(
resourcePath = if (
isExistsAudioContent ||
isExistsBundleAudioContent ||
@ -438,6 +508,9 @@ class AudioContentService(
},
expirationTime = 1000 * 60 * 60 * (audioContent.duration!!.split(":")[0].toLong() + 2)
)
} else {
""
}
val tag = audioContent.audioContentHashTags
.map { it.hashTag!!.tag }
@ -483,6 +556,7 @@ class AudioContentService(
tag = tag,
price = audioContent.price,
duration = audioContent.duration ?: "",
releaseDate = releaseDate,
isAdult = audioContent.isAdult,
isMosaic = audioContent.isAdult && member.auth == null,
isOnlyRental = audioContent.isOnlyRental,
@ -546,7 +620,8 @@ class AudioContentService(
duration = it.duration,
likeCount = likeCount,
commentCount = commentCount,
isAdult = it.isAdult
isAdult = it.isAdult,
isScheduledToOpen = it.releaseDate != null && it.releaseDate!! > LocalDateTime.now()
)
}

View File

@ -5,6 +5,8 @@ data class CreateAudioContentRequest(
val detail: String,
val tags: String,
val price: Int,
val timezone: String,
val releaseDate: String? = null,
val themeId: Long = 0,
val isAdult: Boolean = false,
val isGeneratePreview: Boolean = true,

View File

@ -14,6 +14,7 @@ data class GetAudioContentDetailResponse(
val tag: String,
val price: Int,
val duration: String,
val releaseDate: String?,
val isAdult: Boolean,
val isMosaic: Boolean,
val isOnlyRental: Boolean,

View File

@ -14,5 +14,6 @@ data class GetAudioContentListItem(
val duration: String?,
val likeCount: Int,
val commentCount: Int,
val isAdult: Boolean
val isAdult: Boolean,
val isScheduledToOpen: Boolean
)

View File

@ -15,19 +15,8 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/audio-content/main")
class AudioContentMainController(
private val service: AudioContentMainService,
private val orderService: OrderService,
private val manageService: AudioContentMainManageService
private val orderService: OrderService
) {
@GetMapping
fun getMain(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
ApiResponse.ok(manageService.getMain(member))
}
@GetMapping("/new-content-upload-creator")
fun newContentUploadCreatorList(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?

View File

@ -1,94 +0,0 @@
package kr.co.vividnext.sodalive.content.main
import kr.co.vividnext.sodalive.content.AudioContentRepository
import kr.co.vividnext.sodalive.content.AudioContentService
import kr.co.vividnext.sodalive.content.order.OrderService
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.block.BlockMemberRepository
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.DayOfWeek
import java.time.LocalDateTime
import java.time.temporal.TemporalAdjusters
@Service
class AudioContentMainManageService(
private val service: AudioContentMainService,
private val orderService: OrderService,
private val audioContentService: AudioContentService,
private val repository: AudioContentRepository,
private val blockMemberRepository: BlockMemberRepository,
private val audioContentThemeRepository: AudioContentThemeQueryRepository,
@Value("\${cloud.aws.cloud-front.host}")
private val imageHost: String
) {
@Transactional(readOnly = true)
fun getMain(member: Member): GetAudioContentMainResponse {
val memberId = member.id!!
val isAdult = member.auth != null
val newContentUploadCreatorList = service.getNewContentUploadCreatorList(
memberId = memberId,
isAdult = isAdult
)
val bannerList = service.getAudioContentMainBannerList(memberId = memberId, isAdult = isAdult)
val orderList = orderService.getAudioContentMainOrderList(
memberId = memberId,
limit = 20
)
// 콘텐츠 테마
val themeList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult)
// 새 콘텐츠 20개 - 시간 내림차순 정렬
val newContentList = repository.findByTheme(
cloudfrontHost = imageHost,
isAdult = isAdult
)
.asSequence()
.filter {
!blockMemberRepository.isBlocked(
blockedMemberId = memberId,
memberId = it.creatorId
)
}
.toList()
val curationList = service.getAudioContentCurationList(memberId = memberId, isAdult = isAdult)
val currentDateTime = LocalDateTime.now()
val startDate = currentDateTime
.minusWeeks(1)
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
.withHour(15)
.withMinute(0)
.withSecond(0)
val endDate = startDate.plusDays(7)
val contentRankingSortTypeList = audioContentService.getContentRankingSortTypeList()
val contentRanking = audioContentService.getAudioContentRanking(
isAdult = isAdult,
startDate = startDate,
endDate = endDate,
offset = 0,
limit = 12
)
return GetAudioContentMainResponse(
newContentUploadCreatorList = newContentUploadCreatorList,
bannerList = bannerList,
orderList = orderList,
themeList = themeList,
newContentList = newContentList,
curationList = curationList,
contentRankingSortTypeList = contentRankingSortTypeList,
contentRanking = contentRanking
)
}
}

View File

@ -33,6 +33,7 @@ class AudioContentMainService(
fun getNewContentByTheme(theme: String, member: Member, pageable: Pageable): List<GetAudioContentMainItem> {
return repository.findByTheme(
cloudfrontHost = imageHost,
memberId = member.id!!,
theme = theme,
isAdult = member.auth != null,
offset = pageable.offset,
@ -45,9 +46,11 @@ class AudioContentMainService(
@Transactional(readOnly = true)
fun getNewContentFor2WeeksByTheme(theme: String, member: Member, pageable: Pageable): GetNewContentAllResponse {
val totalCount = repository.totalCountNewContentFor2Weeks(theme, isAdult = member.auth != null)
val totalCount =
repository.totalCountNewContentFor2Weeks(theme, memberId = member.id!!, isAdult = member.auth != null)
val items = repository.findByThemeFor2Weeks(
cloudfrontHost = imageHost,
memberId = member.id!!,
theme = theme,
isAdult = member.auth != null,
offset = pageable.offset,
@ -119,31 +122,6 @@ class AudioContentMainService(
}
.toList()
@Transactional(readOnly = true)
@Cacheable(cacheNames = ["default"], key = "'contentCurationList:' + #memberId + ':' + #isAdult")
fun getAudioContentCurationList(memberId: Long, isAdult: Boolean) =
repository.getAudioContentCurations(isAdult = isAdult)
.asSequence()
.map {
GetAudioContentCurationResponse(
curationId = it.id!!,
title = it.title,
description = it.description,
contents = repository.findAudioContentByCurationId(
curationId = it.id!!,
cloudfrontHost = imageHost,
isAdult = isAdult
)
.asSequence()
.filter { content ->
!blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = content.creatorId)
}
.toList()
)
}
.filter { it.contents.isNotEmpty() }
.toList()
@Transactional(readOnly = true)
@Cacheable(
cacheNames = ["default"],

View File

@ -161,6 +161,7 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde
.from(order)
.where(
order.member.id.eq(memberId)
.and(order.isActive.isTrue)
.and(
order.type.eq(OrderType.KEEP)
.or(
@ -199,6 +200,7 @@ class OrderQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Orde
.innerJoin(audioContent.member, member)
.where(
order.member.id.eq(memberId)
.and(order.isActive.isTrue)
.and(
order.type.eq(OrderType.KEEP)
.or(

View File

@ -97,7 +97,7 @@ class AuthService(
repository.save(auth)
} else {
throw SodaException("19세 미만 인증 오류")
throw SodaException("2005년 1월 1일 이전 출생자만 본인인증이 가능합니다.")
}
}
}