feat(home-following): 팔로잉 탭 조회 repository를 추가한다
This commit is contained in:
+455
@@ -0,0 +1,455 @@
|
||||
package kr.co.vividnext.sodalive.v2.home.following.adapter.out.persistence
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||
import kr.co.vividnext.sodalive.content.AudioContent
|
||||
import kr.co.vividnext.sodalive.content.theme.AudioContentTheme
|
||||
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
|
||||
import kr.co.vividnext.sodalive.live.room.LiveRoom
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.block.BlockMember
|
||||
import kr.co.vividnext.sodalive.member.following.CreatorFollowing
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.CreatorActivityType
|
||||
import kr.co.vividnext.sodalive.v2.home.following.domain.FollowingNewsType
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.context.annotation.Import
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@DataJpaTest(
|
||||
properties = [
|
||||
"spring.cache.type=none",
|
||||
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
|
||||
]
|
||||
)
|
||||
@Import(QueryDslConfig::class)
|
||||
class DefaultHomeFollowingQueryRepositoryTest @Autowired constructor(
|
||||
private val entityManager: EntityManager,
|
||||
queryFactory: JPAQueryFactory
|
||||
) {
|
||||
private val repository = DefaultHomeFollowingQueryRepository(queryFactory, "https://cdn.test")
|
||||
|
||||
@Test
|
||||
@DisplayName("팔로잉 크리에이터는 활성 팔로우와 활성 크리에이터만 최신 팔로우순으로 조회한다")
|
||||
fun shouldFindActiveFollowingCreatorsByLatestFollowOrder() {
|
||||
val viewer = saveMember("following-viewer", MemberRole.USER)
|
||||
val activeCreator = saveMember("following-active", MemberRole.CREATOR, profileImage = "active.png")
|
||||
val inactiveCreator = saveMember("following-inactive", MemberRole.CREATOR, isActive = false)
|
||||
val olderCreator = saveMember("following-older", MemberRole.CREATOR)
|
||||
val nonCreator = saveMember("following-non-creator", MemberRole.USER)
|
||||
val olderFollow = saveFollowing(viewer, olderCreator, isActive = true)
|
||||
val activeFollow = saveFollowing(viewer, activeCreator, isActive = true)
|
||||
saveFollowing(viewer, inactiveCreator, isActive = true)
|
||||
saveFollowing(viewer, nonCreator, isActive = true)
|
||||
saveFollowing(viewer, saveMember("following-disabled", MemberRole.CREATOR), isActive = false)
|
||||
olderFollow.createdAt = LocalDateTime.of(2026, 6, 24, 0, 0)
|
||||
activeFollow.createdAt = LocalDateTime.of(2026, 6, 25, 0, 0)
|
||||
flushAndClear()
|
||||
|
||||
val creators = repository.findFollowingCreators(memberId = viewer.id!!, limit = 20)
|
||||
|
||||
assertEquals(listOf(activeCreator.id!!, olderCreator.id!!), creators.map { it.creatorId })
|
||||
assertEquals("https://cdn.test/active.png", creators.first().creatorProfileImageUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("팔로잉 크리에이터는 회원과 크리에이터의 양방향 차단 관계를 제외한다")
|
||||
fun shouldExcludeBlockedFollowingCreators() {
|
||||
val viewer = saveMember("blocked-viewer", MemberRole.USER)
|
||||
val viewerBlockedCreator = saveMember("viewer-blocked", MemberRole.CREATOR)
|
||||
val creatorBlockedViewer = saveMember("creator-blocked", MemberRole.CREATOR)
|
||||
val visibleCreator = saveMember("visible", MemberRole.CREATOR)
|
||||
saveFollowing(viewer, viewerBlockedCreator)
|
||||
saveFollowing(viewer, creatorBlockedViewer)
|
||||
saveFollowing(viewer, visibleCreator)
|
||||
saveBlock(viewer, viewerBlockedCreator)
|
||||
saveBlock(creatorBlockedViewer, viewer)
|
||||
flushAndClear()
|
||||
|
||||
val creators = repository.findFollowingCreators(memberId = viewer.id!!, limit = 20)
|
||||
|
||||
assertEquals(listOf(visibleCreator.id!!), creators.map { it.creatorId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("On Air는 팔로우한 크리에이터의 진행 중 라이브만 최신순으로 조회하고 성인 라이브를 필터링한다")
|
||||
fun shouldFindFollowingOnAirLivesWithAdultFilter() {
|
||||
val viewer = saveMember("live-viewer", MemberRole.USER)
|
||||
val creator = saveMember("live-creator", MemberRole.CREATOR, profileImage = "live-profile.png")
|
||||
val otherCreator = saveMember("live-other", MemberRole.CREATOR)
|
||||
val nonCreator = saveMember("live-non-creator", MemberRole.USER)
|
||||
saveFollowing(viewer, creator)
|
||||
saveFollowing(viewer, otherCreator, isActive = false)
|
||||
saveFollowing(viewer, nonCreator)
|
||||
val older = saveLiveRoom(creator, LocalDateTime.of(2026, 6, 25, 10, 0), channelName = "older")
|
||||
saveLiveRoom(creator, LocalDateTime.of(2026, 6, 25, 11, 0), channelName = "adult", isAdult = true)
|
||||
val latest = saveLiveRoom(creator, LocalDateTime.of(2026, 6, 25, 12, 0), channelName = "latest")
|
||||
saveLiveRoom(creator, LocalDateTime.of(2026, 6, 25, 13, 0), channelName = null)
|
||||
saveLiveRoom(otherCreator, LocalDateTime.of(2026, 6, 25, 14, 0), channelName = "other")
|
||||
saveLiveRoom(nonCreator, LocalDateTime.of(2026, 6, 25, 15, 0), channelName = "non-creator")
|
||||
flushAndClear()
|
||||
|
||||
val lives = repository.findOnAirLives(memberId = viewer.id!!, canViewAdultContent = false, limit = 10)
|
||||
|
||||
assertEquals(listOf(latest.id!!, older.id!!), lives.map { it.liveId })
|
||||
assertEquals("https://cdn.test/live-profile.png", lives.first().creatorProfileImageUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("이달의 스케줄은 KST 오늘 00시부터 다음 달 00시 전까지 라이브와 오디오를 가까운 순으로 조회한다")
|
||||
fun shouldFindMonthlySchedulesInKstWindow() {
|
||||
val viewer = saveMember("schedule-viewer", MemberRole.USER)
|
||||
val creator = saveMember("schedule-creator", MemberRole.CREATOR)
|
||||
val blockedCreator = saveMember("schedule-blocked", MemberRole.CREATOR)
|
||||
val nonCreator = saveMember("schedule-non-creator", MemberRole.USER)
|
||||
val theme = saveTheme("schedule-theme")
|
||||
saveFollowing(viewer, creator)
|
||||
saveFollowing(viewer, blockedCreator)
|
||||
saveFollowing(viewer, nonCreator)
|
||||
val live = saveLiveRoom(creator, LocalDateTime.of(2026, 6, 24, 15, 0), channelName = null)
|
||||
val audio = saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 25, 1, 0), isActive = false)
|
||||
saveLiveRoom(creator, LocalDateTime.of(2026, 6, 24, 14, 59), channelName = null)
|
||||
saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 30, 15, 0))
|
||||
saveLiveRoom(blockedCreator, LocalDateTime.of(2026, 6, 25, 0, 0), channelName = null)
|
||||
saveAudioContent(nonCreator, theme, LocalDateTime.of(2026, 6, 25, 2, 0))
|
||||
saveBlock(viewer, blockedCreator)
|
||||
flushAndClear()
|
||||
|
||||
val schedules = repository.findMonthlySchedules(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
now = LocalDateTime.of(2026, 6, 25, 12, 0),
|
||||
limit = 3
|
||||
)
|
||||
|
||||
assertEquals(listOf("LIVE:${live.id!!}", "AUDIO:${audio.id!!}"), schedules.map { it.scheduleId })
|
||||
assertEquals(listOf(CreatorActivityType.LIVE, CreatorActivityType.AUDIO), schedules.map { it.type })
|
||||
assertFalse(schedules.first().isOnAir)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("예약 오디오는 공개 전 비활성 상태여도 duration과 월간 releaseDate가 있으면 스케줄에 포함한다")
|
||||
fun shouldIncludeInactiveScheduledAudioInMonthlySchedules() {
|
||||
val viewer = saveMember("schedule-inactive-audio-viewer", MemberRole.USER)
|
||||
val creator = saveMember("schedule-inactive-audio-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme("schedule-inactive-audio-theme")
|
||||
saveFollowing(viewer, creator)
|
||||
val scheduledAudio = saveAudioContent(
|
||||
creator = creator,
|
||||
theme = theme,
|
||||
releaseDate = LocalDateTime.of(2026, 6, 25, 3, 0),
|
||||
isActive = false
|
||||
)
|
||||
saveAudioContent(
|
||||
creator = creator,
|
||||
theme = theme,
|
||||
releaseDate = LocalDateTime.of(2026, 6, 25, 4, 0),
|
||||
isActive = false
|
||||
).duration = null
|
||||
flushAndClear()
|
||||
|
||||
val schedules = repository.findMonthlySchedules(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
now = LocalDateTime.of(2026, 6, 25, 0, 0),
|
||||
limit = 3
|
||||
)
|
||||
|
||||
assertEquals(listOf("AUDIO:${scheduledAudio.id!!}"), schedules.map { it.scheduleId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("이달의 스케줄은 UTC now를 KST로 변환해 KST 저녁의 같은 날 일정을 포함한다")
|
||||
fun shouldIncludeSameKstDayScheduleWhenUtcNowIsKstEvening() {
|
||||
val viewer = saveMember("schedule-evening-viewer", MemberRole.USER)
|
||||
val creator = saveMember("schedule-evening-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme("schedule-evening-theme")
|
||||
saveFollowing(viewer, creator)
|
||||
val sameKstDayEvening = saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 25, 14, 45))
|
||||
saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 25, 14, 20))
|
||||
flushAndClear()
|
||||
|
||||
val schedules = repository.findMonthlySchedules(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
now = LocalDateTime.of(2026, 6, 25, 14, 30),
|
||||
limit = 3
|
||||
)
|
||||
|
||||
assertTrue(schedules.map { it.scheduleId }.contains("AUDIO:${sameKstDayEvening.id!!}"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("이달의 스케줄은 같은 시각이면 type과 targetId 순으로 안정 정렬한다")
|
||||
fun shouldSortMonthlySchedulesByTypeAndTargetIdWhenScheduledAtIsSame() {
|
||||
val viewer = saveMember("schedule-tie-viewer", MemberRole.USER)
|
||||
val creator = saveMember("schedule-tie-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme("schedule-tie-theme")
|
||||
saveFollowing(viewer, creator)
|
||||
val sameTime = LocalDateTime.of(2026, 6, 25, 1, 0)
|
||||
val firstLive = saveLiveRoom(creator, sameTime, channelName = null)
|
||||
val secondLive = saveLiveRoom(creator, sameTime, channelName = null)
|
||||
val firstAudio = saveAudioContent(creator, theme, sameTime)
|
||||
val secondAudio = saveAudioContent(creator, theme, sameTime)
|
||||
flushAndClear()
|
||||
|
||||
val schedules = repository.findMonthlySchedules(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
now = LocalDateTime.of(2026, 6, 25, 0, 0),
|
||||
limit = 10
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"LIVE:${firstLive.id!!}",
|
||||
"LIVE:${secondLive.id!!}",
|
||||
"AUDIO:${firstAudio.id!!}",
|
||||
"AUDIO:${secondAudio.id!!}"
|
||||
),
|
||||
schedules.map { it.scheduleId }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 소식은 활성 노출 가능 inbox를 최신순으로 조회하고 creatorId 없이 nullable rank만 반환한다")
|
||||
fun shouldFindRecentNewsWithoutCreatorIdAndWithNullableRank() {
|
||||
val viewer = saveMember("news-viewer", MemberRole.USER)
|
||||
val creator = saveMember("news-creator", MemberRole.CREATOR)
|
||||
val blockedCreator = saveMember("news-blocked", MemberRole.CREATOR)
|
||||
val nonCreator = saveMember("news-non-creator", MemberRole.USER)
|
||||
saveFollowing(viewer, creator)
|
||||
saveFollowing(viewer, blockedCreator)
|
||||
saveFollowing(viewer, nonCreator)
|
||||
val oldVisible = saveNews(viewer.id!!, creator.id!!, "old", LocalDateTime.of(2026, 6, 25, 8, 0), rank = null)
|
||||
val latestVisible = saveNews(viewer.id!!, creator.id!!, "latest", LocalDateTime.of(2026, 6, 25, 9, 0), rank = 3)
|
||||
saveNews(viewer.id!!, creator.id!!, "future", LocalDateTime.of(2026, 6, 25, 10, 0), rank = 1)
|
||||
saveNews(viewer.id!!, creator.id!!, "adult", LocalDateTime.of(2026, 6, 25, 9, 30), isAdult = true)
|
||||
saveNews(viewer.id!!, blockedCreator.id!!, "blocked", LocalDateTime.of(2026, 6, 25, 9, 45))
|
||||
saveNews(viewer.id!!, nonCreator.id!!, "non-creator", LocalDateTime.of(2026, 6, 25, 9, 15))
|
||||
saveBlock(viewer, blockedCreator)
|
||||
flushAndClear()
|
||||
|
||||
val news = repository.findRecentNews(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
nowUtc = LocalDateTime.of(2026, 6, 25, 9, 30),
|
||||
limit = 30
|
||||
)
|
||||
|
||||
assertEquals(listOf(latestVisible.id!!.toString(), oldVisible.id!!.toString()), news.map { it.newsId })
|
||||
assertEquals(listOf(3, null), news.map { it.rank })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 소식은 UTC now 이후 visibleFromAtUtc row를 조기 노출하지 않는다")
|
||||
fun shouldNotExposeNewsVisibleAfterUtcNow() {
|
||||
val viewer = saveMember("news-utc-viewer", MemberRole.USER)
|
||||
val creator = saveMember("news-utc-creator", MemberRole.CREATOR)
|
||||
saveFollowing(viewer, creator)
|
||||
val visibleNow = saveNews(viewer.id!!, creator.id!!, "visible-now", LocalDateTime.of(2026, 6, 25, 14, 30))
|
||||
saveNews(viewer.id!!, creator.id!!, "future-utc", LocalDateTime.of(2026, 6, 25, 14, 31))
|
||||
flushAndClear()
|
||||
|
||||
val news = repository.findRecentNews(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = false,
|
||||
nowUtc = LocalDateTime.of(2026, 6, 25, 14, 30),
|
||||
limit = 30
|
||||
)
|
||||
|
||||
assertEquals(listOf(visibleNow.id!!.toString()), news.map { it.newsId })
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 소식은 오디오와 커뮤니티 원천 target이 비활성화되면 제외한다")
|
||||
fun shouldExcludeRecentNewsWhenSourceTargetIsInactive() {
|
||||
val viewer = saveMember("news-target-viewer", MemberRole.USER)
|
||||
val creator = saveMember("news-target-creator", MemberRole.CREATOR)
|
||||
val theme = saveTheme("news-target-theme")
|
||||
saveFollowing(viewer, creator)
|
||||
val activeAudio = saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 25, 8, 0), isActive = true)
|
||||
val inactiveAudio = saveAudioContent(creator, theme, LocalDateTime.of(2026, 6, 25, 8, 10), isActive = false)
|
||||
val activePost = saveCommunityPost(creator, "active-post", isActive = true)
|
||||
val inactivePost = saveCommunityPost(creator, "inactive-post", isActive = false)
|
||||
saveNews(
|
||||
memberId = viewer.id!!,
|
||||
creatorId = creator.id!!,
|
||||
sourceKey = "active-audio",
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 25, 9, 0),
|
||||
newsType = FollowingNewsType.AUDIO_CONTENT,
|
||||
targetId = activeAudio.id!!
|
||||
)
|
||||
saveNews(
|
||||
memberId = viewer.id!!,
|
||||
creatorId = creator.id!!,
|
||||
sourceKey = "inactive-audio",
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 25, 9, 1),
|
||||
newsType = FollowingNewsType.AUDIO_CONTENT,
|
||||
targetId = inactiveAudio.id!!
|
||||
)
|
||||
saveNews(
|
||||
memberId = viewer.id!!,
|
||||
creatorId = creator.id!!,
|
||||
sourceKey = "active-post",
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 25, 9, 2),
|
||||
newsType = FollowingNewsType.COMMUNITY_POST,
|
||||
targetId = activePost.id!!
|
||||
)
|
||||
saveNews(
|
||||
memberId = viewer.id!!,
|
||||
creatorId = creator.id!!,
|
||||
sourceKey = "inactive-post",
|
||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 25, 9, 3),
|
||||
newsType = FollowingNewsType.COMMUNITY_POST,
|
||||
targetId = inactivePost.id!!
|
||||
)
|
||||
flushAndClear()
|
||||
|
||||
val news = repository.findRecentNews(
|
||||
memberId = viewer.id!!,
|
||||
canViewAdultContent = true,
|
||||
nowUtc = LocalDateTime.of(2026, 6, 25, 10, 0),
|
||||
limit = 30
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(activePost.id!!, activeAudio.id!!),
|
||||
news.map { it.targetId }
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveMember(seed: String, role: MemberRole, profileImage: String? = null, isActive: Boolean = true): Member {
|
||||
val member = Member(
|
||||
email = "$seed@test.com",
|
||||
password = "password",
|
||||
nickname = seed,
|
||||
profileImage = profileImage,
|
||||
role = role
|
||||
)
|
||||
member.isActive = isActive
|
||||
entityManager.persist(member)
|
||||
return member
|
||||
}
|
||||
|
||||
private fun saveFollowing(member: Member, creator: Member, isActive: Boolean = true): CreatorFollowing {
|
||||
val following = CreatorFollowing(isActive = isActive).apply {
|
||||
this.member = member
|
||||
this.creator = creator
|
||||
}
|
||||
entityManager.persist(following)
|
||||
return following
|
||||
}
|
||||
|
||||
private fun saveBlock(member: Member, blockedMember: Member): BlockMember {
|
||||
val block = BlockMember(isActive = true).apply {
|
||||
this.member = member
|
||||
this.blockedMember = blockedMember
|
||||
}
|
||||
entityManager.persist(block)
|
||||
return block
|
||||
}
|
||||
|
||||
private fun saveLiveRoom(
|
||||
creator: Member,
|
||||
beginDateTime: LocalDateTime,
|
||||
channelName: String?,
|
||||
isAdult: Boolean = false
|
||||
): LiveRoom {
|
||||
val liveRoom = LiveRoom(
|
||||
title = "live-${creator.nickname}-$beginDateTime",
|
||||
notice = "notice",
|
||||
beginDateTime = beginDateTime,
|
||||
numberOfPeople = 0,
|
||||
isAdult = isAdult
|
||||
).apply {
|
||||
member = creator
|
||||
this.channelName = channelName
|
||||
}
|
||||
entityManager.persist(liveRoom)
|
||||
return liveRoom
|
||||
}
|
||||
|
||||
private fun saveTheme(seed: String): AudioContentTheme {
|
||||
val theme = AudioContentTheme(theme = seed, image = "$seed.png", isActive = true)
|
||||
entityManager.persist(theme)
|
||||
return theme
|
||||
}
|
||||
|
||||
private fun saveAudioContent(
|
||||
creator: Member,
|
||||
theme: AudioContentTheme,
|
||||
releaseDate: LocalDateTime,
|
||||
isActive: Boolean = true
|
||||
): AudioContent {
|
||||
val content = AudioContent(
|
||||
title = "audio-$releaseDate",
|
||||
detail = "detail",
|
||||
languageCode = "ko",
|
||||
releaseDate = releaseDate
|
||||
).apply {
|
||||
member = creator
|
||||
this.theme = theme
|
||||
duration = "00:10:00"
|
||||
this.isActive = isActive
|
||||
}
|
||||
entityManager.persist(content)
|
||||
return content
|
||||
}
|
||||
|
||||
private fun saveCommunityPost(creator: Member, content: String, isActive: Boolean): CreatorCommunity {
|
||||
val post = CreatorCommunity(
|
||||
content = content,
|
||||
price = 0,
|
||||
isCommentAvailable = true,
|
||||
isAdult = false,
|
||||
isActive = isActive
|
||||
).apply {
|
||||
member = creator
|
||||
}
|
||||
entityManager.persist(post)
|
||||
return post
|
||||
}
|
||||
|
||||
private fun saveNews(
|
||||
memberId: Long,
|
||||
creatorId: Long,
|
||||
sourceKey: String,
|
||||
visibleFromAtUtc: LocalDateTime,
|
||||
rank: Int? = null,
|
||||
isAdult: Boolean = false,
|
||||
newsType: FollowingNewsType = FollowingNewsType.CREATOR_RANKING,
|
||||
targetId: Long = creatorId
|
||||
): HomeFollowingNewsInbox {
|
||||
val news = HomeFollowingNewsInbox(
|
||||
memberId = memberId,
|
||||
creatorId = creatorId,
|
||||
newsType = newsType,
|
||||
sourceKey = sourceKey,
|
||||
targetId = targetId,
|
||||
occurredAtUtc = visibleFromAtUtc.minusHours(1),
|
||||
visibleFromAtUtc = visibleFromAtUtc,
|
||||
creatorNickname = "creator-$creatorId",
|
||||
creatorProfileImagePath = "profile-$creatorId.png",
|
||||
title = "title-$sourceKey",
|
||||
body = "body",
|
||||
thumbnailImagePath = null,
|
||||
rank = rank,
|
||||
isAdult = isAdult
|
||||
)
|
||||
entityManager.persist(news)
|
||||
return news
|
||||
}
|
||||
|
||||
private fun flushAndClear() {
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user