feat(series-banner): 시리즈 배너를 언어별로 등록하고 노출한다

This commit is contained in:
2026-04-02 16:59:31 +09:00
parent d3b1f4bcd4
commit 3c32559c5d
10 changed files with 371 additions and 8 deletions

View File

@@ -56,7 +56,9 @@ class AdminContentSeriesBannerController(
val banners = bannerService.getActiveBanners(pageable)
val response = SeriesBannerListPageResponse(
totalCount = banners.totalElements,
content = banners.content.map { SeriesBannerResponse.from(it, imageHost) }
content = banners.content.map {
SeriesBannerResponse.from(it, imageHost, appendLanguageToSeriesTitle = true)
}
)
ApiResponse.ok(response)
}
@@ -82,7 +84,7 @@ class AdminContentSeriesBannerController(
val objectMapper = ObjectMapper()
val request = objectMapper.readValue(requestString, SeriesBannerRegisterRequest::class.java)
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "")
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "", lang = request.lang)
val imagePath = saveImage(banner.id!!, image)
val updatedBanner = bannerService.updateBanner(banner.id!!, imagePath)
val response = SeriesBannerResponse.from(updatedBanner, imageHost)

View File

@@ -2,10 +2,12 @@ package kr.co.vividnext.sodalive.admin.content.series.banner.dto
import com.fasterxml.jackson.annotation.JsonProperty
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
import kr.co.vividnext.sodalive.i18n.Lang
// 시리즈 배너 등록 요청 DTO
data class SeriesBannerRegisterRequest(
@JsonProperty("seriesId") val seriesId: Long
@JsonProperty("seriesId") val seriesId: Long,
@JsonProperty("lang") val lang: Lang? = null
)
// 시리즈 배너 수정 요청 DTO
@@ -22,14 +24,30 @@ data class SeriesBannerResponse(
val seriesTitle: String
) {
companion object {
fun from(banner: SeriesBanner, imageHost: String): SeriesBannerResponse {
fun from(
banner: SeriesBanner,
imageHost: String,
appendLanguageToSeriesTitle: Boolean = false
): SeriesBannerResponse {
return SeriesBannerResponse(
id = banner.id!!,
imagePath = "$imageHost/${banner.imagePath}",
seriesId = banner.series.id!!,
seriesTitle = banner.series.title
seriesTitle = if (appendLanguageToSeriesTitle) {
"${banner.series.title} (${getLanguageLabel(banner.lang)})"
} else {
banner.series.title
}
)
}
private fun getLanguageLabel(lang: Lang): String {
return when (lang) {
Lang.KO -> "한국어"
Lang.EN -> "영어"
Lang.JA -> "일본어"
}
}
}
}

View File

@@ -6,6 +6,7 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.series.ContentSeriesService
import kr.co.vividnext.sodalive.content.series.main.banner.ContentSeriesBannerService
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 org.springframework.beans.factory.annotation.Value
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController
class SeriesMainController(
private val contentSeriesService: ContentSeriesService,
private val bannerService: ContentSeriesBannerService,
private val langContext: LangContext,
private val memberContentPreferenceService: MemberContentPreferenceService,
@Value("\${cloud.aws.cloud-front.host}")
@@ -33,7 +35,7 @@ class SeriesMainController(
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val preference = resolvePreference(member)
val banners = bannerService.getActiveBanners(PageRequest.of(0, 10))
val banners = bannerService.getDisplayBanners(PageRequest.of(0, 10), langContext.lang)
.content
.map {
SeriesBannerResponse.from(it, imageHost)

View File

@@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.content.series.main.banner
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.i18n.Lang
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
@@ -16,22 +17,28 @@ class ContentSeriesBannerService(
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
}
fun getDisplayBanners(pageable: Pageable, lang: Lang): Page<SeriesBanner> {
return bannerRepository.findByIsActiveTrueAndLangOrderBySortOrderAsc(lang, pageable)
}
fun getBannerById(bannerId: Long): SeriesBanner {
return bannerRepository.findById(bannerId)
.orElseThrow { SodaException(messageKey = "series.banner.error.not_found") }
}
@Transactional
fun registerBanner(seriesId: Long, imagePath: String): SeriesBanner {
fun registerBanner(seriesId: Long, imagePath: String, lang: Lang? = null): SeriesBanner {
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
?: throw SodaException(messageKey = "series.banner.error.series_not_found")
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
val finalLang = lang ?: Lang.KO
val banner = SeriesBanner(
imagePath = imagePath,
series = series,
sortOrder = finalSortOrder
sortOrder = finalSortOrder,
lang = finalLang
)
return bannerRepository.save(banner)
}

View File

@@ -2,7 +2,11 @@ package kr.co.vividnext.sodalive.content.series.main.banner
import kr.co.vividnext.sodalive.common.BaseEntity
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
import kr.co.vividnext.sodalive.i18n.Lang
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.EnumType
import javax.persistence.Enumerated
import javax.persistence.FetchType
import javax.persistence.JoinColumn
import javax.persistence.ManyToOne
@@ -25,6 +29,10 @@ class SeriesBanner(
// 정렬 순서 (낮을수록 먼저 표시)
var sortOrder: Int = 0,
@Column(nullable = false)
@Enumerated(EnumType.STRING)
var lang: Lang = Lang.KO,
// 활성화 여부 (소프트 삭제용)
var isActive: Boolean = true
) : BaseEntity()

View File

@@ -1,5 +1,6 @@
package kr.co.vividnext.sodalive.content.series.main.banner
import kr.co.vividnext.sodalive.i18n.Lang
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
@@ -10,6 +11,8 @@ import org.springframework.stereotype.Repository
interface SeriesBannerRepository : JpaRepository<SeriesBanner, Long> {
fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<SeriesBanner>
fun findByIsActiveTrueAndLangOrderBySortOrderAsc(lang: Lang, pageable: Pageable): Page<SeriesBanner>
@Query("SELECT MAX(b.sortOrder) FROM SeriesBanner b WHERE b.isActive = true")
fun findMaxSortOrder(): Int?
}