탐색 메인 - API 추가
This commit is contained in:
		| @@ -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 | package kr.co.vividnext.sodalive.explorer | ||||||
|  |  | ||||||
|  | import com.querydsl.core.types.dsl.Expressions | ||||||
| import com.querydsl.jpa.impl.JPAQueryFactory | import com.querydsl.jpa.impl.JPAQueryFactory | ||||||
| import kr.co.vividnext.sodalive.can.use.CanUsage | import kr.co.vividnext.sodalive.can.use.CanUsage | ||||||
| import kr.co.vividnext.sodalive.can.use.QUseCan.useCan | 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.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 | ||||||
|  | import kr.co.vividnext.sodalive.member.QMember.member | ||||||
| import kr.co.vividnext.sodalive.member.following.QCreatorFollowing.creatorFollowing | 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.beans.factory.annotation.Value | ||||||
| import org.springframework.stereotype.Repository | import org.springframework.stereotype.Repository | ||||||
|  | import java.time.LocalDateTime | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| class ExplorerQueryRepository( | 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, |             websiteUrl = user.websiteUrl, | ||||||
|             blogUrl = user.blogUrl, |             blogUrl = user.blogUrl, | ||||||
|             introduce = user.introduce, |             introduce = user.introduce, | ||||||
|             tags = "", |             tags = user.tags | ||||||
|  |                 .asSequence() | ||||||
|  |                 .filter { it.tag.isActive } | ||||||
|  |                 .map { it.tag.tag } | ||||||
|  |                 .joinToString(" ") { tag -> "#$tag" }, | ||||||
|             isSpeaker = isSpeaker, |             isSpeaker = isSpeaker, | ||||||
|             isManager = isManager, |             isManager = isManager, | ||||||
|             isFollowing = isFollowing, |             isFollowing = isFollowing, | ||||||
|   | |||||||
| @@ -2,8 +2,10 @@ package kr.co.vividnext.sodalive.member | |||||||
|  |  | ||||||
| import kr.co.vividnext.sodalive.common.BaseEntity | import kr.co.vividnext.sodalive.common.BaseEntity | ||||||
| import kr.co.vividnext.sodalive.member.auth.Auth | 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.notification.MemberNotification | ||||||
| import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree | import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree | ||||||
|  | import kr.co.vividnext.sodalive.member.tag.MemberCreatorTag | ||||||
| import javax.persistence.CascadeType | import javax.persistence.CascadeType | ||||||
| import javax.persistence.Column | import javax.persistence.Column | ||||||
| import javax.persistence.Entity | import javax.persistence.Entity | ||||||
| @@ -33,6 +35,12 @@ data class Member( | |||||||
|     @OneToMany(mappedBy = "member", cascade = [CascadeType.ALL]) |     @OneToMany(mappedBy = "member", cascade = [CascadeType.ALL]) | ||||||
|     val stipulationAgrees: MutableList<StipulationAgree> = mutableListOf() |     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) |     @OneToOne(mappedBy = "member", fetch = FetchType.LAZY) | ||||||
|     var notification: MemberNotification? = null |     var notification: MemberNotification? = null | ||||||
|  |  | ||||||
|   | |||||||
| @@ -332,4 +332,6 @@ class MemberService( | |||||||
|             blockMember.isActive = true |             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 { | interface BlockMemberQueryRepository { | ||||||
|     fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember? |     fun getBlockAccount(blockedMemberId: Long, memberId: Long): BlockMember? | ||||||
|  |     fun isBlocked(blockedMemberId: Long, memberId: Long): Boolean | ||||||
| } | } | ||||||
|  |  | ||||||
| @Repository | @Repository | ||||||
| @@ -24,4 +25,18 @@ class BlockMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) | |||||||
|             .orderBy(blockMember.id.desc()) |             .orderBy(blockMember.id.desc()) | ||||||
|             .fetchFirst() |             .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() | ||||||
		Reference in New Issue
	
	Block a user