diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveController.kt index a4f7224..869dd19 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveController.kt @@ -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), + "수정되었습니다." + ) } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveRoomQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveRoomQueryRepository.kt index a7f081b..dbc2890 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveRoomQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveRoomQueryRepository.kt @@ -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 { + return queryFactory + .selectFrom(recommendLiveCreatorBanner) + .offset(pageable.offset) + .limit(pageable.pageSize.toLong()) + .orderBy(recommendLiveCreatorBanner.orders.asc(), recommendLiveCreatorBanner.id.desc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveService.kt index bec8b06..0865230 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/AdminLiveService.kt @@ -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) { + for (index in ids.indices) { + val recommendCreatorBanner = recommendCreatorBannerRepository.findByIdOrNull(id = ids[index]) + + if (recommendCreatorBanner != null) { + recommendCreatorBanner.orders = firstOrders + index + } + } + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/GetAdminRecommendCreatorResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/GetAdminRecommendCreatorResponse.kt new file mode 100644 index 0000000..f527cfe --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/GetAdminRecommendCreatorResponse.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.admin.live + +data class GetAdminRecommendCreatorResponse( + val totalCount: Int, + val recommendCreatorList: List +) + +data class GetAdminRecommendCreatorResponseItem( + val id: Long, + val image: String, + val creatorId: Long, + val creatorNickname: String, + val startDate: String, + val endDate: String, + val isAdult: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/UpdateAdminRecommendCreatorBannerOrdersRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/UpdateAdminRecommendCreatorBannerOrdersRequest.kt new file mode 100644 index 0000000..93bd453 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/live/UpdateAdminRecommendCreatorBannerOrdersRequest.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.admin.live + +data class UpdateAdminRecommendCreatorBannerOrdersRequest( + val firstOrders: Int, + val ids: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt index 78e823e..b56d030 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt @@ -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) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBannerRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBannerRepository.kt new file mode 100644 index 0000000..7c5c605 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBannerRepository.kt @@ -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 diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index 7edb17f..6eaa3fd 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -14,6 +14,7 @@ interface MemberRepository : JpaRepository, MemberQueryRepository interface MemberQueryRepository { fun findByPushToken(pushToken: String): List fun findByNicknameAndOtherCondition(nickname: String, memberId: Long): List + 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() + } }