관리자 - 콘텐츠 리스트, 콘텐츠 배너관리, 콘텐츠 큐레이션 관리 API
This commit is contained in:
parent
7696f06fbd
commit
14b25bdfc3
|
@ -0,0 +1,30 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
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.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@RequestMapping("/admin/audio-content")
|
||||||
|
class AdminContentController(private val service: AdminContentService) {
|
||||||
|
@GetMapping("/list")
|
||||||
|
fun getAudioContentList(pageable: Pageable) = ApiResponse.ok(service.getAudioContentList(pageable))
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
fun searchAudioContent(
|
||||||
|
@RequestParam(value = "search_word") searchWord: String,
|
||||||
|
pageable: Pageable
|
||||||
|
) = ApiResponse.ok(service.searchAudioContent(searchWord, pageable))
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
fun modifyAudioContent(
|
||||||
|
@RequestBody request: UpdateAdminContentRequest
|
||||||
|
) = ApiResponse.ok(service.updateAudioContent(request))
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content
|
||||||
|
|
||||||
|
import com.querydsl.core.types.dsl.DateTimePath
|
||||||
|
import com.querydsl.core.types.dsl.Expressions
|
||||||
|
import com.querydsl.core.types.dsl.StringTemplate
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.QAudioContent.audioContent
|
||||||
|
import kr.co.vividnext.sodalive.content.hashtag.QAudioContentHashTag.audioContentHashTag
|
||||||
|
import kr.co.vividnext.sodalive.content.hashtag.QHashTag.hashTag
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
||||||
|
import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface AdminContentRepository : JpaRepository<AudioContent, Long>, AdminAudioContentQueryRepository
|
||||||
|
|
||||||
|
interface AdminAudioContentQueryRepository {
|
||||||
|
fun getAudioContentTotalCount(searchWord: String = ""): Int
|
||||||
|
fun getAudioContentList(offset: Long, limit: Long, searchWord: String = ""): List<GetAdminContentListItem>
|
||||||
|
fun getHashTagList(audioContentId: Long): List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdminAudioContentQueryRepositoryImpl(
|
||||||
|
private val queryFactory: JPAQueryFactory
|
||||||
|
) : AdminAudioContentQueryRepository {
|
||||||
|
override fun getAudioContentTotalCount(searchWord: String): Int {
|
||||||
|
var where = audioContent.duration.isNotNull
|
||||||
|
.and(audioContent.member.isNotNull)
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
|
if (searchWord.trim().length > 1) {
|
||||||
|
where = where.and(
|
||||||
|
audioContent.title.contains(searchWord)
|
||||||
|
.or(audioContent.member.nickname.contains(searchWord))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(audioContent.id)
|
||||||
|
.from(audioContent)
|
||||||
|
.where(where)
|
||||||
|
.fetch()
|
||||||
|
.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAudioContentList(offset: Long, limit: Long, searchWord: String): List<GetAdminContentListItem> {
|
||||||
|
var where = audioContent.duration.isNotNull
|
||||||
|
.and(audioContent.member.isNotNull)
|
||||||
|
.and(audioContent.isActive.isTrue)
|
||||||
|
|
||||||
|
if (searchWord.trim().length > 1) {
|
||||||
|
where = where.and(
|
||||||
|
audioContent.title.contains(searchWord)
|
||||||
|
.or(audioContent.member.nickname.contains(searchWord))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminContentListItem(
|
||||||
|
audioContent.id,
|
||||||
|
audioContent.title,
|
||||||
|
audioContent.detail,
|
||||||
|
audioContentCuration.title,
|
||||||
|
audioContentCuration.id.nullif(0),
|
||||||
|
audioContent.coverImage,
|
||||||
|
audioContent.member!!.nickname,
|
||||||
|
audioContentTheme.theme,
|
||||||
|
audioContent.price,
|
||||||
|
audioContent.isAdult,
|
||||||
|
audioContent.duration,
|
||||||
|
audioContent.content,
|
||||||
|
formattedDateExpression(audioContent.createdAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(audioContent)
|
||||||
|
.leftJoin(audioContent.curation, audioContentCuration)
|
||||||
|
.innerJoin(audioContent.theme, audioContentTheme)
|
||||||
|
.where(where)
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
.orderBy(audioContent.id.desc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHashTagList(audioContentId: Long): List<String> {
|
||||||
|
return queryFactory
|
||||||
|
.select(hashTag.tag)
|
||||||
|
.from(audioContentHashTag)
|
||||||
|
.innerJoin(audioContentHashTag.hashTag, hashTag)
|
||||||
|
.innerJoin(audioContentHashTag.audioContent, audioContent)
|
||||||
|
.where(
|
||||||
|
audioContent.duration.isNotNull
|
||||||
|
.and(audioContent.member.isNotNull)
|
||||||
|
.and(audioContentHashTag.audioContent.id.eq(audioContentId))
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formattedDateExpression(
|
||||||
|
dateTime: DateTimePath<LocalDateTime>,
|
||||||
|
format: String = "%Y-%m-%d"
|
||||||
|
): StringTemplate {
|
||||||
|
return Expressions.stringTemplate(
|
||||||
|
"DATE_FORMAT({0}, {1})",
|
||||||
|
Expressions.dateTimeTemplate(
|
||||||
|
LocalDateTime::class.java,
|
||||||
|
"CONVERT_TZ({0},{1},{2})",
|
||||||
|
dateTime,
|
||||||
|
"UTC",
|
||||||
|
"Asia/Seoul"
|
||||||
|
),
|
||||||
|
format
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.admin.content.curation.AdminContentCurationRepository
|
||||||
|
import kr.co.vividnext.sodalive.aws.cloudfront.AudioContentCloudFront
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminContentService(
|
||||||
|
private val repository: AdminContentRepository,
|
||||||
|
private val audioContentCloudFront: AudioContentCloudFront,
|
||||||
|
private val curationRepository: AdminContentCurationRepository,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val coverImageHost: String
|
||||||
|
) {
|
||||||
|
fun getAudioContentList(pageable: Pageable): GetAdminContentListResponse {
|
||||||
|
val totalCount = repository.getAudioContentTotalCount()
|
||||||
|
val audioContentAndThemeList = repository.getAudioContentList(
|
||||||
|
offset = pageable.offset,
|
||||||
|
limit = pageable.pageSize.toLong()
|
||||||
|
)
|
||||||
|
|
||||||
|
val audioContentList = audioContentAndThemeList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
val tags = repository
|
||||||
|
.getHashTagList(audioContentId = it.audioContentId)
|
||||||
|
.joinToString(" ") { tag -> tag }
|
||||||
|
it.tags = tags
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
it.contentUrl = audioContentCloudFront.generateSignedURL(
|
||||||
|
resourcePath = it.contentUrl,
|
||||||
|
expirationTime = 1000 * 60 * 60 * (it.remainingTime.split(":")[0].toLong() + 2)
|
||||||
|
)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}"
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
return GetAdminContentListResponse(totalCount, audioContentList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchAudioContent(searchWord: String, pageable: Pageable): GetAdminContentListResponse {
|
||||||
|
if (searchWord.length < 2) throw SodaException("2글자 이상 입력하세요.")
|
||||||
|
val totalCount = repository.getAudioContentTotalCount(searchWord)
|
||||||
|
val audioContentAndThemeList = repository.getAudioContentList(
|
||||||
|
offset = pageable.offset,
|
||||||
|
limit = pageable.pageSize.toLong(),
|
||||||
|
searchWord = searchWord
|
||||||
|
)
|
||||||
|
|
||||||
|
val audioContentList = audioContentAndThemeList
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
val tags = repository
|
||||||
|
.getHashTagList(audioContentId = it.audioContentId)
|
||||||
|
.joinToString(" ") { tag -> tag }
|
||||||
|
it.tags = tags
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
it.contentUrl = audioContentCloudFront.generateSignedURL(
|
||||||
|
resourcePath = it.contentUrl,
|
||||||
|
expirationTime = 1000 * 60 * 60 * (it.remainingTime.split(":")[0].toLong() + 2)
|
||||||
|
)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
it.coverImageUrl = "$coverImageHost/${it.coverImageUrl}"
|
||||||
|
it
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
return GetAdminContentListResponse(totalCount, audioContentList)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateAudioContent(request: UpdateAdminContentRequest) {
|
||||||
|
val audioContent = repository.findByIdOrNull(id = request.id)
|
||||||
|
?: throw SodaException("없는 콘텐츠 입니다.")
|
||||||
|
|
||||||
|
if (request.isDefaultCoverImage) {
|
||||||
|
audioContent.coverImage = "profile/default_profile.png"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isActive != null) {
|
||||||
|
audioContent.isActive = request.isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isAdult != null) {
|
||||||
|
audioContent.isAdult = request.isAdult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isCommentAvailable != null) {
|
||||||
|
audioContent.isCommentAvailable = request.isCommentAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.title != null) {
|
||||||
|
audioContent.title = request.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.detail != null) {
|
||||||
|
audioContent.detail = request.detail
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.curationId != null) {
|
||||||
|
val curation = curationRepository.findByIdAndActive(id = request.curationId)
|
||||||
|
audioContent.curation = curation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class GetAdminContentListResponse(
|
||||||
|
val totalCount: Int,
|
||||||
|
val items: List<GetAdminContentListItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GetAdminContentListItem @QueryProjection constructor(
|
||||||
|
val audioContentId: Long,
|
||||||
|
val title: String,
|
||||||
|
val detail: String,
|
||||||
|
val curationTitle: String?,
|
||||||
|
val curationId: Long,
|
||||||
|
var coverImageUrl: String,
|
||||||
|
val creatorNickname: String,
|
||||||
|
val theme: String,
|
||||||
|
val price: Int,
|
||||||
|
val isAdult: Boolean,
|
||||||
|
val remainingTime: String,
|
||||||
|
var contentUrl: String,
|
||||||
|
val date: String
|
||||||
|
) {
|
||||||
|
var tags: String = ""
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content
|
||||||
|
|
||||||
|
data class UpdateAdminContentRequest(
|
||||||
|
val id: Long,
|
||||||
|
val isDefaultCoverImage: Boolean,
|
||||||
|
val title: String?,
|
||||||
|
val detail: String?,
|
||||||
|
val curationId: Long?,
|
||||||
|
val isAdult: Boolean?,
|
||||||
|
val isActive: Boolean?,
|
||||||
|
val isCommentAvailable: Boolean?
|
||||||
|
)
|
|
@ -0,0 +1,37 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
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.RequestPart
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/admin/audio-content/banner")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
class AdminContentBannerController(private val service: AdminContentBannerService) {
|
||||||
|
@PostMapping
|
||||||
|
fun createAudioContentMainBanner(
|
||||||
|
@RequestPart("image") image: MultipartFile,
|
||||||
|
@RequestPart("request") requestString: String
|
||||||
|
) = ApiResponse.ok(service.createAudioContentMainBanner(image, requestString))
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
fun modifyAudioContentMainBanner(
|
||||||
|
@RequestPart("image", required = false) image: MultipartFile? = null,
|
||||||
|
@RequestPart("request") requestString: String
|
||||||
|
) = ApiResponse.ok(service.updateAudioContentMainBanner(image, requestString))
|
||||||
|
|
||||||
|
@PutMapping("/orders")
|
||||||
|
fun updateBannerOrders(
|
||||||
|
@RequestBody request: UpdateBannerOrdersRequest
|
||||||
|
) = ApiResponse.ok(service.updateBannerOrders(request.ids), "수정되었습니다.")
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun getAudioContentMainBannerList() = ApiResponse.ok(service.getAudioContentMainBannerList())
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.event.QEvent.event
|
||||||
|
import kr.co.vividnext.sodalive.member.QMember.member
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface AdminContentBannerRepository : JpaRepository<AudioContentBanner, Long>, AdminContentBannerQueryRepository
|
||||||
|
|
||||||
|
interface AdminContentBannerQueryRepository {
|
||||||
|
fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdminContentBannerQueryRepositoryImpl(
|
||||||
|
private val queryFactory: JPAQueryFactory,
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
|
) : AdminContentBannerQueryRepository {
|
||||||
|
override fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminContentBannerResponse(
|
||||||
|
audioContentBanner.id,
|
||||||
|
audioContentBanner.type,
|
||||||
|
audioContentBanner.thumbnailImage.prepend("/").prepend(cloudFrontHost),
|
||||||
|
audioContentBanner.event.id,
|
||||||
|
audioContentBanner.event.thumbnailImage,
|
||||||
|
audioContentBanner.creator.id,
|
||||||
|
audioContentBanner.creator.nickname,
|
||||||
|
audioContentBanner.link,
|
||||||
|
audioContentBanner.isAdult
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(audioContentBanner)
|
||||||
|
.leftJoin(audioContentBanner.event, event)
|
||||||
|
.leftJoin(audioContentBanner.creator, member)
|
||||||
|
.where(audioContentBanner.isActive.isTrue)
|
||||||
|
.orderBy(audioContentBanner.orders.asc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
import kr.co.vividnext.sodalive.event.EventRepository
|
||||||
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import kr.co.vividnext.sodalive.utils.generateFileName
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import org.springframework.web.multipart.MultipartFile
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminContentBannerService(
|
||||||
|
private val s3Uploader: S3Uploader,
|
||||||
|
private val repository: AdminContentBannerRepository,
|
||||||
|
private val memberRepository: MemberRepository,
|
||||||
|
private val eventRepository: EventRepository,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.s3.bucket}")
|
||||||
|
private val bucket: String
|
||||||
|
) {
|
||||||
|
@Transactional
|
||||||
|
fun createAudioContentMainBanner(image: MultipartFile, requestString: String) {
|
||||||
|
val request = objectMapper.readValue(requestString, CreateContentBannerRequest::class.java)
|
||||||
|
if (request.type == AudioContentBannerType.CREATOR && request.creatorId == null) {
|
||||||
|
throw SodaException("크리에이터를 선택하세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type == AudioContentBannerType.LINK && request.link == null) {
|
||||||
|
throw SodaException("링크 url을 입력하세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type == AudioContentBannerType.EVENT && request.eventId == null) {
|
||||||
|
throw SodaException("이벤트를 선택하세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val event = if (request.eventId != null && request.eventId > 0) {
|
||||||
|
eventRepository.findByIdOrNull(request.eventId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val creator = if (request.creatorId != null && request.creatorId > 0) {
|
||||||
|
memberRepository.findByIdOrNull(request.creatorId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val audioContentBanner = AudioContentBanner(type = request.type)
|
||||||
|
audioContentBanner.link = request.link
|
||||||
|
audioContentBanner.isAdult = request.isAdult
|
||||||
|
audioContentBanner.event = event
|
||||||
|
audioContentBanner.creator = creator
|
||||||
|
repository.save(audioContentBanner)
|
||||||
|
|
||||||
|
val fileName = generateFileName()
|
||||||
|
val imagePath = s3Uploader.upload(
|
||||||
|
inputStream = image.inputStream,
|
||||||
|
bucket = bucket,
|
||||||
|
filePath = "audio_content_banner/${audioContentBanner.id}/$fileName"
|
||||||
|
)
|
||||||
|
audioContentBanner.thumbnailImage = imagePath
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateAudioContentMainBanner(image: MultipartFile?, requestString: String) {
|
||||||
|
val request = objectMapper.readValue(requestString, UpdateContentBannerRequest::class.java)
|
||||||
|
val audioContentBanner = repository.findByIdOrNull(request.id)
|
||||||
|
?: throw SodaException("잘못된 요청입니다.")
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
val fileName = generateFileName()
|
||||||
|
val imagePath = s3Uploader.upload(
|
||||||
|
inputStream = image.inputStream,
|
||||||
|
bucket = bucket,
|
||||||
|
filePath = "audio_content_banner/${audioContentBanner.id}/$fileName"
|
||||||
|
)
|
||||||
|
audioContentBanner.thumbnailImage = imagePath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isAdult != null) {
|
||||||
|
audioContentBanner.isAdult = request.isAdult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isActive != null) {
|
||||||
|
audioContentBanner.isActive = request.isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.type != null) {
|
||||||
|
audioContentBanner.creator = null
|
||||||
|
audioContentBanner.event = null
|
||||||
|
audioContentBanner.link = null
|
||||||
|
|
||||||
|
if (request.type == AudioContentBannerType.CREATOR) {
|
||||||
|
if (request.creatorId != null) {
|
||||||
|
val creator = memberRepository.findByIdOrNull(request.creatorId)
|
||||||
|
?: throw SodaException("크리에이터를 선택하세요.")
|
||||||
|
|
||||||
|
audioContentBanner.creator = creator
|
||||||
|
} else {
|
||||||
|
throw SodaException("크리에이터를 선택하세요.")
|
||||||
|
}
|
||||||
|
} else if (request.type == AudioContentBannerType.LINK) {
|
||||||
|
if (request.link != null) {
|
||||||
|
audioContentBanner.link = request.link
|
||||||
|
} else {
|
||||||
|
throw SodaException("링크 url을 입력하세요.")
|
||||||
|
}
|
||||||
|
} else if (request.type == AudioContentBannerType.EVENT) {
|
||||||
|
if (request.eventId != null) {
|
||||||
|
val event = eventRepository.findByIdOrNull(request.eventId)
|
||||||
|
?: throw SodaException("이벤트를 선택하세요.")
|
||||||
|
|
||||||
|
audioContentBanner.event = event
|
||||||
|
} else {
|
||||||
|
throw SodaException("이벤트를 선택하세요.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audioContentBanner.type = request.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateBannerOrders(ids: List<Long>) {
|
||||||
|
for (index in ids.indices) {
|
||||||
|
val tag = repository.findByIdOrNull(ids[index])
|
||||||
|
|
||||||
|
if (tag != null) {
|
||||||
|
tag.orders = index + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAudioContentMainBannerList(): List<GetAdminContentBannerResponse> {
|
||||||
|
return repository.getAudioContentMainBannerList()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
|
||||||
|
data class CreateContentBannerRequest(
|
||||||
|
val type: AudioContentBannerType,
|
||||||
|
val eventId: Long?,
|
||||||
|
val creatorId: Long?,
|
||||||
|
val link: String?,
|
||||||
|
val isAdult: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,16 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
|
||||||
|
data class GetAdminContentBannerResponse @QueryProjection constructor(
|
||||||
|
val id: Long,
|
||||||
|
val type: AudioContentBannerType,
|
||||||
|
val thumbnailImageUrl: String,
|
||||||
|
val eventId: Long?,
|
||||||
|
val eventThumbnailImage: String?,
|
||||||
|
val creatorId: Long?,
|
||||||
|
val creatorNickname: String?,
|
||||||
|
val link: String?,
|
||||||
|
val isAdult: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,5 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
data class UpdateBannerOrdersRequest(
|
||||||
|
val ids: List<Long>
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerType
|
||||||
|
|
||||||
|
data class UpdateContentBannerRequest(
|
||||||
|
val id: Long,
|
||||||
|
val type: AudioContentBannerType?,
|
||||||
|
val eventId: Long?,
|
||||||
|
val creatorId: Long?,
|
||||||
|
val link: String?,
|
||||||
|
val isAdult: Boolean?,
|
||||||
|
val isActive: Boolean?
|
||||||
|
)
|
|
@ -0,0 +1,33 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.curation
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
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.RestController
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/admin/audio-content/curation")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
class AdminContentCurationController(private val service: AdminContentCurationService) {
|
||||||
|
@PostMapping
|
||||||
|
fun createContentCuration(
|
||||||
|
@RequestBody request: CreateContentCurationRequest
|
||||||
|
) = ApiResponse.ok(service.createContentCuration(request))
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
fun updateContentCuration(
|
||||||
|
@RequestBody request: UpdateContentCurationRequest
|
||||||
|
) = ApiResponse.ok(service.updateContentCuration(request))
|
||||||
|
|
||||||
|
@PutMapping("/orders")
|
||||||
|
fun updateContentCurationOrders(
|
||||||
|
@RequestBody request: UpdateContentCurationOrdersRequest
|
||||||
|
) = ApiResponse.ok(service.updateContentCurationOrders(request.ids), "수정되었습니다.")
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun getContentCurationList() = ApiResponse.ok(service.getContentCurationList())
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.curation
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface AdminContentCurationRepository :
|
||||||
|
JpaRepository<AudioContentCuration, Long>,
|
||||||
|
AdminContentCurationQueryRepository
|
||||||
|
|
||||||
|
interface AdminContentCurationQueryRepository {
|
||||||
|
fun getAudioContentCurationList(): List<GetAdminContentCurationResponse>
|
||||||
|
fun findByIdAndActive(id: Long): AudioContentCuration?
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class AdminContentCurationQueryRepositoryImpl(
|
||||||
|
private val queryFactory: JPAQueryFactory
|
||||||
|
) : AdminContentCurationQueryRepository {
|
||||||
|
override fun getAudioContentCurationList(): List<GetAdminContentCurationResponse> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminContentCurationResponse(
|
||||||
|
audioContentCuration.id,
|
||||||
|
audioContentCuration.title,
|
||||||
|
audioContentCuration.description,
|
||||||
|
audioContentCuration.isAdult
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(audioContentCuration)
|
||||||
|
.where(audioContentCuration.isActive.isTrue)
|
||||||
|
.orderBy(audioContentCuration.orders.asc())
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByIdAndActive(id: Long): AudioContentCuration? {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(audioContentCuration)
|
||||||
|
.where(
|
||||||
|
audioContentCuration.id.eq(id)
|
||||||
|
.and(audioContentCuration.isActive.isTrue)
|
||||||
|
)
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.curation
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
|
import kr.co.vividnext.sodalive.content.main.curation.AudioContentCuration
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AdminContentCurationService(
|
||||||
|
private val repository: AdminContentCurationRepository
|
||||||
|
) {
|
||||||
|
@Transactional
|
||||||
|
fun createContentCuration(request: CreateContentCurationRequest) {
|
||||||
|
repository.save(
|
||||||
|
AudioContentCuration(
|
||||||
|
title = request.title,
|
||||||
|
description = request.description,
|
||||||
|
isAdult = request.isAdult
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateContentCuration(request: UpdateContentCurationRequest) {
|
||||||
|
val audioContentCuration = repository.findByIdOrNull(id = request.id)
|
||||||
|
?: throw SodaException("잘못된 요청입니다.")
|
||||||
|
|
||||||
|
if (request.title != null) {
|
||||||
|
audioContentCuration.title = request.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.description != null) {
|
||||||
|
audioContentCuration.description = request.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isAdult != null) {
|
||||||
|
audioContentCuration.isAdult = request.isAdult
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isActive != null) {
|
||||||
|
audioContentCuration.isActive = request.isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun updateContentCurationOrders(ids: List<Long>) {
|
||||||
|
for (index in ids.indices) {
|
||||||
|
val audioContentCuration = repository.findByIdOrNull(ids[index])
|
||||||
|
|
||||||
|
if (audioContentCuration != null) {
|
||||||
|
audioContentCuration.orders = index + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getContentCurationList(): List<GetAdminContentCurationResponse> {
|
||||||
|
return repository.getAudioContentCurationList()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.curation
|
||||||
|
|
||||||
|
data class CreateContentCurationRequest(
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val isAdult: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UpdateContentCurationRequest(
|
||||||
|
val id: Long,
|
||||||
|
val title: String?,
|
||||||
|
val description: String?,
|
||||||
|
val isAdult: Boolean?,
|
||||||
|
val isActive: Boolean?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class UpdateContentCurationOrdersRequest(
|
||||||
|
val ids: List<Long>
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.content.curation
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class GetAdminContentCurationResponse @QueryProjection constructor(
|
||||||
|
val id: Long,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val isAdult: Boolean
|
||||||
|
)
|
|
@ -21,6 +21,9 @@ class AdminMemberController(private val service: AdminMemberService) {
|
||||||
pageable: Pageable
|
pageable: Pageable
|
||||||
) = ApiResponse.ok(service.searchMember(searchWord, pageable))
|
) = ApiResponse.ok(service.searchMember(searchWord, pageable))
|
||||||
|
|
||||||
|
@GetMapping("/creator/all/list")
|
||||||
|
fun getCreatorAllList() = ApiResponse.ok(service.getCreatorAllList())
|
||||||
|
|
||||||
@GetMapping("/creator/list")
|
@GetMapping("/creator/list")
|
||||||
fun getCreatorList(pageable: Pageable) = ApiResponse.ok(service.getCreatorList(pageable))
|
fun getCreatorList(pageable: Pageable) = ApiResponse.ok(service.getCreatorList(pageable))
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ interface AdminMemberQueryRepository {
|
||||||
fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole? = null): List<Member>
|
fun searchMember(searchWord: String, offset: Long, limit: Long, role: MemberRole? = null): List<Member>
|
||||||
|
|
||||||
fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int
|
fun searchMemberTotalCount(searchWord: String, role: MemberRole? = null): Int
|
||||||
|
fun getCreatorAllList(): List<GetAdminCreatorAllListResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository {
|
class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : AdminMemberQueryRepository {
|
||||||
|
@ -92,4 +93,20 @@ class AdminMemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory)
|
||||||
.fetch()
|
.fetch()
|
||||||
.size
|
.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCreatorAllList(): List<GetAdminCreatorAllListResponse> {
|
||||||
|
return queryFactory
|
||||||
|
.select(
|
||||||
|
QGetAdminCreatorAllListResponse(
|
||||||
|
member.id,
|
||||||
|
member.nickname
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.from(member)
|
||||||
|
.where(
|
||||||
|
member.role.eq(MemberRole.CREATOR)
|
||||||
|
.and(member.isActive.isTrue)
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,4 +117,8 @@ class AdminMemberService(
|
||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCreatorAllList(): List<GetAdminCreatorAllListResponse> {
|
||||||
|
return repository.getCreatorAllList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package kr.co.vividnext.sodalive.admin.member
|
||||||
|
|
||||||
|
import com.querydsl.core.annotations.QueryProjection
|
||||||
|
|
||||||
|
data class GetAdminCreatorAllListResponse @QueryProjection constructor(
|
||||||
|
val id: Long,
|
||||||
|
val nickname: String
|
||||||
|
)
|
Loading…
Reference in New Issue