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)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(audioContent.id.desc())
|
||||
.orderBy(audioContent.releaseDate.desc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
|
|
|
@ -196,4 +196,26 @@ class AudioContentController(private val service: AudioContentService) {
|
|||
fun releaseContent() = run {
|
||||
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.QAudioContentCuration.audioContentCuration
|
||||
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.event.QEvent.event
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
|
@ -33,11 +34,12 @@ interface AudioContentQueryRepository {
|
|||
fun findBundleByContentId(contentId: Long): List<AudioContent>
|
||||
fun findByCreatorId(
|
||||
creatorId: Long,
|
||||
coverImageHost: String,
|
||||
isAdult: Boolean = false,
|
||||
sortType: SortType = SortType.NEWEST,
|
||||
offset: Long = 0,
|
||||
limit: Long = 10
|
||||
): List<AudioContent>
|
||||
): List<GetAudioContentListItem>
|
||||
|
||||
fun findTotalCountByCreatorId(creatorId: Long, isAdult: Boolean = false): Int
|
||||
fun getCreatorOtherContentList(
|
||||
|
@ -139,11 +141,12 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
|||
|
||||
override fun findByCreatorId(
|
||||
creatorId: Long,
|
||||
coverImageHost: String,
|
||||
isAdult: Boolean,
|
||||
sortType: SortType,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<AudioContent> {
|
||||
): List<GetAudioContentListItem> {
|
||||
val orderBy = when (sortType) {
|
||||
SortType.NEWEST -> audioContent.releaseDate.desc()
|
||||
SortType.PRICE_HIGH -> audioContent.price.desc()
|
||||
|
@ -161,11 +164,28 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
|||
}
|
||||
|
||||
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)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(orderBy)
|
||||
.orderBy(pinContent.isActive.desc(), pinContent.updatedAt.desc(), orderBy)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
|
@ -594,6 +614,7 @@ class AudioContentQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
|||
val where = audioContent.isActive.isFalse
|
||||
.and(audioContent.releaseDate.isNotNull)
|
||||
.and(audioContent.releaseDate.loe(LocalDateTime.now()))
|
||||
.and(audioContent.duration.isNotNull)
|
||||
|
||||
return queryFactory
|
||||
.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.order.OrderRepository
|
||||
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.explorer.ExplorerQueryRepository
|
||||
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
|
||||
|
@ -49,6 +51,7 @@ class AudioContentService(
|
|||
private val playbackTrackingRepository: PlaybackTrackingRepository,
|
||||
private val commentRepository: AudioContentCommentRepository,
|
||||
private val audioContentLikeRepository: AudioContentLikeRepository,
|
||||
private val pinContentRepository: PinContentRepository,
|
||||
|
||||
private val s3Uploader: S3Uploader,
|
||||
private val objectMapper: ObjectMapper,
|
||||
|
@ -551,6 +554,25 @@ class AudioContentService(
|
|||
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(
|
||||
contentId = audioContent.id!!,
|
||||
title = audioContent.title,
|
||||
|
@ -576,6 +598,8 @@ class AudioContentService(
|
|||
likeCount = likeCount,
|
||||
commentList = commentList,
|
||||
commentCount = commentCount,
|
||||
isPin = isPin,
|
||||
isAvailablePin = isAvailablePin,
|
||||
creator = AudioContentCreator(
|
||||
creatorId = creatorId,
|
||||
nickname = creator.nickname,
|
||||
|
@ -603,6 +627,7 @@ class AudioContentService(
|
|||
|
||||
val audioContentList = repository.findByCreatorId(
|
||||
creatorId = creatorId,
|
||||
coverImageHost = coverImageHost,
|
||||
isAdult = member.auth != null,
|
||||
sortType = sortType,
|
||||
offset = offset,
|
||||
|
@ -612,23 +637,15 @@ class AudioContentService(
|
|||
val items = audioContentList
|
||||
.map {
|
||||
val commentCount = commentRepository
|
||||
.totalCountCommentByContentId(it.id!!)
|
||||
.totalCountCommentByContentId(it.contentId)
|
||||
|
||||
val likeCount = audioContentLikeRepository
|
||||
.totalCountAudioContentLike(it.id!!)
|
||||
.totalCountAudioContentLike(it.contentId)
|
||||
|
||||
GetAudioContentListItem(
|
||||
contentId = it.id!!,
|
||||
coverImageUrl = "$coverImageHost/${it.coverImage!!}",
|
||||
title = it.title,
|
||||
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()
|
||||
)
|
||||
it.likeCount = likeCount
|
||||
it.commentCount = commentCount
|
||||
|
||||
it
|
||||
}
|
||||
|
||||
return GetAudioContentListResponse(
|
||||
|
@ -696,4 +713,41 @@ class AudioContentService(
|
|||
fun getContentRankingSortTypeList(): List<String> {
|
||||
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 commentList: List<GetAudioContentCommentListItem>,
|
||||
val commentCount: Int,
|
||||
val isPin: Boolean,
|
||||
val isAvailablePin: Boolean,
|
||||
val creator: AudioContentCreator
|
||||
)
|
||||
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.content
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
|
||||
data class GetAudioContentListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetAudioContentListItem>
|
||||
)
|
||||
|
||||
data class GetAudioContentListItem(
|
||||
data class GetAudioContentListItem @QueryProjection constructor(
|
||||
val contentId: Long,
|
||||
val coverImageUrl: String,
|
||||
val title: String,
|
||||
val price: Int,
|
||||
val themeStr: String,
|
||||
val duration: String?,
|
||||
val likeCount: Int,
|
||||
val commentCount: Int,
|
||||
var likeCount: Int = 0,
|
||||
var commentCount: Int = 0,
|
||||
val isPin: Boolean,
|
||||
val isAdult: 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,
|
||||
userMember = member,
|
||||
timezone = timezone,
|
||||
limit = 4
|
||||
limit = 3
|
||||
)
|
||||
|
||||
// 오디오 콘텐츠
|
||||
|
@ -224,7 +224,7 @@ class ExplorerService(
|
|||
sortType = SortType.NEWEST,
|
||||
member = member,
|
||||
offset = 0,
|
||||
limit = 4
|
||||
limit = 3
|
||||
).items
|
||||
|
||||
// 공지사항
|
||||
|
|
Loading…
Reference in New Issue