Merge pull request '콘텐츠 상단 고정 기능 추가' (#120) from test into main
Reviewed-on: #120
This commit is contained in:
commit
a91db4f956
|
@ -83,7 +83,7 @@ class AdminAudioContentQueryRepositoryImpl(
|
||||||
.where(where)
|
.where(where)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(audioContent.id.desc())
|
.orderBy(audioContent.releaseDate.desc())
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -196,4 +196,26 @@ class AudioContentController(private val service: AudioContentService) {
|
||||||
fun releaseContent() = run {
|
fun releaseContent() = run {
|
||||||
ApiResponse.ok(service.releaseContent())
|
ApiResponse.ok(service.releaseContent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pin-to-the-top/{id}")
|
||||||
|
@PreAuthorize("hasRole('CREATOR')")
|
||||||
|
fun pinToTheTop(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.pinToTheTop(contentId = id, member = member))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/unpin-at-the-top/{id}")
|
||||||
|
@PreAuthorize("hasRole('CREATOR')")
|
||||||
|
fun unpinAtTheTop(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
|
||||||
|
ApiResponse.ok(service.unpinAtTheTop(contentId = id, member = member))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioCon
|
||||||
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||||
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
||||||
import kr.co.vividnext.sodalive.content.order.QOrder.order
|
import kr.co.vividnext.sodalive.content.order.QOrder.order
|
||||||
|
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
|
||||||
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||||
import kr.co.vividnext.sodalive.event.QEvent.event
|
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
@ -33,11 +34,12 @@ interface AudioContentQueryRepository {
|
||||||
fun findBundleByContentId(contentId: Long): List<AudioContent>
|
fun findBundleByContentId(contentId: Long): List<AudioContent>
|
||||||
fun findByCreatorId(
|
fun findByCreatorId(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
|
coverImageHost: String,
|
||||||
isAdult: Boolean = false,
|
isAdult: Boolean = false,
|
||||||
sortType: SortType = SortType.NEWEST,
|
sortType: SortType = SortType.NEWEST,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 10
|
limit: Long = 10
|
||||||
): List<AudioContent>
|
): List<GetAudioContentListItem>
|
||||||
|
|
||||||
fun findTotalCountByCreatorId(creatorId: Long, isAdult: Boolean = false): Int
|
fun findTotalCountByCreatorId(creatorId: Long, isAdult: Boolean = false): Int
|
||||||
fun getCreatorOtherContentList(
|
fun getCreatorOtherContentList(
|
||||||
|
@ -139,11 +141,12 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
||||||
|
|
||||||
override fun findByCreatorId(
|
override fun findByCreatorId(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
|
coverImageHost: String,
|
||||||
isAdult: Boolean,
|
isAdult: Boolean,
|
||||||
sortType: SortType,
|
sortType: SortType,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long
|
||||||
): List<AudioContent> {
|
): List<GetAudioContentListItem> {
|
||||||
val orderBy = when (sortType) {
|
val orderBy = when (sortType) {
|
||||||
SortType.NEWEST -> audioContent.releaseDate.desc()
|
SortType.NEWEST -> audioContent.releaseDate.desc()
|
||||||
SortType.PRICE_HIGH -> audioContent.price.desc()
|
SortType.PRICE_HIGH -> audioContent.price.desc()
|
||||||
|
@ -161,11 +164,28 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectFrom(audioContent)
|
.select(
|
||||||
|
QGetAudioContentListItem(
|
||||||
|
audioContent.id,
|
||||||
|
audioContent.coverImage.prepend("/").prepend(coverImageHost),
|
||||||
|
audioContent.title,
|
||||||
|
audioContent.price,
|
||||||
|
audioContent.theme.theme,
|
||||||
|
audioContent.duration,
|
||||||
|
Expressions.constant(0),
|
||||||
|
Expressions.constant(0),
|
||||||
|
pinContent.id.isNotNull,
|
||||||
|
audioContent.isAdult,
|
||||||
|
audioContent.releaseDate.gt(LocalDateTime.now())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(audioContent)
|
||||||
|
.leftJoin(pinContent)
|
||||||
|
.on(audioContent.id.eq(pinContent.content.id).and(pinContent.isActive.ne(false)))
|
||||||
.where(where)
|
.where(where)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.orderBy(orderBy)
|
.orderBy(pinContent.isActive.desc(), pinContent.updatedAt.desc(), orderBy)
|
||||||
.fetch()
|
.fetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +614,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
||||||
val where = audioContent.isActive.isFalse
|
val where = audioContent.isActive.isFalse
|
||||||
.and(audioContent.releaseDate.isNotNull)
|
.and(audioContent.releaseDate.isNotNull)
|
||||||
.and(audioContent.releaseDate.loe(LocalDateTime.now()))
|
.and(audioContent.releaseDate.loe(LocalDateTime.now()))
|
||||||
|
.and(audioContent.duration.isNotNull)
|
||||||
|
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.select(audioContent.id)
|
.select(audioContent.id)
|
||||||
|
|
|
@ -16,6 +16,8 @@ import kr.co.vividnext.sodalive.content.like.PutAudioContentLikeResponse
|
||||||
import kr.co.vividnext.sodalive.content.main.GetAudioContentRanking
|
import kr.co.vividnext.sodalive.content.main.GetAudioContentRanking
|
||||||
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
import kr.co.vividnext.sodalive.content.order.OrderRepository
|
||||||
import kr.co.vividnext.sodalive.content.order.OrderType
|
import kr.co.vividnext.sodalive.content.order.OrderType
|
||||||
|
import kr.co.vividnext.sodalive.content.pin.PinContent
|
||||||
|
import kr.co.vividnext.sodalive.content.pin.PinContentRepository
|
||||||
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
|
import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository
|
||||||
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
import kr.co.vividnext.sodalive.explorer.ExplorerQueryRepository
|
||||||
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||||
|
@ -49,6 +51,7 @@ class AudioContentService(
|
||||||
private val playbackTrackingRepository: PlaybackTrackingRepository,
|
private val playbackTrackingRepository: PlaybackTrackingRepository,
|
||||||
private val commentRepository: AudioContentCommentRepository,
|
private val commentRepository: AudioContentCommentRepository,
|
||||||
private val audioContentLikeRepository: AudioContentLikeRepository,
|
private val audioContentLikeRepository: AudioContentLikeRepository,
|
||||||
|
private val pinContentRepository: PinContentRepository,
|
||||||
|
|
||||||
private val s3Uploader: S3Uploader,
|
private val s3Uploader: S3Uploader,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
|
@ -551,6 +554,25 @@ class AudioContentService(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pinContent = pinContentRepository.findByContentIdAndMemberId(
|
||||||
|
contentId = id,
|
||||||
|
memberId = member.id!!,
|
||||||
|
active = true
|
||||||
|
)
|
||||||
|
|
||||||
|
val isPin = if (member.id!! == audioContent.member!!.id!!) {
|
||||||
|
pinContent != null
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
val pinContentListCount = pinContentRepository.getPinContentList(memberId = member.id!!, active = true).size
|
||||||
|
val isAvailablePin = if (member.id!! == audioContent.member!!.id!!) {
|
||||||
|
pinContentListCount < 3
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
return GetAudioContentDetailResponse(
|
return GetAudioContentDetailResponse(
|
||||||
contentId = audioContent.id!!,
|
contentId = audioContent.id!!,
|
||||||
title = audioContent.title,
|
title = audioContent.title,
|
||||||
|
@ -576,6 +598,8 @@ class AudioContentService(
|
||||||
likeCount = likeCount,
|
likeCount = likeCount,
|
||||||
commentList = commentList,
|
commentList = commentList,
|
||||||
commentCount = commentCount,
|
commentCount = commentCount,
|
||||||
|
isPin = isPin,
|
||||||
|
isAvailablePin = isAvailablePin,
|
||||||
creator = AudioContentCreator(
|
creator = AudioContentCreator(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = creator.nickname,
|
nickname = creator.nickname,
|
||||||
|
@ -603,6 +627,7 @@ class AudioContentService(
|
||||||
|
|
||||||
val audioContentList = repository.findByCreatorId(
|
val audioContentList = repository.findByCreatorId(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
|
coverImageHost = coverImageHost,
|
||||||
isAdult = member.auth != null,
|
isAdult = member.auth != null,
|
||||||
sortType = sortType,
|
sortType = sortType,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
|
@ -612,23 +637,15 @@ class AudioContentService(
|
||||||
val items = audioContentList
|
val items = audioContentList
|
||||||
.map {
|
.map {
|
||||||
val commentCount = commentRepository
|
val commentCount = commentRepository
|
||||||
.totalCountCommentByContentId(it.id!!)
|
.totalCountCommentByContentId(it.contentId)
|
||||||
|
|
||||||
val likeCount = audioContentLikeRepository
|
val likeCount = audioContentLikeRepository
|
||||||
.totalCountAudioContentLike(it.id!!)
|
.totalCountAudioContentLike(it.contentId)
|
||||||
|
|
||||||
GetAudioContentListItem(
|
it.likeCount = likeCount
|
||||||
contentId = it.id!!,
|
it.commentCount = commentCount
|
||||||
coverImageUrl = "$coverImageHost/${it.coverImage!!}",
|
|
||||||
title = it.title,
|
it
|
||||||
price = it.price,
|
|
||||||
themeStr = it.theme!!.theme,
|
|
||||||
duration = it.duration,
|
|
||||||
likeCount = likeCount,
|
|
||||||
commentCount = commentCount,
|
|
||||||
isAdult = it.isAdult,
|
|
||||||
isScheduledToOpen = it.releaseDate != null && it.releaseDate!! > LocalDateTime.now()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAudioContentListResponse(
|
return GetAudioContentListResponse(
|
||||||
|
@ -696,4 +713,41 @@ class AudioContentService(
|
||||||
fun getContentRankingSortTypeList(): List<String> {
|
fun getContentRankingSortTypeList(): List<String> {
|
||||||
return listOf("매출", "댓글", "좋아요")
|
return listOf("매출", "댓글", "좋아요")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun pinToTheTop(contentId: Long, member: Member) {
|
||||||
|
val audioContent = repository.findByIdAndCreatorId(contentId = contentId, creatorId = member.id!!)
|
||||||
|
?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
|
||||||
|
|
||||||
|
var pinContent = pinContentRepository.findByContentIdAndMemberId(
|
||||||
|
contentId = contentId,
|
||||||
|
memberId = member.id!!
|
||||||
|
)
|
||||||
|
|
||||||
|
if (pinContent != null) {
|
||||||
|
pinContent.isActive = true
|
||||||
|
} else {
|
||||||
|
val pinContentList = pinContentRepository.getPinContentList(memberId = member.id!!)
|
||||||
|
pinContent = if (pinContentList.size >= 3) {
|
||||||
|
pinContentList[0]
|
||||||
|
} else {
|
||||||
|
PinContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
pinContent.isActive = true
|
||||||
|
pinContent.member = member
|
||||||
|
pinContent.content = audioContent
|
||||||
|
pinContentRepository.save(pinContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun unpinAtTheTop(contentId: Long, member: Member) {
|
||||||
|
val pinContent = pinContentRepository.findByContentIdAndMemberId(
|
||||||
|
contentId = contentId,
|
||||||
|
memberId = member.id!!
|
||||||
|
) ?: throw SodaException("잘못된 콘텐츠 입니다.\n다시 시도해 주세요.")
|
||||||
|
|
||||||
|
pinContent.isActive = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ data class GetAudioContentDetailResponse(
|
||||||
val likeCount: Int,
|
val likeCount: Int,
|
||||||
val commentList: List<GetAudioContentCommentListItem>,
|
val commentList: List<GetAudioContentCommentListItem>,
|
||||||
val commentCount: Int,
|
val commentCount: Int,
|
||||||
|
val isPin: Boolean,
|
||||||
|
val isAvailablePin: Boolean,
|
||||||
val creator: AudioContentCreator
|
val creator: AudioContentCreator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
package kr.co.vividnext.sodalive.content
|
package kr.co.vividnext.sodalive.content
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
data class GetAudioContentListResponse(
|
data class GetAudioContentListResponse(
|
||||||
val totalCount: Int,
|
val totalCount: Int,
|
||||||
val items: List<GetAudioContentListItem>
|
val items: List<GetAudioContentListItem>
|
||||||
)
|
)
|
||||||
|
|
||||||
data class GetAudioContentListItem(
|
data class GetAudioContentListItem @QueryProjection constructor(
|
||||||
val contentId: Long,
|
val contentId: Long,
|
||||||
val coverImageUrl: String,
|
val coverImageUrl: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val price: Int,
|
val price: Int,
|
||||||
val themeStr: String,
|
val themeStr: String,
|
||||||
val duration: String?,
|
val duration: String?,
|
||||||
val likeCount: Int,
|
var likeCount: Int = 0,
|
||||||
val commentCount: Int,
|
var commentCount: Int = 0,
|
||||||
|
val isPin: Boolean,
|
||||||
val isAdult: Boolean,
|
val isAdult: Boolean,
|
||||||
val isScheduledToOpen: Boolean
|
val isScheduledToOpen: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package kr.co.vividnext.sodalive.content.pin
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.FetchType
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class PinContent(var isActive: Boolean = true) : BaseEntity() {
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
|
var member: Member? = null
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "content_id", nullable = false)
|
||||||
|
var content: AudioContent? = null
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package kr.co.vividnext.sodalive.content.pin
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.content.pin.QPinContent.pinContent
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
|
interface PinContentRepository : JpaRepository<PinContent, Long>, PinContentQueryRepository
|
||||||
|
|
||||||
|
interface PinContentQueryRepository {
|
||||||
|
fun getPinContentList(memberId: Long, active: Boolean? = null): List<PinContent>
|
||||||
|
|
||||||
|
fun findByContentIdAndMemberId(contentId: Long, memberId: Long, active: Boolean? = null): PinContent?
|
||||||
|
}
|
||||||
|
|
||||||
|
class PinContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : PinContentQueryRepository {
|
||||||
|
override fun getPinContentList(memberId: Long, active: Boolean?): List<PinContent> {
|
||||||
|
var where = pinContent.member.id.eq(memberId)
|
||||||
|
|
||||||
|
if (active != null) {
|
||||||
|
where = where.and(pinContent.isActive.eq(active))
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(pinContent)
|
||||||
|
.where(where)
|
||||||
|
.orderBy(pinContent.isActive.asc(), pinContent.updatedAt.asc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByContentIdAndMemberId(contentId: Long, memberId: Long, active: Boolean?): PinContent? {
|
||||||
|
var where = pinContent.content.id.eq(contentId)
|
||||||
|
.and(pinContent.member.id.eq(memberId))
|
||||||
|
|
||||||
|
if (active != null) {
|
||||||
|
where = where
|
||||||
|
.and(pinContent.isActive.eq(active))
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(pinContent)
|
||||||
|
.where(where)
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
|
}
|
|
@ -215,7 +215,7 @@ class ExplorerService(
|
||||||
creatorId,
|
creatorId,
|
||||||
userMember = member,
|
userMember = member,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
limit = 4
|
limit = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// 오디오 콘텐츠
|
// 오디오 콘텐츠
|
||||||
|
@ -224,7 +224,7 @@ class ExplorerService(
|
||||||
sortType = SortType.NEWEST,
|
sortType = SortType.NEWEST,
|
||||||
member = member,
|
member = member,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 4
|
limit = 3
|
||||||
).items
|
).items
|
||||||
|
|
||||||
// 공지사항
|
// 공지사항
|
||||||
|
|
Loading…
Reference in New Issue