feat(content-all): 전체 탭 조회 서비스를 추가한다
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.all.application
|
||||
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAll
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllQueryPolicy
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllType
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentPage
|
||||
import kr.co.vividnext.sodalive.v2.content.all.port.out.MainContentAllQueryPort
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
class MainContentAllQueryService(
|
||||
private val queryPort: MainContentAllQueryPort,
|
||||
private val memberContentPreferenceService: MemberContentPreferenceService,
|
||||
private val queryPolicy: MainContentAllQueryPolicy = MainContentAllQueryPolicy(),
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
fun getContents(
|
||||
type: String?,
|
||||
sort: String?,
|
||||
dayOfWeek: String?,
|
||||
page: Int?,
|
||||
size: Int?,
|
||||
member: Member?
|
||||
): MainContentAll {
|
||||
val resolvedType = queryPolicy.resolveType(type)
|
||||
val resolvedSort = queryPolicy.resolveSort(sort)
|
||||
val resolvedDayOfWeek = queryPolicy.resolveDayOfWeek(resolvedType, dayOfWeek)
|
||||
val resolvedPage = queryPolicy.createPage(page, size)
|
||||
val now = LocalDateTime.now()
|
||||
val memberId = member?.id
|
||||
val canViewAdultContent = canViewAdultContent(member)
|
||||
|
||||
return when (resolvedType) {
|
||||
MainContentAllType.AUDIO -> getAudioContents(
|
||||
type = resolvedType,
|
||||
sort = resolvedSort,
|
||||
dayOfWeek = resolvedDayOfWeek,
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now
|
||||
)
|
||||
|
||||
MainContentAllType.FREE -> getAudioContents(
|
||||
type = resolvedType,
|
||||
sort = resolvedSort,
|
||||
dayOfWeek = resolvedDayOfWeek,
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
onlyFree = true
|
||||
)
|
||||
|
||||
MainContentAllType.POINT -> getAudioContents(
|
||||
type = resolvedType,
|
||||
sort = resolvedSort,
|
||||
dayOfWeek = resolvedDayOfWeek,
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
onlyPointAvailable = true
|
||||
)
|
||||
|
||||
MainContentAllType.SERIES -> getSeriesContents(
|
||||
type = resolvedType,
|
||||
sort = resolvedSort,
|
||||
dayOfWeek = resolvedDayOfWeek,
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now
|
||||
)
|
||||
|
||||
MainContentAllType.ORIGINAL -> getSeriesContents(
|
||||
type = resolvedType,
|
||||
sort = resolvedSort,
|
||||
dayOfWeek = null,
|
||||
page = resolvedPage,
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
onlyOriginal = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAudioContents(
|
||||
type: MainContentAllType,
|
||||
sort: ContentSort,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek?,
|
||||
page: MainContentPage,
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyFree: Boolean = false,
|
||||
onlyPointAvailable: Boolean = false
|
||||
): MainContentAll {
|
||||
val totalCount = queryPort.countAudios(memberId, canViewAdultContent, now, onlyFree, onlyPointAvailable)
|
||||
val audios = queryPort.findAudios(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
sort = sort,
|
||||
offset = page.offset,
|
||||
limit = page.size + 1,
|
||||
onlyFree = onlyFree,
|
||||
onlyPointAvailable = onlyPointAvailable
|
||||
)
|
||||
|
||||
return MainContentAll(
|
||||
type = type,
|
||||
totalCount = totalCount,
|
||||
audios = queryPolicy.limitItems(audios, page),
|
||||
series = emptyList(),
|
||||
sort = sort,
|
||||
dayOfWeek = dayOfWeek,
|
||||
page = page,
|
||||
hasNext = queryPolicy.hasNext(audios, page)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSeriesContents(
|
||||
type: MainContentAllType,
|
||||
sort: ContentSort,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek?,
|
||||
page: MainContentPage,
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyOriginal: Boolean = false
|
||||
): MainContentAll {
|
||||
val totalCount = queryPort.countSeries(memberId, canViewAdultContent, now, onlyOriginal, dayOfWeek)
|
||||
val series = queryPort.findSeries(
|
||||
memberId = memberId,
|
||||
canViewAdultContent = canViewAdultContent,
|
||||
now = now,
|
||||
sort = sort,
|
||||
offset = page.offset,
|
||||
limit = page.size + 1,
|
||||
onlyOriginal = onlyOriginal,
|
||||
dayOfWeek = dayOfWeek,
|
||||
locale = langContext.lang.code
|
||||
)
|
||||
|
||||
return MainContentAll(
|
||||
type = type,
|
||||
totalCount = totalCount,
|
||||
audios = emptyList(),
|
||||
series = queryPolicy.limitItems(series, page),
|
||||
sort = sort,
|
||||
dayOfWeek = dayOfWeek,
|
||||
page = page,
|
||||
hasNext = queryPolicy.hasNext(series, page)
|
||||
)
|
||||
}
|
||||
|
||||
private fun canViewAdultContent(member: Member?): Boolean {
|
||||
if (member == null) return false
|
||||
return memberContentPreferenceService.canViewAdultContent(member)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.all.port.out
|
||||
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllAudio
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllSeries
|
||||
import java.time.LocalDateTime
|
||||
|
||||
interface MainContentAllQueryPort {
|
||||
fun countAudios(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyFree: Boolean = false,
|
||||
onlyPointAvailable: Boolean = false
|
||||
): Int
|
||||
|
||||
fun findAudios(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
sort: ContentSort,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
onlyFree: Boolean = false,
|
||||
onlyPointAvailable: Boolean = false
|
||||
): List<MainContentAllAudio>
|
||||
|
||||
fun countSeries(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyOriginal: Boolean = false,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek? = null
|
||||
): Int
|
||||
|
||||
fun findSeries(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
sort: ContentSort,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
onlyOriginal: Boolean = false,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek? = null,
|
||||
locale: String
|
||||
): List<MainContentAllSeries>
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package kr.co.vividnext.sodalive.v2.content.all.application
|
||||
|
||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||
import kr.co.vividnext.sodalive.i18n.Lang
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.member.MemberRole
|
||||
import kr.co.vividnext.sodalive.member.contentpreference.MemberContentPreferenceService
|
||||
import kr.co.vividnext.sodalive.v2.common.domain.ContentSort
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllAudio
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllQueryPolicy
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllSeries
|
||||
import kr.co.vividnext.sodalive.v2.content.all.domain.MainContentAllType
|
||||
import kr.co.vividnext.sodalive.v2.content.all.port.out.MainContentAllQueryPort
|
||||
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.mockito.Mockito
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class MainContentAllQueryServiceTest {
|
||||
@Test
|
||||
@DisplayName("AUDIO 타입은 audio port를 기본 필터로 호출한다")
|
||||
fun shouldQueryAudiosForAudioType() {
|
||||
val port = FakeMainContentAllQueryPort()
|
||||
val service = createService(port)
|
||||
|
||||
val tab = service.getContents(type = "AUDIO", sort = "POPULAR", dayOfWeek = "MON", page = 1, size = 20, member = null)
|
||||
|
||||
assertEquals(MainContentAllType.AUDIO, tab.type)
|
||||
assertEquals("audio", port.lastListKind)
|
||||
assertEquals(ContentSort.POPULAR, port.lastSort)
|
||||
assertEquals(20L, port.lastOffset)
|
||||
assertEquals(21, port.lastLimit)
|
||||
assertFalse(port.lastOnlyFree)
|
||||
assertFalse(port.lastOnlyPointAvailable)
|
||||
assertEquals(20, tab.audios.size)
|
||||
assertTrue(tab.hasNext)
|
||||
assertEquals(emptyList<MainContentAllSeries>(), tab.series)
|
||||
assertEquals(null, tab.dayOfWeek)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("FREE 타입은 audio port를 무료 필터로 호출한다")
|
||||
fun shouldQueryAudiosForFreeType() {
|
||||
val port = FakeMainContentAllQueryPort()
|
||||
val service = createService(port)
|
||||
|
||||
val tab = service.getContents(type = "FREE", sort = "LATEST", dayOfWeek = null, page = 0, size = 20, member = null)
|
||||
|
||||
assertEquals(MainContentAllType.FREE, tab.type)
|
||||
assertEquals("audio", port.lastListKind)
|
||||
assertTrue(port.lastOnlyFree)
|
||||
assertFalse(port.lastOnlyPointAvailable)
|
||||
assertEquals(21, port.lastLimit)
|
||||
assertEquals(20, tab.audios.size)
|
||||
assertTrue(tab.hasNext)
|
||||
assertEquals(emptyList<MainContentAllSeries>(), tab.series)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("POINT 타입은 audio port를 포인트 사용 가능 필터로 호출한다")
|
||||
fun shouldQueryAudiosForPointType() {
|
||||
val port = FakeMainContentAllQueryPort()
|
||||
val service = createService(port)
|
||||
|
||||
val tab = service.getContents(type = "POINT", sort = "PRICE_LOW", dayOfWeek = null, page = 0, size = 20, member = null)
|
||||
|
||||
assertEquals(MainContentAllType.POINT, tab.type)
|
||||
assertEquals("audio", port.lastListKind)
|
||||
assertEquals(ContentSort.PRICE_LOW, port.lastSort)
|
||||
assertFalse(port.lastOnlyFree)
|
||||
assertTrue(port.lastOnlyPointAvailable)
|
||||
assertEquals(21, port.lastLimit)
|
||||
assertEquals(20, tab.audios.size)
|
||||
assertTrue(tab.hasNext)
|
||||
assertEquals(emptyList<MainContentAllSeries>(), tab.series)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SERIES는 요일을 전달하고 ORIGINAL은 original 필터와 dayOfWeek null을 전달한다")
|
||||
fun shouldQuerySeriesByType() {
|
||||
val seriesPort = FakeMainContentAllQueryPort()
|
||||
val service = createService(seriesPort, lang = Lang.JA)
|
||||
|
||||
val seriesTab = service.getContents("SERIES", "POPULAR", "MON", 0, 20, null)
|
||||
|
||||
assertEquals(MainContentAllType.SERIES, seriesTab.type)
|
||||
assertEquals("series", seriesPort.lastListKind)
|
||||
assertEquals(SeriesPublishedDaysOfWeek.MON, seriesPort.lastDayOfWeek)
|
||||
assertEquals("ja", seriesPort.lastLocale)
|
||||
assertFalse(seriesPort.lastOnlyOriginal)
|
||||
|
||||
val originalPort = FakeMainContentAllQueryPort()
|
||||
val originalService = createService(originalPort)
|
||||
|
||||
val originalTab = originalService.getContents("ORIGINAL", "POPULAR", "MON", 0, 20, null)
|
||||
|
||||
assertEquals(MainContentAllType.ORIGINAL, originalTab.type)
|
||||
assertEquals("series", originalPort.lastListKind)
|
||||
assertEquals(null, originalPort.lastDayOfWeek)
|
||||
assertTrue(originalPort.lastOnlyOriginal)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("비회원은 성인 콘텐츠 비노출로 조회하고 회원은 preference 결과를 전달한다")
|
||||
fun shouldPassAdultVisibilityByMember() {
|
||||
val anonymousPort = FakeMainContentAllQueryPort()
|
||||
createService(anonymousPort).getContents("AUDIO", null, null, null, null, null)
|
||||
|
||||
assertEquals(null, anonymousPort.lastMemberId)
|
||||
assertFalse(anonymousPort.lastCanViewAdultContent)
|
||||
|
||||
val member = Member(
|
||||
email = "viewer@test.com",
|
||||
password = "password",
|
||||
nickname = "viewer",
|
||||
role = MemberRole.USER
|
||||
).apply { id = 10L }
|
||||
val memberPort = FakeMainContentAllQueryPort()
|
||||
val preferenceService = Mockito.mock(MemberContentPreferenceService::class.java)
|
||||
Mockito.doReturn(true).`when`(preferenceService).canViewAdultContent(member)
|
||||
|
||||
createService(memberPort, preferenceService).getContents("AUDIO", null, null, null, null, member)
|
||||
|
||||
assertEquals(10L, memberPort.lastMemberId)
|
||||
assertTrue(memberPort.lastCanViewAdultContent)
|
||||
Mockito.verify(preferenceService).canViewAdultContent(member)
|
||||
}
|
||||
|
||||
private fun createService(
|
||||
port: MainContentAllQueryPort,
|
||||
preferenceService: MemberContentPreferenceService = Mockito.mock(MemberContentPreferenceService::class.java),
|
||||
lang: Lang = Lang.EN
|
||||
): MainContentAllQueryService {
|
||||
val langContext = LangContext()
|
||||
langContext.setLang(lang)
|
||||
return MainContentAllQueryService(
|
||||
queryPort = port,
|
||||
memberContentPreferenceService = preferenceService,
|
||||
queryPolicy = MainContentAllQueryPolicy(),
|
||||
langContext = langContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeMainContentAllQueryPort : MainContentAllQueryPort {
|
||||
var lastListKind: String? = null
|
||||
var lastMemberId: Long? = null
|
||||
var lastCanViewAdultContent: Boolean = false
|
||||
var lastSort: ContentSort? = null
|
||||
var lastOffset: Long? = null
|
||||
var lastLimit: Int? = null
|
||||
var lastOnlyFree: Boolean = false
|
||||
var lastOnlyPointAvailable: Boolean = false
|
||||
var lastOnlyOriginal: Boolean = false
|
||||
var lastDayOfWeek: SeriesPublishedDaysOfWeek? = null
|
||||
var lastLocale: String? = null
|
||||
|
||||
override fun countAudios(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyFree: Boolean,
|
||||
onlyPointAvailable: Boolean
|
||||
): Int {
|
||||
lastMemberId = memberId
|
||||
lastCanViewAdultContent = canViewAdultContent
|
||||
lastOnlyFree = onlyFree
|
||||
lastOnlyPointAvailable = onlyPointAvailable
|
||||
return 30
|
||||
}
|
||||
|
||||
override fun findAudios(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
sort: ContentSort,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
onlyFree: Boolean,
|
||||
onlyPointAvailable: Boolean
|
||||
): List<MainContentAllAudio> {
|
||||
lastListKind = "audio"
|
||||
lastMemberId = memberId
|
||||
lastCanViewAdultContent = canViewAdultContent
|
||||
lastSort = sort
|
||||
lastOffset = offset
|
||||
lastLimit = limit
|
||||
lastOnlyFree = onlyFree
|
||||
lastOnlyPointAvailable = onlyPointAvailable
|
||||
return (1L..limit.toLong()).map { id ->
|
||||
MainContentAllAudio(
|
||||
audioContentId = id,
|
||||
title = "audio-$id",
|
||||
imageUrl = null,
|
||||
price = 0,
|
||||
isAdult = false,
|
||||
isPointAvailable = true,
|
||||
isFirstContent = id == 1L,
|
||||
isOriginalSeries = false,
|
||||
creatorNickname = "creator"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun countSeries(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
onlyOriginal: Boolean,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek?
|
||||
): Int {
|
||||
lastMemberId = memberId
|
||||
lastCanViewAdultContent = canViewAdultContent
|
||||
lastOnlyOriginal = onlyOriginal
|
||||
lastDayOfWeek = dayOfWeek
|
||||
return 10
|
||||
}
|
||||
|
||||
override fun findSeries(
|
||||
memberId: Long?,
|
||||
canViewAdultContent: Boolean,
|
||||
now: LocalDateTime,
|
||||
sort: ContentSort,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
onlyOriginal: Boolean,
|
||||
dayOfWeek: SeriesPublishedDaysOfWeek?,
|
||||
locale: String
|
||||
): List<MainContentAllSeries> {
|
||||
lastListKind = "series"
|
||||
lastMemberId = memberId
|
||||
lastCanViewAdultContent = canViewAdultContent
|
||||
lastSort = sort
|
||||
lastOffset = offset
|
||||
lastLimit = limit
|
||||
lastOnlyOriginal = onlyOriginal
|
||||
lastDayOfWeek = dayOfWeek
|
||||
lastLocale = locale
|
||||
return listOf(
|
||||
MainContentAllSeries(
|
||||
seriesId = 1L,
|
||||
title = "series",
|
||||
coverImageUrl = null,
|
||||
creatorNickname = "creator",
|
||||
isOriginal = onlyOriginal,
|
||||
isAdult = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user