크리에이터 채널 API

This commit is contained in:
Klaus 2023-08-01 14:40:52 +09:00
parent 049e1c41de
commit c25b105d4d
19 changed files with 889 additions and 0 deletions

View File

@ -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
)

View File

@ -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))
}
}

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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>
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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>
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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?
)

View File

@ -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
}

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
package kr.co.vividnext.sodalive.explorer.profile
data class PostCreatorNoticeRequest(val notice: String)

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.explorer.profile
data class PostWriteCheersRequest(
val parentId: Long? = null,
val creatorId: Long,
val content: String
)

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.explorer.profile
data class PutWriteCheersRequest(
val cheersId: Long,
val content: String
)

View File

@ -0,0 +1,8 @@
package kr.co.vividnext.sodalive.explorer.profile
import java.time.LocalDateTime
data class TimeDifferenceResult(
val beginDateTime: LocalDateTime,
val updatedAt: LocalDateTime
)