탐색 메인 - API 추가
This commit is contained in:
parent
df861bf8a1
commit
049e1c41de
|
@ -0,0 +1,32 @@
|
|||
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.member.Member
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/explorer")
|
||||
class ExplorerController(private val service: ExplorerService) {
|
||||
@GetMapping
|
||||
fun getExplorer(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
|
||||
ApiResponse.ok(service.getExplorer(member))
|
||||
}
|
||||
|
||||
@GetMapping("/search/channel")
|
||||
fun getSearchChannel(
|
||||
@RequestParam channel: String,
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
) = run {
|
||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||
ApiResponse.ok(service.getSearchChannel(channel, member))
|
||||
}
|
||||
}
|
|
@ -1,13 +1,22 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
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.explorer.section.ExplorerSection
|
||||
import kr.co.vividnext.sodalive.explorer.section.QExplorerSection.explorerSection
|
||||
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
|
||||
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.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.LocalDateTime
|
||||
|
||||
@Repository
|
||||
class ExplorerQueryRepository(
|
||||
|
@ -68,4 +77,68 @@ class ExplorerQueryRepository(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSubscriberGrowthRankingCreators(limit: Long): List<Member> {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.join(member.follower, creatorFollowing)
|
||||
.where(
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
.and(creatorFollowing.createdAt.goe(LocalDateTime.now().minusMonths(1)))
|
||||
.and(creatorFollowing.isActive.isTrue)
|
||||
)
|
||||
.groupBy(member.id)
|
||||
.orderBy(member.follower.size().desc())
|
||||
.limit(limit)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getNewCreators(): List<Member> {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.where(
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
.and(member.createdAt.goe(LocalDateTime.now().minusDays(30)))
|
||||
)
|
||||
.orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
|
||||
.limit(20)
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun getExplorerSectionData(isAdult: Boolean): List<ExplorerSection> {
|
||||
var explorerSectionCondition = explorerSection.isActive.eq(true)
|
||||
if (!isAdult) {
|
||||
explorerSectionCondition = explorerSectionCondition.and(explorerSection.isAdult.isFalse)
|
||||
}
|
||||
|
||||
return queryFactory
|
||||
.selectFrom(explorerSection)
|
||||
.where(explorerSectionCondition)
|
||||
.orderBy(explorerSection.orders.asc())
|
||||
.fetch()
|
||||
}
|
||||
|
||||
fun findMemberByTag(tags: List<String>): List<Member> {
|
||||
return queryFactory
|
||||
.selectFrom(member)
|
||||
.leftJoin(member.tags, memberCreatorTag)
|
||||
.join(memberCreatorTag.tag, creatorTag)
|
||||
.where(
|
||||
member.role.eq(MemberRole.CREATOR)
|
||||
.and(creatorTag.tag.`in`(tags))
|
||||
)
|
||||
.fetch()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
fun getSearchChannel(channel: String, accountId: Long): List<Member> {
|
||||
return queryFactory.selectFrom(member)
|
||||
.where(
|
||||
member.nickname.containsIgnoreCase(channel)
|
||||
.and(member.isActive.isTrue)
|
||||
.and(member.id.ne(accountId))
|
||||
.and(member.role.eq(MemberRole.CREATOR))
|
||||
)
|
||||
.fetch()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberService
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class ExplorerService(
|
||||
private val memberService: MemberService,
|
||||
private val queryRepository: ExplorerQueryRepository,
|
||||
|
||||
@Value("\${cloud.aws.cloud-front.host}")
|
||||
private val cloudFrontHost: String
|
||||
) {
|
||||
fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse {
|
||||
val sections = mutableListOf<GetExplorerSectionResponse>()
|
||||
|
||||
// 인기 급상승중 (subscriberGrowthRankingCreators)
|
||||
val growthRankingCreators = queryRepository
|
||||
.getSubscriberGrowthRankingCreators(limit = growthRankingCreatorsLimit)
|
||||
.asSequence()
|
||||
.filter { !memberService.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) }
|
||||
.map {
|
||||
GetExplorerSectionCreatorResponse(
|
||||
id = it.id!!,
|
||||
nickname = it.nickname,
|
||||
tags = it.tags
|
||||
.asSequence()
|
||||
.filter { tag -> tag.tag.isActive }
|
||||
.map { tag -> tag.tag.tag }
|
||||
.joinToString(" ") { tag -> "#$tag" },
|
||||
profileImageUrl = if (it.profileImage != null) {
|
||||
"$cloudFrontHost/${it.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
val growthRankingSection = GetExplorerSectionResponse(
|
||||
title = "인기 급상승중",
|
||||
coloredTitle = "인기",
|
||||
color = "FF5C49",
|
||||
creators = growthRankingCreators
|
||||
)
|
||||
sections.add(growthRankingSection)
|
||||
|
||||
// 새로 시작 (newCreators)
|
||||
val newCreators = queryRepository
|
||||
.getNewCreators()
|
||||
.asSequence()
|
||||
.filter { !memberService.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) }
|
||||
.map {
|
||||
GetExplorerSectionCreatorResponse(
|
||||
id = it.id!!,
|
||||
nickname = it.nickname,
|
||||
tags = it.tags
|
||||
.asSequence()
|
||||
.filter { tag -> tag.tag.isActive }
|
||||
.map { tag -> tag.tag.tag }
|
||||
.joinToString(" ") { tag -> "#$tag" },
|
||||
profileImageUrl = if (it.profileImage != null) {
|
||||
"$cloudFrontHost/${it.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
val newCreatorsSection = GetExplorerSectionResponse(
|
||||
title = "새로 시작",
|
||||
coloredTitle = "새로",
|
||||
color = "5FD28F",
|
||||
creators = newCreators
|
||||
)
|
||||
sections.add(newCreatorsSection)
|
||||
|
||||
// 관리자에서 설정한 타이틀과 크리에이터
|
||||
sections.addAll(
|
||||
queryRepository
|
||||
.getExplorerSectionData(isAdult = member.auth != null)
|
||||
.asSequence()
|
||||
.map {
|
||||
val tags = it.tags.asSequence().map { explorerSectionTag -> explorerSectionTag.tag!!.tag }.toList()
|
||||
val creators = queryRepository.findMemberByTag(tags)
|
||||
.asSequence()
|
||||
.filter { creator ->
|
||||
!memberService.isBlocked(
|
||||
blockedMemberId = member.id!!,
|
||||
memberId = creator.id!!
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
GetExplorerSectionResponse(
|
||||
it.title,
|
||||
it.coloredTitle,
|
||||
it.color,
|
||||
creators = creators
|
||||
.asSequence()
|
||||
.map { account ->
|
||||
GetExplorerSectionCreatorResponse(
|
||||
id = account.id!!,
|
||||
nickname = account.nickname,
|
||||
tags = account.tags
|
||||
.asSequence()
|
||||
.filter { counselorTag -> counselorTag.tag.isActive }
|
||||
.toList()
|
||||
.joinToString(" ") { counselorTag ->
|
||||
"#${counselorTag.tag.tag}"
|
||||
},
|
||||
profileImageUrl = if (account.profileImage != null) {
|
||||
"$cloudFrontHost/${account.profileImage}"
|
||||
} else {
|
||||
"$cloudFrontHost/profile/default-profile.png"
|
||||
}
|
||||
)
|
||||
}.toList()
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
)
|
||||
|
||||
return GetExplorerResponse(sections = sections)
|
||||
}
|
||||
|
||||
fun getSearchChannel(channel: String, member: Member): List<GetRoomDetailUser> {
|
||||
if (channel.length < 2) {
|
||||
throw SodaException("두 글자 이상 입력 하셔야 합니다.")
|
||||
}
|
||||
|
||||
return queryRepository.getSearchChannel(channel, member.id!!)
|
||||
.asSequence()
|
||||
.filter { !memberService.isBlocked(blockedMemberId = member.id!!, memberId = it.id!!) }
|
||||
.map { GetRoomDetailUser(it, cloudFrontHost) }
|
||||
.toList()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package kr.co.vividnext.sodalive.explorer
|
||||
|
||||
data class GetExplorerResponse(val sections: List<GetExplorerSectionResponse>)
|
||||
|
||||
data class GetExplorerSectionResponse(
|
||||
val title: String,
|
||||
val coloredTitle: String?,
|
||||
val color: String?,
|
||||
val creators: List<GetExplorerSectionCreatorResponse>
|
||||
)
|
||||
|
||||
data class GetExplorerSectionCreatorResponse(
|
||||
val id: Long,
|
||||
val nickname: String,
|
||||
val tags: String,
|
||||
val profileImageUrl: String
|
||||
)
|
|
@ -0,0 +1,43 @@
|
|||
package kr.co.vividnext.sodalive.explorer.section
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.tag.CreatorTag
|
||||
import javax.persistence.CascadeType
|
||||
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 ExplorerSection(
|
||||
var title: String,
|
||||
var isAdult: Boolean,
|
||||
@Column(nullable = false)
|
||||
var orders: Int = 1
|
||||
) : BaseEntity() {
|
||||
|
||||
@Column(nullable = true)
|
||||
var coloredTitle: String? = null
|
||||
|
||||
@Column(nullable = true)
|
||||
var color: String? = null
|
||||
|
||||
@Column(nullable = false)
|
||||
var isActive: Boolean = true
|
||||
|
||||
@OneToMany(mappedBy = "explorerSection", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||
var tags: MutableList<ExplorerSectionCreatorTag> = mutableListOf()
|
||||
}
|
||||
|
||||
@Entity
|
||||
class ExplorerSectionCreatorTag : BaseEntity() {
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "explorer_section_id", nullable = false)
|
||||
var explorerSection: ExplorerSection? = null
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "creator_tag_id", nullable = false)
|
||||
var tag: CreatorTag? = null
|
||||
}
|
|
@ -696,7 +696,11 @@ class LiveRoomService(
|
|||
websiteUrl = user.websiteUrl,
|
||||
blogUrl = user.blogUrl,
|
||||
introduce = user.introduce,
|
||||
tags = "",
|
||||
tags = user.tags
|
||||
.asSequence()
|
||||
.filter { it.tag.isActive }
|
||||
.map { it.tag.tag }
|
||||
.joinToString(" ") { tag -> "#$tag" },
|
||||
isSpeaker = isSpeaker,
|
||||
isManager = isManager,
|
||||
isFollowing = isFollowing,
|
||||
|
|
|
@ -2,8 +2,10 @@ package kr.co.vividnext.sodalive.member
|
|||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import kr.co.vividnext.sodalive.member.auth.Auth
|
||||
import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||
import kr.co.vividnext.sodalive.member.notification.MemberNotification
|
||||
import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree
|
||||
import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag
|
||||
import javax.persistence.CascadeType
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
|
@ -33,6 +35,12 @@ data class Member(
|
|||
@OneToMany(mappedBy = "member", cascade = [CascadeType.ALL])
|
||||
val stipulationAgrees: MutableList<StipulationAgree> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "member", cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||
var tags: MutableList<MemberCreatorTag> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "creator")
|
||||
var follower: MutableList<CreatorFollowing> = mutableListOf()
|
||||
|
||||
@OneToOne(mappedBy = "member", fetch = FetchType.LAZY)
|
||||
var notification: MemberNotification? = null
|
||||
|
||||
|
|
|
@ -332,4 +332,6 @@ class MemberService(
|
|||
blockMember.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
fun isBlocked(blockedMemberId: Long, memberId: Long) = blockMemberRepository.isBlocked(blockedMemberId, memberId)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ interface BlockMemberRepository : JpaRepository<BlockMember, Long>, BlockMemberQ
|
|||
|
||||
interface BlockMemberQueryRepository {
|
||||
fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember?
|
||||
fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean
|
||||
}
|
||||
|
||||
@Repository
|
||||
|
@ -24,4 +25,18 @@ class BlockMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
|||
.orderBy(blockMember.id.desc())
|
||||
.fetchFirst()
|
||||
}
|
||||
|
||||
override fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean {
|
||||
val blockedAccount = queryFactory
|
||||
.select(blockMember.id)
|
||||
.from(blockMember)
|
||||
.where(
|
||||
blockMember.memberId.eq(memberId)
|
||||
.and(blockMember.blockedMemberId.eq(blockedMemberId))
|
||||
.and(blockMember.isActive.isTrue)
|
||||
)
|
||||
.fetchOne()
|
||||
|
||||
return blockedAccount != null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.Entity
|
||||
|
||||
@Entity
|
||||
data class CreatorTag(
|
||||
@Column(unique = true, nullable = false)
|
||||
var tag: String,
|
||||
@Column(nullable = true)
|
||||
var image: String? = null,
|
||||
@Column(nullable = false)
|
||||
var isActive: Boolean = true,
|
||||
@Column(nullable = false)
|
||||
var orders: Int = 1
|
||||
) : BaseEntity()
|
|
@ -0,0 +1,19 @@
|
|||
package kr.co.vividnext.sodalive.member.tag
|
||||
|
||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||
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 MemberCreatorTag(
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "member_id", nullable = false)
|
||||
var member: Member,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "creator_tag_id", nullable = false)
|
||||
var tag: CreatorTag
|
||||
) : BaseEntity()
|
Loading…
Reference in New Issue