Compare commits
2 Commits
39760e16ff
...
27be9a4fc2
| Author | SHA1 | Date | |
|---|---|---|---|
| 27be9a4fc2 | |||
| 9464cc5ed4 |
@@ -0,0 +1,144 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.content.series.banner
|
||||||
|
|
||||||
|
import com.amazonaws.services.s3.model.ObjectMetadata
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
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.SeriesBannerResponse
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.banner.dto.SeriesBannerUpdateRequest
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/admin/audio-content/series/banner")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
class AdminContentSeriesBannerController(
|
||||||
|
private val bannerService: AdminContentSeriesBannerService,
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val s3Bucket: String,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val imageHost: String
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 활성화된 배너 목록 조회 API
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
fun getBannerList(
|
||||||
|
@RequestParam(defaultValue = "0") page: Int,
|
||||||
|
@RequestParam(defaultValue = "20") size: Int
|
||||||
|
) = run {
|
||||||
|
val pageable = PageRequest.of(page, size)
|
||||||
|
val banners = bannerService.getActiveBanners(pageable)
|
||||||
|
val response = SeriesBannerListPageResponse(
|
||||||
|
totalCount = banners.totalElements,
|
||||||
|
content = banners.content.map { SeriesBannerResponse.from(it, imageHost) }
|
||||||
|
)
|
||||||
|
ApiResponse.ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 상세 조회 API
|
||||||
|
*/
|
||||||
|
@GetMapping("/{bannerId}")
|
||||||
|
fun getBannerDetail(@PathVariable bannerId: Long) = run {
|
||||||
|
val banner = bannerService.getBannerById(bannerId)
|
||||||
|
val response = SeriesBannerResponse.from(banner, imageHost)
|
||||||
|
ApiResponse.ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 등록 API
|
||||||
|
*/
|
||||||
|
@PostMapping("/register")
|
||||||
|
fun registerBanner(
|
||||||
|
@RequestPart("image") image: MultipartFile,
|
||||||
|
@RequestPart("request") requestString: String
|
||||||
|
) = run {
|
||||||
|
val objectMapper = ObjectMapper()
|
||||||
|
val request = objectMapper.readValue(requestString, SeriesBannerRegisterRequest::class.java)
|
||||||
|
|
||||||
|
val banner = bannerService.registerBanner(seriesId = request.seriesId, imagePath = "")
|
||||||
|
val imagePath = saveImage(banner.id!!, image)
|
||||||
|
val updatedBanner = bannerService.updateBanner(banner.id!!, imagePath)
|
||||||
|
val response = SeriesBannerResponse.from(updatedBanner, imageHost)
|
||||||
|
ApiResponse.ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 수정 API
|
||||||
|
*/
|
||||||
|
@PutMapping("/update")
|
||||||
|
fun updateBanner(
|
||||||
|
@RequestPart("image") image: MultipartFile,
|
||||||
|
@RequestPart("request") requestString: String
|
||||||
|
) = run {
|
||||||
|
val objectMapper = ObjectMapper()
|
||||||
|
val request = objectMapper.readValue(requestString, SeriesBannerUpdateRequest::class.java)
|
||||||
|
// 배너 존재 확인
|
||||||
|
bannerService.getBannerById(request.bannerId)
|
||||||
|
val imagePath = saveImage(request.bannerId, image)
|
||||||
|
val updated = bannerService.updateBanner(
|
||||||
|
bannerId = request.bannerId,
|
||||||
|
imagePath = imagePath,
|
||||||
|
seriesId = request.seriesId
|
||||||
|
)
|
||||||
|
val response = SeriesBannerResponse.from(updated, imageHost)
|
||||||
|
ApiResponse.ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 삭제 API (소프트 삭제)
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{bannerId}")
|
||||||
|
fun deleteBanner(@PathVariable bannerId: Long) = run {
|
||||||
|
bannerService.deleteBanner(bannerId)
|
||||||
|
ApiResponse.ok("배너가 성공적으로 삭제되었습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배너 정렬 순서 일괄 변경 API
|
||||||
|
*/
|
||||||
|
@PutMapping("/orders")
|
||||||
|
fun updateBannerOrders(
|
||||||
|
@RequestBody request: UpdateBannerOrdersRequest
|
||||||
|
) = run {
|
||||||
|
bannerService.updateBannerOrders(request.ids)
|
||||||
|
ApiResponse.ok(null, "배너 정렬 순서가 성공적으로 변경되었습니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveImage(bannerId: Long, image: MultipartFile): String {
|
||||||
|
try {
|
||||||
|
val metadata = ObjectMetadata()
|
||||||
|
metadata.contentLength = image.size
|
||||||
|
val fileName = generateFileName("series-banner")
|
||||||
|
return s3Uploader.upload(
|
||||||
|
inputStream = image.inputStream,
|
||||||
|
bucket = s3Bucket,
|
||||||
|
filePath = "series_banner/$bannerId/$fileName",
|
||||||
|
metadata = metadata
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw SodaException("이미지 저장에 실패했습니다: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package kr.co.vividnext.sodalive.admin.content.series.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.series.main.banner.SeriesBannerRepository
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminContentSeriesBannerService(
|
||||||
|
private val bannerRepository: SeriesBannerRepository,
|
||||||
|
private val seriesRepository: AdminContentSeriesRepository
|
||||||
|
) {
|
||||||
|
fun getActiveBanners(pageable: Pageable): Page<SeriesBanner> {
|
||||||
|
return bannerRepository.findByIsActiveTrueOrderBySortOrderAsc(pageable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBannerById(bannerId: Long): SeriesBanner {
|
||||||
|
return bannerRepository.findById(bannerId)
|
||||||
|
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.springframework.transaction.annotation.Transactional
|
||||||
|
fun registerBanner(seriesId: Long, imagePath: String): SeriesBanner {
|
||||||
|
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
||||||
|
?: throw SodaException("시리즈를 찾을 수 없습니다: $seriesId")
|
||||||
|
|
||||||
|
val finalSortOrder = (bannerRepository.findMaxSortOrder() ?: 0) + 1
|
||||||
|
|
||||||
|
val banner = SeriesBanner(
|
||||||
|
imagePath = imagePath,
|
||||||
|
series = series,
|
||||||
|
sortOrder = finalSortOrder
|
||||||
|
)
|
||||||
|
return bannerRepository.save(banner)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.springframework.transaction.annotation.Transactional
|
||||||
|
fun updateBanner(
|
||||||
|
bannerId: Long,
|
||||||
|
imagePath: String? = null,
|
||||||
|
seriesId: Long? = null
|
||||||
|
): SeriesBanner {
|
||||||
|
val banner = bannerRepository.findById(bannerId)
|
||||||
|
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
|
||||||
|
if (!banner.isActive) throw SodaException("비활성화된 배너는 수정할 수 없습니다: $bannerId")
|
||||||
|
|
||||||
|
if (imagePath != null) banner.imagePath = imagePath
|
||||||
|
|
||||||
|
if (seriesId != null) {
|
||||||
|
val series = seriesRepository.findByIdAndActiveTrue(seriesId)
|
||||||
|
?: throw SodaException("시리즈를 찾을 수 없습니다: $seriesId")
|
||||||
|
banner.series = series
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannerRepository.save(banner)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.springframework.transaction.annotation.Transactional
|
||||||
|
fun deleteBanner(bannerId: Long) {
|
||||||
|
val banner = bannerRepository.findById(bannerId)
|
||||||
|
.orElseThrow { SodaException("배너를 찾을 수 없습니다: $bannerId") }
|
||||||
|
banner.isActive = false
|
||||||
|
bannerRepository.save(banner)
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.springframework.transaction.annotation.Transactional
|
||||||
|
fun updateBannerOrders(ids: List<Long>): List<SeriesBanner> {
|
||||||
|
val updated = mutableListOf<SeriesBanner>()
|
||||||
|
for (index in ids.indices) {
|
||||||
|
val banner = bannerRepository.findById(ids[index])
|
||||||
|
.orElseThrow { SodaException("배너를 찾을 수 없습니다: ${ids[index]}") }
|
||||||
|
if (!banner.isActive) throw SodaException("비활성화된 배너는 수정할 수 없습니다: ${ids[index]}")
|
||||||
|
banner.sortOrder = index + 1
|
||||||
|
updated.add(bannerRepository.save(banner))
|
||||||
|
}
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
// 시리즈 배너 등록 요청 DTO
|
||||||
|
data class SeriesBannerRegisterRequest(
|
||||||
|
@JsonProperty("seriesId") val seriesId: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
// 시리즈 배너 수정 요청 DTO
|
||||||
|
data class SeriesBannerUpdateRequest(
|
||||||
|
@JsonProperty("bannerId") val bannerId: Long,
|
||||||
|
@JsonProperty("seriesId") val seriesId: Long? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
// 시리즈 배너 응답 DTO
|
||||||
|
data class SeriesBannerResponse(
|
||||||
|
val id: Long,
|
||||||
|
val imagePath: String,
|
||||||
|
val seriesId: Long,
|
||||||
|
val seriesTitle: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(banner: SeriesBanner, imageHost: String): SeriesBannerResponse {
|
||||||
|
return SeriesBannerResponse(
|
||||||
|
id = banner.id!!,
|
||||||
|
imagePath = "$imageHost/${banner.imagePath}",
|
||||||
|
seriesId = banner.series.id!!,
|
||||||
|
seriesTitle = banner.series.title
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 시리즈 배너 목록 페이지 응답 DTO
|
||||||
|
data class SeriesBannerListPageResponse(
|
||||||
|
val totalCount: Long,
|
||||||
|
val content: List<SeriesBannerResponse>
|
||||||
|
)
|
||||||
@@ -20,6 +20,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
|
|||||||
fun getSeriesList(
|
fun getSeriesList(
|
||||||
@RequestParam(required = false) creatorId: Long?,
|
@RequestParam(required = false) creatorId: Long?,
|
||||||
@RequestParam(name = "isOriginal", required = false) isOriginal: Boolean? = null,
|
@RequestParam(name = "isOriginal", required = false) isOriginal: Boolean? = null,
|
||||||
|
@RequestParam(name = "isCompleted", required = false) isCompleted: Boolean? = null,
|
||||||
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
|
@RequestParam("isAdultContentVisible", required = false) isAdultContentVisible: Boolean? = null,
|
||||||
@RequestParam("contentType", required = false) contentType: ContentType? = null,
|
@RequestParam("contentType", required = false) contentType: ContentType? = null,
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
||||||
@@ -31,6 +32,7 @@ class ContentSeriesController(private val service: ContentSeriesService) {
|
|||||||
service.getSeriesList(
|
service.getSeriesList(
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
isOriginal = isOriginal ?: false,
|
isOriginal = isOriginal ?: false,
|
||||||
|
isCompleted = isCompleted ?: false,
|
||||||
isAdultContentVisible = isAdultContentVisible ?: true,
|
isAdultContentVisible = isAdultContentVisible ?: true,
|
||||||
contentType = contentType ?: ContentType.ALL,
|
contentType = contentType ?: ContentType.ALL,
|
||||||
member = member,
|
member = member,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series
|
|||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
import kr.co.vividnext.sodalive.creator.admin.content.series.QSeriesContent.seriesContent
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
import kr.co.vividnext.sodalive.creator.admin.content.series.Series
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesPublishedDaysOfWeek
|
||||||
|
import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState
|
||||||
import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword
|
import kr.co.vividnext.sodalive.creator.admin.content.series.keyword.QSeriesKeyword.seriesKeyword
|
||||||
import kr.co.vividnext.sodalive.member.MemberRole
|
import kr.co.vividnext.sodalive.member.MemberRole
|
||||||
import kr.co.vividnext.sodalive.member.QMember.member
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
@@ -23,13 +24,21 @@ import org.springframework.data.jpa.repository.JpaRepository
|
|||||||
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
|
interface ContentSeriesRepository : JpaRepository<Series, Long>, ContentSeriesQueryRepository
|
||||||
|
|
||||||
interface ContentSeriesQueryRepository {
|
interface ContentSeriesQueryRepository {
|
||||||
fun getSeriesTotalCount(creatorId: Long?, isAuth: Boolean, contentType: ContentType, isOriginal: Boolean): Int
|
fun getSeriesTotalCount(
|
||||||
|
creatorId: Long?,
|
||||||
|
isAuth: Boolean,
|
||||||
|
contentType: ContentType,
|
||||||
|
isOriginal: Boolean,
|
||||||
|
isCompleted: Boolean
|
||||||
|
): Int
|
||||||
|
|
||||||
fun getSeriesList(
|
fun getSeriesList(
|
||||||
imageHost: String,
|
imageHost: String,
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isOriginal: Boolean,
|
isOriginal: Boolean,
|
||||||
|
isCompleted: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long
|
||||||
): List<Series>
|
): List<Series>
|
||||||
@@ -60,7 +69,13 @@ interface ContentSeriesQueryRepository {
|
|||||||
class ContentSeriesQueryRepositoryImpl(
|
class ContentSeriesQueryRepositoryImpl(
|
||||||
private val queryFactory: JPAQueryFactory
|
private val queryFactory: JPAQueryFactory
|
||||||
) : ContentSeriesQueryRepository {
|
) : ContentSeriesQueryRepository {
|
||||||
override fun getSeriesTotalCount(creatorId: Long?, isAuth: Boolean, contentType: ContentType, isOriginal: Boolean): Int {
|
override fun getSeriesTotalCount(
|
||||||
|
creatorId: Long?,
|
||||||
|
isAuth: Boolean,
|
||||||
|
contentType: ContentType,
|
||||||
|
isOriginal: Boolean,
|
||||||
|
isCompleted: Boolean
|
||||||
|
): Int {
|
||||||
var where = series.isActive.isTrue
|
var where = series.isActive.isTrue
|
||||||
|
|
||||||
if (creatorId != null) {
|
if (creatorId != null) {
|
||||||
@@ -71,6 +86,10 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
where = where.and(series.isOriginal.isTrue)
|
where = where.and(series.isOriginal.isTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
|
where = where.and(series.state.eq(SeriesState.COMPLETE))
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
@@ -104,6 +123,7 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
isAuth: Boolean,
|
isAuth: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
isOriginal: Boolean,
|
isOriginal: Boolean,
|
||||||
|
isCompleted: Boolean,
|
||||||
offset: Long,
|
offset: Long,
|
||||||
limit: Long
|
limit: Long
|
||||||
): List<Series> {
|
): List<Series> {
|
||||||
@@ -116,6 +136,10 @@ class ContentSeriesQueryRepositoryImpl(
|
|||||||
where = where.and(series.isOriginal.isTrue)
|
where = where.and(series.isOriginal.isTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCompleted) {
|
||||||
|
where = where.and(series.state.eq(SeriesState.COMPLETE))
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
where = where.and(series.isAdult.isFalse)
|
where = where.and(series.isAdult.isFalse)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ class ContentSeriesService(
|
|||||||
fun getSeriesList(
|
fun getSeriesList(
|
||||||
creatorId: Long?,
|
creatorId: Long?,
|
||||||
isOriginal: Boolean = false,
|
isOriginal: Boolean = false,
|
||||||
|
isCompleted: Boolean = false,
|
||||||
isAdultContentVisible: Boolean,
|
isAdultContentVisible: Boolean,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
member: Member,
|
member: Member,
|
||||||
offset: Long = 0,
|
offset: Long = 0,
|
||||||
limit: Long = 10
|
limit: Long = 20
|
||||||
): GetSeriesListResponse {
|
): GetSeriesListResponse {
|
||||||
val isAuth = member.auth != null && isAdultContentVisible
|
val isAuth = member.auth != null && isAdultContentVisible
|
||||||
|
|
||||||
@@ -63,7 +64,8 @@ class ContentSeriesService(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
isOriginal = isOriginal
|
isOriginal = isOriginal,
|
||||||
|
isCompleted = isCompleted
|
||||||
)
|
)
|
||||||
|
|
||||||
val rawItems = repository.getSeriesList(
|
val rawItems = repository.getSeriesList(
|
||||||
@@ -72,6 +74,7 @@ class ContentSeriesService(
|
|||||||
isAuth = isAuth,
|
isAuth = isAuth,
|
||||||
contentType = contentType,
|
contentType = contentType,
|
||||||
isOriginal = isOriginal,
|
isOriginal = isOriginal,
|
||||||
|
isCompleted = isCompleted,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
limit = limit
|
limit = limit
|
||||||
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
).filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.member!!.id!!) }
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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 javax.persistence.Entity
|
||||||
|
import javax.persistence.FetchType
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.ManyToOne
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시리즈 배너 엔티티
|
||||||
|
* 이미지와 시리즈 ID를 가지며, 소프트 삭제(isActive = false)를 지원합니다.
|
||||||
|
* 정렬 순서(sortOrder)를 통해 배너의 표시 순서를 결정합니다.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
class SeriesBanner(
|
||||||
|
// 배너 이미지 경로
|
||||||
|
var imagePath: String? = null,
|
||||||
|
|
||||||
|
// 연관된 캐릭터
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "series_id")
|
||||||
|
var series: Series,
|
||||||
|
|
||||||
|
// 정렬 순서 (낮을수록 먼저 표시)
|
||||||
|
var sortOrder: Int = 0,
|
||||||
|
|
||||||
|
// 활성화 여부 (소프트 삭제용)
|
||||||
|
var isActive: Boolean = true
|
||||||
|
) : BaseEntity()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package kr.co.vividnext.sodalive.content.series.main.banner
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.data.jpa.repository.Query
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface SeriesBannerRepository : JpaRepository<SeriesBanner, Long> {
|
||||||
|
fun findByIsActiveTrueOrderBySortOrderAsc(pageable: Pageable): Page<SeriesBanner>
|
||||||
|
|
||||||
|
@Query("SELECT MAX(b.sortOrder) FROM SeriesBanner b WHERE b.isActive = true")
|
||||||
|
fun findMaxSortOrder(): Int?
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user