test #1
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class CreatorResponse(
|
||||
val creatorId: Long,
|
||||
val profileUrl: String,
|
||||
val nickname: String,
|
||||
val tags: List<String>,
|
||||
val introduce: String = "",
|
||||
val instagramUrl: String? = null,
|
||||
val youtubeUrl: String? = null,
|
||||
val websiteUrl: String? = null,
|
||||
val blogUrl: String? = null,
|
||||
val isNotification: Boolean,
|
||||
val notificationRecipientCount: Int
|
||||
)
|
|
@ -2,9 +2,18 @@ package kr.co.vividnext.sodalive.explorer
|
|||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PostCreatorNoticeRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.domain.Pageable
|
||||
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.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
@ -29,4 +38,52 @@ class ExplorerController(private val service: ExplorerService) {
|
|||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.getSearchChannel(channel, member))
|
||||
}
|
||||
|
||||
@GetMapping("/profile/{id}")
|
||||
fun getCreatorProfile(
|
||||
@PathVariable("id") creatorId: Long,
|
||||
@RequestParam timezone: String,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.getCreatorProfile(creatorId, timezone, member))
|
||||
}
|
||||
|
||||
@PostMapping("/profile/cheers")
|
||||
fun writeCheers(
|
||||
@RequestBody request: PostWriteCheersRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.writeCheers(request, member), "등록되었습니다.")
|
||||
}
|
||||
|
||||
@PutMapping("/profile/cheers")
|
||||
fun modifyCheers(
|
||||
@RequestBody request: PutWriteCheersRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.modifyCheers(request, member), "수정되었습니다.")
|
||||
}
|
||||
|
||||
@PostMapping("/profile/notice")
|
||||
@PreAuthorize("hasAnyRole('CREATOR')")
|
||||
fun postCreatorNotice(
|
||||
@RequestBody request: PostCreatorNoticeRequest,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.saveNotice(member, request.notice), "공지사항이 저장되었습니다.")
|
||||
}
|
||||
|
||||
@GetMapping("/profile/{id}/follower-list")
|
||||
fun getFollowerList(
|
||||
@PathVariable("id") creatorId: Long,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||
pageable: Pageable
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.getFollowerList(creatorId, member, pageable))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,40 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import com.querydsl.core.types.Predicate
|
||||
import com.querydsl.core.types.Projections
|
||||
import com.querydsl.core.types.dsl.Expressions
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||
import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListDto
|
||||
import kr.co.vividnext.sodalive.explorer.follower.QGetFollowerListDto
|
||||
import kr.co.vividnext.sodalive.explorer.profile.ChannelNotice
|
||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
|
||||
import kr.co.vividnext.sodalive.explorer.profile.QChannelNotice.channelNotice
|
||||
import kr.co.vividnext.sodalive.explorer.profile.QCreatorCheers.creatorCheers
|
||||
import kr.co.vividnext.sodalive.explorer.profile.TimeDifferenceResult
|
||||
import kr.co.vividnext.sodalive.explorer.section.ExplorerSection
|
||||
import kr.co.vividnext.sodalive.explorer.section.QExplorerSection.explorerSection
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
import kr.co.vividnext.sodalive.live.room.cancel.QLiveRoomCancel.liveRoomCancel
|
||||
import kr.co.vividnext.sodalive.live.room.visit.QLiveRoomVisit.liveRoomVisit
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.QMember
|
||||
import kr.co.vividnext.sodalive.member.QMember.member
|
||||
import kr.co.vividnext.sodalive.member.auth.QAuth.auth
|
||||
import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing
|
||||
import kr.co.vividnext.sodalive.member.tag.QCreatorTag.creatorTag
|
||||
import kr.co.vividnext.sodalive.member.tag.QMemberCreatorTag.memberCreatorTag
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@Repository
|
||||
class ExplorerQueryRepository(
|
||||
|
@ -141,4 +159,442 @@ class ExplorerQueryRepository(
|
|||
)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getAccount(userId: Long): Member? {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.id.eq(userId))
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getUserDonationRanking(
|
||||
creatorId: Long,
|
||||
limit: Long,
|
||||
offset: Long = 0,
|
||||
withDonationCan: Boolean
|
||||
): List<UserDonationRankingResponse> {
|
||||
val creatorMember = QMember("creator")
|
||||
val userMember = QMember("user")
|
||||
|
||||
val donation = useCan.rewardCan.add(useCan.can).sum()
|
||||
return queryFactory
|
||||
.select(userMember, donation)
|
||||
.from(useCan)
|
||||
.join(useCan.room, liveRoom)
|
||||
.join(liveRoom.member, creatorMember)
|
||||
.join(useCan.member, userMember)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.where(
|
||||
useCan.canUsage.eq(CanUsage.DONATION)
|
||||
.and(useCan.isRefund.isFalse)
|
||||
.and(creatorMember.id.eq(creatorId))
|
||||
)
|
||||
.groupBy(useCan.member.id)
|
||||
.orderBy(donation.desc(), userMember.id.desc())
|
||||
.fetch()
|
||||
.map {
|
||||
val account = it.get(userMember)!!
|
||||
val donationCan = it.get(donation)!!
|
||||
UserDonationRankingResponse(
|
||||
account.id!!,
|
||||
account.nickname,
|
||||
if (account.profileImage != null) {
|
||||
"$cloudFrontHost/${account.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
if (withDonationCan) donationCan else 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSimilarCreatorList(creatorId: Long): List<SimilarCreatorResponse> {
|
||||
val creator = queryFactory
|
||||
.selectFrom(member)
|
||||
.where(member.id.eq(creatorId))
|
||||
.fetchFirst() ?: throw SodaException("없는 사용자 입니다.")
|
||||
|
||||
val creatorTagCount = creator.tags
|
||||
.asSequence()
|
||||
.filter { it.tag.isActive }
|
||||
.map { it.tag }
|
||||
.toList().size
|
||||
|
||||
if (creatorTagCount <= 0) {
|
||||
val where = member.role.eq(MemberRole.CREATOR)
|
||||
.and(member.id.ne(creatorId))
|
||||
.and(member.tags.size().gt(0))
|
||||
.and(memberCreatorTag.tag.isActive.isTrue)
|
||||
|
||||
return getRandomCreatorList(where = where, limit = 3)
|
||||
}
|
||||
|
||||
val cnt = memberCreatorTag.member.count()
|
||||
val similarCreators = queryFactory
|
||||
.select(member, cnt)
|
||||
.from(memberCreatorTag)
|
||||
.innerJoin(memberCreatorTag.member, member)
|
||||
.innerJoin(memberCreatorTag.tag, creatorTag)
|
||||
.leftJoin(member.auth, auth)
|
||||
.where(
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
.and(member.id.ne(creatorId))
|
||||
.and(
|
||||
memberCreatorTag.tag.`in`(
|
||||
creator.tags
|
||||
.asSequence()
|
||||
.filter { it.tag.isActive }
|
||||
.map { it.tag }
|
||||
.toList()
|
||||
)
|
||||
)
|
||||
)
|
||||
.groupBy(memberCreatorTag.member.id)
|
||||
.orderBy(cnt.desc())
|
||||
.limit(3)
|
||||
.fetch()
|
||||
.map {
|
||||
val account = it.get(member)!!
|
||||
SimilarCreatorResponse(
|
||||
account.id!!,
|
||||
account.nickname,
|
||||
if (account.profileImage != null) {
|
||||
"$cloudFrontHost/${account.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
account.tags
|
||||
.asSequence()
|
||||
.filter { accountCounselorTag -> accountCounselorTag.tag.isActive }
|
||||
.map { accountCounselorTag -> accountCounselorTag.tag.tag }
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
|
||||
if (similarCreators.size < 3) {
|
||||
val userIds = similarCreators.map { it.userId }
|
||||
var where = member.role.eq(MemberRole.CREATOR)
|
||||
.and(member.id.ne(creatorId))
|
||||
.and(member.tags.size().gt(0))
|
||||
.and(memberCreatorTag.tag.isActive.isTrue)
|
||||
|
||||
for (userId in userIds) {
|
||||
where = where.and(member.id.ne(userId))
|
||||
}
|
||||
|
||||
val additionalCreator = getRandomCreatorList(where = where, limit = (3 - similarCreators.size).toLong())
|
||||
return similarCreators + additionalCreator
|
||||
} else {
|
||||
return similarCreators
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRandomCreatorList(where: Predicate, limit: Long): List<SimilarCreatorResponse> {
|
||||
val result = queryFactory
|
||||
.select(member)
|
||||
.from(memberCreatorTag)
|
||||
.join(memberCreatorTag.member, member)
|
||||
.innerJoin(memberCreatorTag.tag, creatorTag)
|
||||
.leftJoin(member.auth, auth)
|
||||
.groupBy(memberCreatorTag.member.id)
|
||||
.where(where)
|
||||
.limit(limit)
|
||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||
.fetch()
|
||||
.map {
|
||||
SimilarCreatorResponse(
|
||||
it.id!!,
|
||||
it.nickname,
|
||||
if (it.profileImage != null) {
|
||||
"$cloudFrontHost/${it.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
it.tags
|
||||
.asSequence()
|
||||
.filter { accountCounselorTag -> accountCounselorTag.tag.isActive }
|
||||
.map { accountCounselorTag -> accountCounselorTag.tag.tag }
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun getLiveRoomList(
|
||||
creatorId: Long,
|
||||
userMember: Member,
|
||||
timezone: String,
|
||||
limit: Int,
|
||||
offset: Long = 0
|
||||
): List<LiveRoomResponse> {
|
||||
var where = liveRoom.member.id.eq(creatorId)
|
||||
.and(liveRoom.cancel.id.isNull)
|
||||
.and(liveRoom.isActive.isTrue)
|
||||
|
||||
if (userMember.auth == null) {
|
||||
where = where.and(liveRoom.isAdult.isFalse)
|
||||
}
|
||||
|
||||
val result = mutableListOf<LiveRoom>()
|
||||
|
||||
if (offset == 0L) {
|
||||
result.addAll(
|
||||
queryFactory
|
||||
.selectFrom(liveRoom)
|
||||
.innerJoin(liveRoom.member, member)
|
||||
.leftJoin(liveRoom.cancel, liveRoomCancel)
|
||||
.where(where)
|
||||
.orderBy(liveRoom.beginDateTime.asc())
|
||||
.fetch()
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
.map {
|
||||
val reservations = it.reservations
|
||||
.filter { reservation ->
|
||||
reservation.member!!.id!! == userMember.id!! && reservation.isActive
|
||||
}
|
||||
|
||||
val beginDateTime = it.beginDateTime
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
|
||||
val isPaid = if (it.channelName != null) {
|
||||
val useCan = queryFactory
|
||||
.selectFrom(useCan)
|
||||
.where(
|
||||
useCan.member.id.eq(member.id)
|
||||
.and(useCan.room.id.eq(it.id!!))
|
||||
.and(useCan.canUsage.eq(CanUsage.LIVE))
|
||||
)
|
||||
.orderBy(useCan.id.desc())
|
||||
.fetchFirst()
|
||||
|
||||
useCan != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
LiveRoomResponse(
|
||||
roomId = it.id!!,
|
||||
title = it.title,
|
||||
content = it.notice,
|
||||
isPaid = isPaid,
|
||||
beginDateTime = beginDateTime.format(
|
||||
DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")
|
||||
),
|
||||
isAdult = it.isAdult,
|
||||
price = it.price,
|
||||
channelName = it.channelName,
|
||||
managerNickname = it.member!!.nickname,
|
||||
coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
|
||||
it.coverImage!!
|
||||
} else {
|
||||
"$cloudFrontHost/${it.coverImage!!}"
|
||||
},
|
||||
isReservation = reservations.isNotEmpty(),
|
||||
isActive = it.isActive,
|
||||
isPrivateRoom = it.type == LiveRoomType.PRIVATE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getNoticeString(creatorId: Long): String {
|
||||
return queryFactory
|
||||
.select(channelNotice.notice)
|
||||
.from(channelNotice)
|
||||
.where(channelNotice.member.id.eq(creatorId))
|
||||
.fetchFirst() ?: ""
|
||||
}
|
||||
|
||||
fun getCheersList(creatorId: Long, timezone: String, offset: Long, limit: Long): GetCheersResponse {
|
||||
val totalCount = queryFactory
|
||||
.selectFrom(creatorCheers)
|
||||
.where(
|
||||
creatorCheers.creator.id.eq(creatorId)
|
||||
.and(creatorCheers.isActive.isTrue)
|
||||
.and(creatorCheers.parent.isNull)
|
||||
)
|
||||
.fetch()
|
||||
.count()
|
||||
|
||||
val cheers = queryFactory
|
||||
.selectFrom(creatorCheers)
|
||||
.where(
|
||||
creatorCheers.creator.id.eq(creatorId)
|
||||
.and(creatorCheers.isActive.isTrue)
|
||||
.and(creatorCheers.parent.isNull)
|
||||
)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.orderBy(creatorCheers.id.desc())
|
||||
.fetch()
|
||||
.asSequence()
|
||||
.map {
|
||||
val date = it.createdAt!!
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
|
||||
GetCheersResponseItem(
|
||||
cheersId = it.id!!,
|
||||
nickname = it.member!!.nickname,
|
||||
profileUrl = if (it.member!!.profileImage != null) {
|
||||
"$cloudFrontHost/${it.member!!.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
content = it.cheers,
|
||||
date = date.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
|
||||
replyList = it.children.asSequence()
|
||||
.map { cheers ->
|
||||
val replyDate = cheers.createdAt!!
|
||||
.atZone(ZoneId.of("UTC"))
|
||||
.withZoneSameInstant(ZoneId.of(timezone))
|
||||
|
||||
GetCheersResponseItem(
|
||||
cheersId = cheers.id!!,
|
||||
nickname = cheers.member!!.nickname,
|
||||
profileUrl = if (cheers.member!!.profileImage != null) {
|
||||
"$cloudFrontHost/${cheers.member!!.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
content = cheers.cheers,
|
||||
date = replyDate.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
|
||||
replyList = listOf()
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetCheersResponse(
|
||||
totalCount = totalCount,
|
||||
cheers = cheers
|
||||
)
|
||||
}
|
||||
|
||||
fun getLiveCount(creatorId: Long): Long? {
|
||||
return queryFactory
|
||||
.select(liveRoom.id.count())
|
||||
.from(liveRoom)
|
||||
.where(
|
||||
liveRoom.member.id.eq(creatorId)
|
||||
.and(liveRoom.channelName.isNotNull)
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getLiveTime(creatorId: Long): Long {
|
||||
val diffs = queryFactory
|
||||
.select(
|
||||
Projections.constructor(
|
||||
TimeDifferenceResult::class.java,
|
||||
liveRoom.beginDateTime,
|
||||
liveRoom.updatedAt
|
||||
)
|
||||
)
|
||||
.from(liveRoom)
|
||||
.where(
|
||||
liveRoom.member.id.eq(creatorId)
|
||||
.and(liveRoom.channelName.isNotNull)
|
||||
)
|
||||
.fetch()
|
||||
|
||||
return diffs
|
||||
.asSequence()
|
||||
.map { Duration.between(it.beginDateTime, it.updatedAt).toSeconds() }
|
||||
.sum() / 3600
|
||||
}
|
||||
|
||||
fun getLiveContributorCount(creatorId: Long): Long? {
|
||||
return queryFactory
|
||||
.select(liveRoomVisit.member.count())
|
||||
.from(liveRoomVisit)
|
||||
.innerJoin(liveRoomVisit.room, liveRoom)
|
||||
.where(
|
||||
liveRoom.member.id.eq(creatorId)
|
||||
.and(liveRoom.channelName.isNotNull)
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getFollowerListTotalCount(creatorId: Long): Int {
|
||||
return queryFactory.select(creatorFollowing.id)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
.where(
|
||||
member.isActive.isTrue
|
||||
.and(creatorFollowing.isActive.isTrue)
|
||||
.and(creatorFollowing.creator.id.eq(creatorId))
|
||||
.and(creatorFollowing.member.id.ne(creatorId))
|
||||
)
|
||||
.fetch()
|
||||
.size
|
||||
}
|
||||
|
||||
fun getFollowerList(
|
||||
creatorId: Long,
|
||||
offset: Long,
|
||||
limit: Long
|
||||
): List<GetFollowerListDto> {
|
||||
return queryFactory
|
||||
.select(
|
||||
QGetFollowerListDto(
|
||||
member.id,
|
||||
member.profileImage.prepend("/").prepend(cloudFrontHost),
|
||||
member.nickname,
|
||||
member.role
|
||||
)
|
||||
)
|
||||
.from(creatorFollowing)
|
||||
.innerJoin(creatorFollowing.member, member)
|
||||
.where(
|
||||
member.isActive.isTrue
|
||||
.and(creatorFollowing.isActive.isTrue)
|
||||
.and(creatorFollowing.creator.id.eq(creatorId))
|
||||
.and(creatorFollowing.member.id.ne(creatorId))
|
||||
)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun isFollow(creatorId: Long, memberId: Long): Boolean {
|
||||
return queryFactory
|
||||
.select(creatorFollowing.isActive)
|
||||
.from(creatorFollowing)
|
||||
.where(
|
||||
creatorFollowing.creator.id.eq(creatorId)
|
||||
.and(creatorFollowing.member.id.eq(memberId))
|
||||
)
|
||||
.fetchOne() ?: false
|
||||
}
|
||||
|
||||
fun getCreatorCheers(cheersId: Long): CreatorCheers? {
|
||||
return queryFactory
|
||||
.selectFrom(creatorCheers)
|
||||
.where(creatorCheers.id.eq(cheersId))
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getCheers(cheersId: Long, memberId: Long): CreatorCheers? {
|
||||
return queryFactory
|
||||
.selectFrom(creatorCheers)
|
||||
.where(
|
||||
creatorCheers.id.eq(cheersId)
|
||||
.and(creatorCheers.member.id.eq(memberId))
|
||||
)
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
fun getNotice(creatorId: Long): ChannelNotice? {
|
||||
return queryFactory
|
||||
.selectFrom(channelNotice)
|
||||
.where(channelNotice.member.id.eq(creatorId))
|
||||
.fetchFirst()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponse
|
||||
import kr.co.vividnext.sodalive.explorer.follower.GetFollowerListResponseItem
|
||||
import kr.co.vividnext.sodalive.explorer.profile.ChannelNotice
|
||||
import kr.co.vividnext.sodalive.explorer.profile.ChannelNoticeRepository
|
||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheers
|
||||
import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PostWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.explorer.profile.PutWriteCheersRequest
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
|
@ -13,6 +23,8 @@ import org.springframework.transaction.annotation.Transactional
|
|||
class ExplorerService(
|
||||
private val memberService: MemberService,
|
||||
private val queryRepository: ExplorerQueryRepository,
|
||||
private val cheersRepository: CreatorCheersRepository,
|
||||
private val noticeRepository: ChannelNoticeRepository,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
|
@ -142,4 +154,156 @@ class ExplorerService(
|
|||
.map { GetRoomDetailUser(it, cloudFrontHost) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getCreatorProfile(creatorId: Long, timezone: String, member: Member): GetCreatorProfileResponse {
|
||||
// 크리에이터(유저) 정보
|
||||
val creatorAccount = queryRepository.getAccount(creatorId) ?: throw SodaException("없는 사용자 입니다.")
|
||||
|
||||
// 차단된 사용자 체크
|
||||
val isBlocked = memberService.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
|
||||
if (isBlocked) throw SodaException("${creatorAccount.nickname}님의 요청으로 채널 접근이 제한됩니다.")
|
||||
|
||||
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
||||
val isNotification = notificationUserIds.contains(member.id)
|
||||
val notificationRecipientCount = notificationUserIds.size
|
||||
|
||||
// 후원랭킹
|
||||
val userDonationRanking = queryRepository.getUserDonationRanking(
|
||||
creatorId,
|
||||
10,
|
||||
withDonationCan = creatorId == member.id!!
|
||||
)
|
||||
|
||||
// 추천 크리에이터
|
||||
val similarCreatorList = queryRepository.getSimilarCreatorList(creatorId)
|
||||
|
||||
// 라이브
|
||||
val liveRoomList = queryRepository.getLiveRoomList(
|
||||
creatorId,
|
||||
userMember = member,
|
||||
timezone = timezone,
|
||||
limit = 4
|
||||
)
|
||||
|
||||
// 공지사항
|
||||
val notice = queryRepository.getNoticeString(creatorId)
|
||||
|
||||
// 응원
|
||||
val cheers = queryRepository.getCheersList(creatorId, timezone = timezone, offset = 0, limit = 4)
|
||||
|
||||
// 차단한 크리에이터 인지 체크
|
||||
val isBlock = memberService.isBlocked(blockedMemberId = creatorId, memberId = member.id!!)
|
||||
|
||||
// 활동요약 (라이브 횟수, 라이브 시간, 라이브 참여자, 콘텐츠 수)
|
||||
val liveCount = queryRepository.getLiveCount(creatorId) ?: 0
|
||||
val liveTime = queryRepository.getLiveTime(creatorId)
|
||||
val liveContributorCount = queryRepository.getLiveContributorCount(creatorId) ?: 0
|
||||
val contentCount = 0L
|
||||
|
||||
return GetCreatorProfileResponse(
|
||||
creator = CreatorResponse(
|
||||
creatorId = creatorAccount.id!!,
|
||||
profileUrl = if (creatorAccount.profileImage != null) {
|
||||
"$cloudFrontHost/${creatorAccount.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
},
|
||||
nickname = creatorAccount.nickname,
|
||||
tags = creatorAccount.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
|
||||
introduce = creatorAccount.introduce,
|
||||
instagramUrl = creatorAccount.instagramUrl,
|
||||
youtubeUrl = creatorAccount.youtubeUrl,
|
||||
websiteUrl = creatorAccount.websiteUrl,
|
||||
blogUrl = creatorAccount.blogUrl,
|
||||
isNotification = isNotification,
|
||||
notificationRecipientCount = notificationRecipientCount
|
||||
),
|
||||
userDonationRanking = userDonationRanking,
|
||||
similarCreatorList = similarCreatorList,
|
||||
liveRoomList = liveRoomList,
|
||||
notice = notice,
|
||||
cheers = cheers,
|
||||
activitySummary = GetCreatorActivitySummary(
|
||||
liveCount = liveCount,
|
||||
liveTime = liveTime,
|
||||
liveContributorCount = liveContributorCount,
|
||||
contentCount = contentCount
|
||||
),
|
||||
isBlock = isBlock
|
||||
)
|
||||
}
|
||||
|
||||
fun getFollowerList(
|
||||
creatorId: Long,
|
||||
member: Member,
|
||||
pageable: Pageable
|
||||
): GetFollowerListResponse {
|
||||
val totalCount = queryRepository
|
||||
.getFollowerListTotalCount(creatorId)
|
||||
|
||||
val followerList = queryRepository.getFollowerList(creatorId, pageable.offset, pageable.pageSize.toLong())
|
||||
.asSequence()
|
||||
.map {
|
||||
val isFollow = if (it.role == MemberRole.CREATOR) {
|
||||
queryRepository.isFollow(creatorId = it.userId, memberId = member.id!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
GetFollowerListResponseItem(
|
||||
userId = it.userId,
|
||||
profileImage = it.profileImage,
|
||||
nickname = it.nickname,
|
||||
isFollow = isFollow
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
return GetFollowerListResponse(totalCount = totalCount, items = followerList)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun writeCheers(request: PostWriteCheersRequest, member: Member) {
|
||||
val creator = queryRepository.getAccount(request.creatorId) ?: throw SodaException("없는 사용자 입니다.")
|
||||
|
||||
val isBlocked = memberService.isBlocked(blockedMemberId = member.id!!, memberId = request.creatorId)
|
||||
if (isBlocked) throw SodaException("${creator.nickname}님의 요청으로 팬토크 작성이 제한됩니다.")
|
||||
|
||||
val cheers = CreatorCheers(cheers = request.content)
|
||||
cheers.member = member
|
||||
cheers.creator = creator
|
||||
|
||||
val parent = if (request.parentId != null) {
|
||||
queryRepository.getCreatorCheers(request.parentId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
cheers.parent = parent
|
||||
}
|
||||
|
||||
cheersRepository.save(cheers)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun modifyCheers(request: PutWriteCheersRequest, member: Member) {
|
||||
val cheers = queryRepository.getCheers(request.cheersId, member.id!!)
|
||||
?: throw SodaException("잘못된 요청입니다.")
|
||||
|
||||
cheers.cheers = request.content
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun saveNotice(member: Member, notice: String) {
|
||||
var channelNotice = queryRepository.getNotice(creatorId = member.id!!)
|
||||
|
||||
if (channelNotice == null) {
|
||||
channelNotice = ChannelNotice(notice)
|
||||
channelNotice.member = member
|
||||
noticeRepository.save(channelNotice)
|
||||
} else {
|
||||
channelNotice.notice = notice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class GetCheersResponse(
|
||||
val totalCount: Int,
|
||||
val cheers: List<GetCheersResponseItem>
|
||||
)
|
||||
|
||||
data class GetCheersResponseItem(
|
||||
val cheersId: Long,
|
||||
val nickname: String,
|
||||
val profileUrl: String,
|
||||
val content: String,
|
||||
val date: String,
|
||||
val replyList: List<GetCheersResponseItem>
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class GetCreatorProfileResponse(
|
||||
val creator: CreatorResponse,
|
||||
val userDonationRanking: List<UserDonationRankingResponse>,
|
||||
val similarCreatorList: List<SimilarCreatorResponse>,
|
||||
val liveRoomList: List<LiveRoomResponse>,
|
||||
val notice: String,
|
||||
val cheers: GetCheersResponse,
|
||||
val activitySummary: GetCreatorActivitySummary,
|
||||
val isBlock: Boolean
|
||||
)
|
||||
|
||||
data class GetCreatorActivitySummary(
|
||||
val liveCount: Long,
|
||||
val liveTime: Long,
|
||||
val liveContributorCount: Long,
|
||||
val contentCount: Long
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class GetLiveRoomAllResponse(
|
||||
val totalCount: Int,
|
||||
val liveRoomList: List<LiveRoomResponse>
|
||||
)
|
||||
|
||||
data class LiveRoomResponse(
|
||||
val roomId: Long,
|
||||
val title: String,
|
||||
val content: String,
|
||||
val isPaid: Boolean,
|
||||
val beginDateTime: String,
|
||||
val coverImageUrl: String,
|
||||
val isAdult: Boolean,
|
||||
val price: Int,
|
||||
val channelName: String?,
|
||||
val managerNickname: String,
|
||||
val isReservation: Boolean,
|
||||
val isActive: Boolean,
|
||||
val isPrivateRoom: Boolean
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class SimilarCreatorResponse(
|
||||
val userId: Long,
|
||||
val nickname: String,
|
||||
val profileImage: String,
|
||||
val tags: List<String>
|
||||
)
|
|
@ -0,0 +1,16 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class GetDonationAllResponse(
|
||||
val accumulatedCansToday: Int,
|
||||
val accumulatedCansLastWeek: Int,
|
||||
val accumulatedCansThisMonth: Int,
|
||||
val totalCount: Int,
|
||||
val userDonationRanking: List<UserDonationRankingResponse>
|
||||
)
|
||||
|
||||
data class UserDonationRankingResponse(
|
||||
val userId: Long,
|
||||
val nickname: String,
|
||||
val profileImage: String,
|
||||
val donationCan: Int
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
package kr.co.vividnext.sodalive.explorer.follower
|
||||
|
||||
import com.querydsl.core.annotations.QueryProjection
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
|
||||
data class GetFollowerListDto @QueryProjection constructor(
|
||||
val userId: Long,
|
||||
val profileImage: String,
|
||||
val nickname: String,
|
||||
val role: MemberRole
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
package kr.co.vividnext.sodalive.explorer.follower
|
||||
|
||||
data class GetFollowerListResponse(
|
||||
val totalCount: Int,
|
||||
val items: List<GetFollowerListResponseItem>
|
||||
)
|
||||
|
||||
data class GetFollowerListResponseItem(
|
||||
val userId: Long,
|
||||
val profileImage: String,
|
||||
val nickname: String,
|
||||
val isFollow: Boolean?
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.OneToOne
|
||||
|
||||
@Entity
|
||||
data class ChannelNotice(
|
||||
@Column(columnDefinition = "TEXT")
|
||||
var notice: String
|
||||
) : BaseEntity() {
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member? = null
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface ChannelNoticeRepository : JpaRepository<ChannelNotice, Long>
|
|
@ -0,0 +1,36 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
import javax.persistence.FetchType
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.OneToMany
|
||||
|
||||
@Entity
|
||||
data class CreatorCheers(
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
var cheers: String,
|
||||
val isActive: Boolean = true
|
||||
) : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parent_id", nullable = true)
|
||||
var parent: CreatorCheers? = null
|
||||
set(value) {
|
||||
value?.children?.add(this)
|
||||
field = value
|
||||
}
|
||||
|
||||
@OneToMany(mappedBy = "parent")
|
||||
var children: MutableList<CreatorCheers> = mutableListOf()
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member? = null
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "creator_id", nullable = false)
|
||||
var creator: Member? = null
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
interface CreatorCheersRepository : JpaRepository<CreatorCheers, Long>
|
|
@ -0,0 +1,3 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
data class PostCreatorNoticeRequest(val notice: String)
|
|
@ -0,0 +1,7 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
data class PostWriteCheersRequest(
|
||||
val parentId: Long? = null,
|
||||
val creatorId: Long,
|
||||
val content: String
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
data class PutWriteCheersRequest(
|
||||
val cheersId: Long,
|
||||
val content: String
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.explorer.profile
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class TimeDifferenceResult(
|
||||
val beginDateTime: LocalDateTime,
|
||||
val updatedAt: LocalDateTime
|
||||
)
|
Loading…
Reference in New Issue