관리자 - 추천 라이브 크리에이터 API

This commit is contained in:
Klaus 2023-08-07 03:09:44 +09:00
parent 14b25bdfc3
commit 34590347a6
8 changed files with 310 additions and 4 deletions

View File

@ -1,15 +1,58 @@
package kr.co.vividnext.sodalive.admin.live
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.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.RestController
import org.springframework.web.multipart.MultipartFile
@RestController
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping("/admin/live")
class AdminLiveController(private val service: AdminLiveService) {
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
fun getOnAirLive() = ApiResponse.ok(data = service.getLiveList())
@GetMapping("/recommend-creator")
@PreAuthorize("hasRole('ADMIN')")
fun getRecommendCreatorBanner(pageable: Pageable) = ApiResponse.ok(service.getRecommendCreator(pageable))
@PostMapping("/recommend-creator")
fun createRecommendCreatorBanner(
@RequestParam("image") image: MultipartFile,
@RequestParam("creator_id") creatorId: Long,
@RequestParam("start_date") startDate: String,
@RequestParam("end_date") endDate: String,
@RequestParam("is_adult") isAdult: Boolean
) = ApiResponse.ok(
service.createRecommendCreatorBanner(image, creatorId, startDate, endDate, isAdult),
"등록되었습니다."
)
@PutMapping("/recommend-creator")
fun updateRecommendCreatorBanner(
@RequestParam("recommend_creator_banner_id") recommendCreatorBannerId: Long,
@RequestParam("image", required = false) image: MultipartFile?,
@RequestParam("creator_id", required = false) creatorId: Long?,
@RequestParam("start_date", required = false) startDate: String?,
@RequestParam("end_date", required = false) endDate: String?,
@RequestParam("is_adult", required = false) isAdult: Boolean?
) = ApiResponse.ok(
service.updateRecommendCreatorBanner(recommendCreatorBannerId, image, creatorId, startDate, endDate, isAdult),
"수정되었습니다."
)
@PutMapping("/recommend-creator/orders")
fun updateRecommendCreatorBannerOrders(
@RequestBody request: UpdateAdminRecommendCreatorBannerOrdersRequest
) = ApiResponse.ok(
service.updateRecommendCreatorBannerOrders(request.firstOrders, request.ids),
"수정되었습니다."
)
}

View File

@ -1,9 +1,12 @@
package kr.co.vividnext.sodalive.admin.live
import com.querydsl.jpa.impl.JPAQueryFactory
import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBanner
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
import kr.co.vividnext.sodalive.member.QMember.member
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Repository
@Repository
@ -16,4 +19,21 @@ class AdminLiveRoomQueryRepository(private val queryFactory: JPAQueryFactory) {
.orderBy(liveRoom.channelName.desc(), liveRoom.beginDateTime.asc())
.fetch()
}
fun getRecommendCreatorTotalCount(): Int {
return queryFactory
.select(recommendLiveCreatorBanner.id)
.from(recommendLiveCreatorBanner)
.fetch()
.size
}
fun getRecommendCreatorList(pageable: Pageable): List<RecommendLiveCreatorBanner> {
return queryFactory
.selectFrom(recommendLiveCreatorBanner)
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.orderBy(recommendLiveCreatorBanner.orders.asc(), recommendLiveCreatorBanner.id.desc())
.fetch()
}
}

View File

