test #428
@@ -3,9 +3,7 @@ package kr.co.vividnext.sodalive.admin.content.series.banner
|
|||||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import kr.co.vividnext.sodalive.admin.content.banner.UpdateBannerOrdersRequest
|
import kr.co.vividnext.sodalive.admin.content.banner.UpdateBannerOrdersRequest
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerListPageResponse
|
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerRegisterRequest
|
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerRegisterRequest
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerResponse
|
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerUpdateRequest
|
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerUpdateRequest
|
||||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
@@ -53,13 +51,7 @@ class AdminContentSeriesBannerController(
|
|||||||
@RequestParam(defaultValue = "20") size: Int
|
@RequestParam(defaultValue = "20") size: Int
|
||||||
) = run {
|
) = run {
|
||||||
val pageable = PageRequest.of(page, size)
|
val pageable = PageRequest.of(page, size)
|
||||||
val banners = bannerService.getActiveBanners(pageable)
|
val response = bannerService.getActiveBanners(pageable, imageHost)
|
||||||
val response = SeriesBannerListPageResponse(
|
|
||||||
totalCount = banners.totalElements,
|
|
||||||
content = banners.content.map {
|
|
||||||
SeriesBannerResponse.from(it, imageHost, appendLanguageToSeriesTitle = true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +60,7 @@ class AdminContentSeriesBannerController(
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/{bannerId}")
|
@GetMapping("/{bannerId}")
|
||||||
fun getBannerDetail(@PathVariable bannerId: Long) = run {
|
fun getBannerDetail(@PathVariable bannerId: Long) = run {
|
||||||
val banner = bannerService.getBannerById(bannerId)
|
val response = bannerService.getBannerDetailResponse(bannerId, imageHost)
|
||||||
val response = SeriesBannerResponse.from(banner, imageHost)
|
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +77,7 @@ class AdminContentSeriesBannerController(
|
|||||||
|
|
||||||
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "", lang = request.lang)
|
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "", lang = request.lang)
|
||||||
val imagePath = saveImage(banner.id!!, image)
|
val imagePath = saveImage(banner.id!!, image)
|
||||||
val updatedBanner = bannerService.updateBanner(banner.id!!, imagePath)
|
val response = bannerService.updateBannerResponse(banner.id!!, imagePath, imageHost = imageHost)
|
||||||
val response = SeriesBannerResponse.from(updatedBanner, imageHost)
|
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +94,12 @@ class AdminContentSeriesBannerController(
|
|||||||
// 배너 존재 확인
|
// 배너 존재 확인
|
||||||
bannerService.getBannerById(request.bannerId)
|
bannerService.getBannerById(request.bannerId)
|
||||||
val imagePath = saveImage(request.bannerId, image)
|
val imagePath = saveImage(request.bannerId, image)
|
||||||
val updated = bannerService.updateBanner(
|
val response = bannerService.updateBannerResponse(
|
||||||
bannerId = request.bannerId,
|
bannerId = request.bannerId,
|
||||||
imagePath = imagePath,
|
imagePath = imagePath,
|
||||||
seriesId = request.seriesId
|
seriesId = request.seriesId,
|
||||||
|
imageHost = imageHost
|
||||||
)
|
)
|
||||||
val response = SeriesBannerResponse.from(updated, imageHost)
|
|
||||||
ApiResponse.ok(response)
|
ApiResponse.ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package kr.co.vividnext.sodalive.content.series.main
|
package kr.co.vividnext.sodalive.content.series.main
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerResponse
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
|
||||||
@@ -35,11 +34,7 @@ class SeriesMainController(
|
|||||||
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
|
||||||
val preference = resolvePreference(member)
|
val preference = resolvePreference(member)
|
||||||
|
|
||||||
val banners = bannerService.getDisplayBanners(PageRequest.of(0, 10), langContext.lang)
|
val banners = bannerService.getDisplayBannerResponses(PageRequest.of(0, 10), langContext.lang, imageHost)
|
||||||
.content
|
|
||||||
.map {
|
|
||||||
SeriesBannerResponse.from(it, imageHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
val completedSeriesList = contentSeriesService.getSeriesList(
|
val completedSeriesList = contentSeriesService.getSeriesList(
|
||||||
creatorId = null,
|
creatorId = null,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package kr.co.vividnext.sodalive.content.series.main.banner
|
package kr.co.vividnext.sodalive.content.series.main.banner
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerListPageResponse
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.i18n.Lang
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
import org.springframework.data.domain.Page
|
import org.springframework.data.domain.Page
|
||||||
@@ -9,23 +11,54 @@ import org.springframework.stereotype.Service
|
|||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Transactional(readOnly = true)
|
||||||
class ContentSeriesBannerService(
|
class ContentSeriesBannerService(
|
||||||
private val bannerRepository: SeriesBannerRepository,
|
private val bannerRepository: SeriesBannerRepository,
|
||||||
private val seriesRepository: AdminContentSeriesRepository
|
private val seriesRepository: AdminContentSeriesRepository
|
||||||
) {
|
) {
|
||||||
fun getActiveBanners(pageable: Pageable): Page<SeriesBanner> {
|
fun getActiveBanners(pageable: Pageable, imageHost: String): SeriesBannerListPageResponse {
|
||||||
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
val banners = bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
|
return SeriesBannerListPageResponse(
|
||||||
|
totalCount = banners.totalElements,
|
||||||
|
content = banners.content.map {
|
||||||
|
SeriesBannerResponse.from(
|
||||||
|
banner = it,
|
||||||
|
imageHost = imageHost,
|
||||||
|
appendLanguageToSeriesTitle = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDisplayBanners(pageable: Pageable, lang: Lang): Page<SeriesBanner> {
|
fun getDisplayBanners(pageable: Pageable, lang: Lang): Page<SeriesBanner> {
|
||||||
return bannerRepository.findByIsActiveTrueAndLangOrderBySortOrderAsc(lang, pageable)
|
return bannerRepository.findByIsActiveTrueAndLangOrderBySortOrderAsc(lang, pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDisplayBannerResponses(pageable: Pageable, lang: Lang, imageHost: String): List<SeriesBannerResponse> {
|
||||||
|
return getDisplayBanners(pageable, lang).content.map {
|
||||||
|
SeriesBannerResponse.from(it, imageHost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getBannerById(bannerId: Long): SeriesBanner {
|
fun getBannerById(bannerId: Long): SeriesBanner {
|
||||||
return bannerRepository.findById(bannerId)
|
return bannerRepository.findById(bannerId)
|
||||||
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
|
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBannerDetailResponse(bannerId: Long, imageHost: String): SeriesBannerResponse {
|
||||||
|
return SeriesBannerResponse.from(getBannerById(bannerId), imageHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateBannerResponse(
|
||||||
|
bannerId: Long,
|
||||||
|
imagePath: String? = null,
|
||||||
|
seriesId: Long? = null,
|
||||||
|
imageHost: String
|
||||||
|
): SeriesBannerResponse {
|
||||||
|
return SeriesBannerResponse.from(updateBanner(bannerId, imagePath, seriesId), imageHost)
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun registerBanner(seriesId: Long, imagePath: String, lang: Lang? = null): SeriesBanner {
|
fun registerBanner(seriesId: Long, imagePath: String, lang: Lang? = null): SeriesBanner {
|
||||||
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.series.main.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.genre.SeriesGenre
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
|
import kr.co.vividnext.sodalive.i18n.Lang
|
||||||
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.test.annotation.DirtiesContext
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate
|
||||||
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
properties = [
|
||||||
|
"cloud.aws.cloud-front.host=https://cdn.test",
|
||||||
|
"spring.datasource.url=jdbc:h2:mem:series-banner-service-integration;" +
|
||||||
|
"MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||||
|
class ContentSeriesBannerServiceIntegrationTest @Autowired constructor(
|
||||||
|
private val service: ContentSeriesBannerService,
|
||||||
|
private val entityManager: EntityManager,
|
||||||
|
private val transactionTemplate: TransactionTemplate
|
||||||
|
) {
|
||||||
|
@Test
|
||||||
|
@DisplayName("OSIV off 환경에서 관리자 시리즈 배너 목록 응답 생성 시 lazy 초기화 예외가 발생하지 않는다")
|
||||||
|
fun shouldCreateAdminSeriesBannerListResponseWhenOpenInViewIsDisabled() {
|
||||||
|
val fixtureIds = createBannerFixture(suffix = "list", sortOrder = 1)
|
||||||
|
|
||||||
|
val response = service.getActiveBanners(PageRequest.of(0, 20), "https://cdn.test")
|
||||||
|
|
||||||
|
assertEquals(1, response.totalCount)
|
||||||
|
assertEquals(fixtureIds.seriesId, response.content.first().seriesId)
|
||||||
|
assertEquals("series-admin-banner (일본어)", response.content.first().seriesTitle)
|
||||||
|
assertEquals("https://cdn.test/banner/jp.png", response.content.first().imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("OSIV off 환경에서 관리자 시리즈 배너 상세 응답 생성 시 lazy 초기화 예외가 발생하지 않는다")
|
||||||
|
fun shouldCreateAdminSeriesBannerDetailResponseWhenOpenInViewIsDisabled() {
|
||||||
|
val fixtureIds = createBannerFixture(suffix = "detail", sortOrder = 2)
|
||||||
|
|
||||||
|
val response = service.getBannerDetailResponse(fixtureIds.bannerId, "https://cdn.test")
|
||||||
|
|
||||||
|
assertEquals(fixtureIds.bannerId, response.id)
|
||||||
|
assertEquals(fixtureIds.seriesId, response.seriesId)
|
||||||
|
assertEquals("series-admin-banner", response.seriesTitle)
|
||||||
|
assertEquals("https://cdn.test/banner/jp.png", response.imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("OSIV off 환경에서 공개 시리즈 메인 배너 응답 생성 시 lazy 초기화 예외가 발생하지 않는다")
|
||||||
|
fun shouldCreatePublicMainSeriesBannerResponseWhenOpenInViewIsDisabled() {
|
||||||
|
val fixtureIds = createBannerFixture(suffix = "public", sortOrder = 3)
|
||||||
|
|
||||||
|
val response = service.getDisplayBannerResponses(PageRequest.of(0, 10), Lang.JA, "https://cdn.test")
|
||||||
|
|
||||||
|
assertEquals(1, response.size)
|
||||||
|
assertEquals(fixtureIds.seriesId, response.first().seriesId)
|
||||||
|
assertEquals("series-admin-banner", response.first().seriesTitle)
|
||||||
|
assertEquals("https://cdn.test/banner/jp.png", response.first().imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("OSIV off 환경에서 관리자 시리즈 배너 수정 응답 생성 시 lazy 초기화 예외가 발생하지 않는다")
|
||||||
|
fun shouldCreateAdminSeriesBannerUpdateResponseWhenOpenInViewIsDisabled() {
|
||||||
|
val fixtureIds = createBannerFixture(suffix = "update", sortOrder = 4)
|
||||||
|
|
||||||
|
val response = service.updateBannerResponse(
|
||||||
|
bannerId = fixtureIds.bannerId,
|
||||||
|
imagePath = "banner/updated.png",
|
||||||
|
imageHost = "https://cdn.test"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(fixtureIds.bannerId, response.id)
|
||||||
|
assertEquals(fixtureIds.seriesId, response.seriesId)
|
||||||
|
assertEquals("series-admin-banner", response.seriesTitle)
|
||||||
|
assertEquals("https://cdn.test/banner/updated.png", response.imagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBannerFixture(suffix: String, sortOrder: Int): BannerFixtureIds {
|
||||||
|
return transactionTemplate.execute {
|
||||||
|
val creator = Member(
|
||||||
|
email = "series-admin-banner-service-$suffix@test.com",
|
||||||
|
password = "password",
|
||||||
|
nickname = "series-admin-banner-creator",
|
||||||
|
role = MemberRole.CREATOR
|
||||||
|
)
|
||||||
|
entityManager.persist(creator)
|
||||||
|
|
||||||
|
val genre = SeriesGenre(genre = "series-admin-banner-genre")
|
||||||
|
entityManager.persist(genre)
|
||||||
|
|
||||||
|
val series = Series(
|
||||||
|
title = "series-admin-banner",
|
||||||
|
introduction = "introduction",
|
||||||
|
languageCode = "ko"
|
||||||
|
).apply {
|
||||||
|
member = creator
|
||||||
|
this.genre = genre
|
||||||
|
}
|
||||||
|
entityManager.persist(series)
|
||||||
|
|
||||||
|
val banner = SeriesBanner(
|
||||||
|
imagePath = "banner/jp.png",
|
||||||
|
series = series,
|
||||||
|
sortOrder = sortOrder,
|
||||||
|
lang = Lang.JA
|
||||||
|
)
|
||||||
|
entityManager.persist(
|
||||||
|
banner
|
||||||
|
)
|
||||||
|
|
||||||
|
entityManager.flush()
|
||||||
|
val fixtureIds = BannerFixtureIds(
|
||||||
|
seriesId = series.id!!,
|
||||||
|
bannerId = banner.id!!
|
||||||
|
)
|
||||||
|
entityManager.clear()
|
||||||
|
fixtureIds
|
||||||
|
}!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class BannerFixtureIds(
|
||||||
|
val seriesId: Long,
|
||||||
|
val bannerId: Long
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user