@ -1,12 +1,31 @@
package kr.co.vividnext.sodalive.admin.live
import com.amazonaws.services.s3.model.ObjectMetadata
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBanner
import kr.co.vividnext.sodalive.live.recommend.RecommendLiveCreatorBannerRepository
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.domain.Pageable
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.multipart.MultipartFile
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Service
class AdminLiveService(
private val recommendCreatorBannerRepository: RecommendLiveCreatorBannerRepository,
private val repository: AdminLiveRoomQueryRepository,
private val memberRepository: MemberRepository,
private val s3Uploader: S3Uploader,
@Value("\${cloud.aws.s3.bucket}")
private val bucket: String,
@Value("\${cloud.aws.cloud-front.host}")
private val coverImageHost: String
) {
@ -34,4 +53,188 @@ class AdminLiveService(
.toList()
)
}
fun getRecommendCreator(pageable: Pageable): GetAdminRecommendCreatorResponse {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val totalCount = repository.getRecommendCreatorTotalCount()
val recommendCreatorList = repository
.getRecommendCreatorList(pageable)
.asSequence()
.map {
GetAdminRecommendCreatorResponseItem(
it.id!!,
"$coverImageHost/${it.image}",
it.creator!!.id!!,
it.creator!!.nickname,
it.startDate
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
.toLocalDateTime()
.format(dateTimeFormatter),
it.endDate
.atZone(ZoneId.of("UTC"))
.withZoneSameInstant(ZoneId.of("Asia/Seoul"))
.toLocalDateTime()
.format(dateTimeFormatter),
it.isAdult
)
}
.toList()
return GetAdminRecommendCreatorResponse(
totalCount = totalCount,
recommendCreatorList = recommendCreatorList
)
}
@Transactional
fun createRecommendCreatorBanner(
image: MultipartFile,
creatorId: Long,
startDateString: String,
endDateString: String,
isAdult: Boolean
): Long {
if (creatorId < 1) throw SodaException("올바른 크리에이터를 선택해 주세요.")
val creator = memberRepository.findCreatorByIdOrNull(memberId = creatorId)
?: throw SodaException("올바른 크리에이터를 선택해 주세요.")
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val startDate = LocalDateTime.parse(startDateString, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
val nowDate = LocalDateTime.now()
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
if (startDate < nowDate) throw SodaException("노출 시작일은 현재시간 이후로 설정하셔야 합니다.")
val endDate = LocalDateTime.parse(endDateString, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
if (endDate < nowDate) throw SodaException("노출 종료일은 현재시간 이후로 설정하셔야 합니다.")
if (endDate <= startDate) throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
val recommendCreatorBanner = RecommendLiveCreatorBanner(
startDate = startDate,
endDate = endDate,
isAdult = isAdult
)
recommendCreatorBanner.creator = creator
recommendCreatorBannerRepository.save(recommendCreatorBanner)
val metadata = ObjectMetadata()
metadata.contentLength = image.size
val imagePath = s3Uploader.upload(
inputStream = image.inputStream,
bucket = bucket,
filePath = "recommend_creator_banner/${recommendCreatorBanner.id}/${generateFileName()}",
metadata = metadata
)
recommendCreatorBanner.image = imagePath
return recommendCreatorBanner.id!!
}
@Transactional
fun updateRecommendCreatorBanner(
recommendCreatorBannerId: Long,
image: MultipartFile?,
creatorId: Long?,
startDateString: String?,
endDateString: String?,
isAdult: Boolean?
) {
val recommendCreatorBanner = recommendCreatorBannerRepository.findByIdOrNull(recommendCreatorBannerId)
?: throw SodaException("해당하는 추천라이브가 없습니다. 다시 확인해 주세요.")
if (creatorId != null) {
if (creatorId < 1) throw SodaException("올바른 크리에이터를 선택해 주세요.")
val creator = memberRepository.findCreatorByIdOrNull(memberId = creatorId)
?: throw SodaException("올바른 크리에이터를 선택해 주세요.")
recommendCreatorBanner.creator = creator
}
if (image != null) {
val metadata = ObjectMetadata()
metadata.contentLength = image.size
val imagePath = s3Uploader.upload(
inputStream = image.inputStream,
bucket = bucket,
filePath = "recommend_creator_banner/${recommendCreatorBanner.id}/${generateFileName()}",
metadata = metadata
)
recommendCreatorBanner.image = imagePath
}
if (startDateString != null) {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val startDate = LocalDateTime.parse(startDateString, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
val endDate = if (endDateString != null) {
LocalDateTime.parse(endDateString, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
} else {
null
}
if (endDate != null) {
if (endDate <= startDate) {
throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
}
recommendCreatorBanner.endDate = endDate
} else {
if (recommendCreatorBanner.endDate <= startDate) {
throw SodaException("노출 시작일은 노출 종료일 이전으로 설정하셔야 합니다.")
}
}
recommendCreatorBanner.startDate = startDate
} else if (endDateString != null) {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val endDate = LocalDateTime.parse(endDateString, dateTimeFormatter)
.atZone(ZoneId.of("Asia/Seoul"))
.withZoneSameInstant(ZoneId.of("UTC"))
.toLocalDateTime()
if (endDate <= recommendCreatorBanner.startDate) {
throw SodaException("노출 종료일은 노출 시작일 이후로 설정하셔야 합니다.")
}
recommendCreatorBanner.endDate = endDate
}
if (isAdult != null) {
recommendCreatorBanner.isAdult = isAdult
}
}
@Transactional
fun updateRecommendCreatorBannerOrders(firstOrders: Int, ids: List<Long>) {
for (index in ids.indices) {
val recommendCreatorBanner = recommendCreatorBannerRepository.findByIdOrNull(id = ids[index])
if (recommendCreatorBanner != null) {
recommendCreatorBanner.orders = firstOrders + index
}
}
}
}

View File

@ -0,0 +1,16 @@
package kr.co.vividnext.sodalive.admin.live
data class GetAdminRecommendCreatorResponse(
val totalCount: Int,
val recommendCreatorList: List<GetAdminRecommendCreatorResponseItem>
)
data class GetAdminRecommendCreatorResponseItem(
val id: Long,
val image: String,
val creatorId: Long,
val creatorNickname: String,
val startDate: String,
val endDate: String,
val isAdult: Boolean
)

View File

@ -0,0 +1,6 @@
package kr.co.vividnext.sodalive.admin.live
data class UpdateAdminRecommendCreatorBannerOrdersRequest(
val firstOrders: Int,
val ids: List<Long>
)

View File

@ -11,8 +11,6 @@ import javax.persistence.ManyToOne
@Entity
data class RecommendLiveCreatorBanner(
@Column(nullable = false)
var image: String,
@Column(nullable = false)
var startDate: LocalDateTime,
@Column(nullable = false)
@ -20,7 +18,9 @@ data class RecommendLiveCreatorBanner(
@Column(nullable = false)
var isAdult: Boolean = false,
@Column(nullable = false)
var orders: Int = 1
var orders: Int = 1,
@Column(nullable = true)
var image: String? = null
) : BaseEntity() {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "creator_id", nullable = false)

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.live.recommend
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface RecommendLiveCreatorBannerRepository : JpaRepository<RecommendLiveCreatorBanner, Long>

View File

@ -14,6 +14,7 @@ interface MemberRepository : JpaRepository<Member, Long>, MemberQueryRepository
interface MemberQueryRepository {
fun findByPushToken(pushToken: String): List<Member>
fun findByNicknameAndOtherCondition(nickname: String, memberId: Long): List<Member>
fun findCreatorByIdOrNull(memberId: Long): Member?
}
@Repository
@ -36,4 +37,14 @@ class MemberQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Mem
)
.fetch()
}
override fun findCreatorByIdOrNull(memberId: Long): Member? {
return queryFactory
.selectFrom(member)
.where(
member.id.eq(memberId)
.and(member.role.eq(MemberRole.CREATOR))
)
.fetchFirst()
}
}