From 7657f779b54adf55b22b9c75f3be3792c425c0c2 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 3 Feb 2025 18:57:28 +0900 Subject: [PATCH 01/15] =?UTF-8?q?=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=20-?= =?UTF-8?q?=2019=EC=84=B8=20=EB=AF=B8=EB=A7=8C=20=EB=B3=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=B6=88=EA=B0=80=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EB=85=84=EB=8F=84=20=EC=88=98=EC=A0=95=20-=202005?= =?UTF-8?q?=20->=20=EC=98=AC=ED=95=B4=EB=85=84=EB=8F=84=20-=2019=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt index 9d24456..87ef62d 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt @@ -97,7 +97,7 @@ class AuthService( repository.save(auth) } else { - throw SodaException("2005년 1월 1일 이전 출생자만 본인인증이 가능합니다.") + throw SodaException("${nowYear - 19}년 1월 1일 이전 출생자만 본인인증이 가능합니다.") } } } From bbf3fc04b641cd1d04796b629f0d039840543c26 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 3 Feb 2025 19:08:22 +0900 Subject: [PATCH 02/15] =?UTF-8?q?=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=20-?= =?UTF-8?q?=20gender=20=EA=B0=92=20=EB=A6=AC=ED=84=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/member/auth/AuthResponse.kt | 3 +++ .../kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthResponse.kt new file mode 100644 index 0000000..b061dc6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthResponse.kt @@ -0,0 +1,3 @@ +package kr.co.vividnext.sodalive.member.auth + +data class AuthResponse(val gender: Int) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt index 87ef62d..53ce8de 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthService.kt @@ -73,7 +73,7 @@ class AuthService( } @Transactional - fun authenticate(certificate: AuthVerifyCertificate, memberId: Long) { + fun authenticate(certificate: AuthVerifyCertificate, memberId: Long): AuthResponse { val memberIds = repository.getActiveMemberIdsByDi(di = certificate.di) if (memberIds.size >= 3) { throw SodaException( @@ -96,6 +96,7 @@ class AuthService( auth.member = member repository.save(auth) + return AuthResponse(gender = certificate.gender) } else { throw SodaException("${nowYear - 19}년 1월 1일 이전 출생자만 본인인증이 가능합니다.") } From 1b782f3df802f3a580c1bee737085b4b9932b339 Mon Sep 17 00:00:00 2001 From: Klaus Date: Mon, 3 Feb 2025 20:49:29 +0900 Subject: [PATCH 03/15] =?UTF-8?q?=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=20-?= =?UTF-8?q?=20gender=20=EA=B0=92=20=EB=A6=AC=ED=84=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/vividnext/sodalive/member/auth/AuthController.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt index e0bc549..ac823f2 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/auth/AuthController.kt @@ -24,10 +24,8 @@ class AuthController(private val service: AuthService) { if (service.isBlockAuth(authenticateData)) { service.signOut(member.id!!) throw SodaException("운영정책을 위반하여 이용을 제한합니다.") - } else { - service.authenticate(authenticateData, member.id!!) } - ApiResponse.ok(null, null) + ApiResponse.ok(service.authenticate(authenticateData, member.id!!)) } } From 55badb6206b81ae881e5197a2c8dd82d1f00a673 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 4 Feb 2025 00:39:02 +0900 Subject: [PATCH 04/15] =?UTF-8?q?=EC=98=A4=EB=94=94=EC=85=98=20=EC=9D=91?= =?UTF-8?q?=EC=9B=90=20=ED=95=98=EB=A3=A8=20=EC=B5=9C=EB=8C=80=20=EC=9D=91?= =?UTF-8?q?=EC=9B=90=20=EC=88=98=20=EC=88=98=EC=A0=95=20-=2010=ED=9A=8C=20?= =?UTF-8?q?->=20100=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vividnext/sodalive/audition/vote/AuditionVoteService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/vote/AuditionVoteService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/vote/AuditionVoteService.kt index 34261ea..fa0ea92 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/audition/vote/AuditionVoteService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/vote/AuditionVoteService.kt @@ -42,8 +42,8 @@ class AuditionVoteService( endDate = endDate ) - if (voteCount > 10) { - throw SodaException("오늘 응원은 여기까지!\n하루 최대 10회까지 응원이 가능합니다.\n내일 다시 이용해주세요.") + if (voteCount > 100) { + throw SodaException("오늘 응원은 여기까지!\n하루 최대 100회까지 응원이 가능합니다.\n내일 다시 이용해주세요.") } if (voteCount > 0) { From 05e714fff105cd874481ce46bf7998c727af2104 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 4 Feb 2025 23:25:52 +0900 Subject: [PATCH 05/15] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20-=20?= =?UTF-8?q?=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=8B=9C=EB=A6=AC=EC=A6=88,=20?= =?UTF-8?q?=EB=AC=B4=EB=A3=8C=20=EC=B6=94=EC=B2=9C=20=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=A6=88=20=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95/=EC=88=9C?= =?UTF-8?q?=EC=84=9C=EB=B3=80=EA=B2=BD=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminRecommendSeriesController.kt | 40 +++++++++ .../AdminRecommendSeriesRepository.kt | 43 +++++++++ .../recommend/AdminRecommendSeriesService.kt | 87 +++++++++++++++++++ .../recommend/CreateRecommendSeriesRequest.kt | 6 ++ .../GetAdminRecommendSeriesListResponse.kt | 10 +++ .../UpdateRecommendSeriesOrdersRequest.kt | 5 ++ .../recommend/UpdateRecommendSeriesRequest.kt | 7 ++ .../content/main/tab/RecommendSeries.kt | 28 ++++++ 8 files changed, 226 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/CreateRecommendSeriesRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/GetAdminRecommendSeriesListResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesOrdersRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesRequest.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeries.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesController.kt new file mode 100644 index 0000000..bc747b1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesController.kt @@ -0,0 +1,40 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +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.RequestParam +import org.springframework.web.bind.annotation.RequestPart +import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile + +@RestController +@PreAuthorize("hasRole('ADMIN')") +@RequestMapping("/admin/audio-content/series/recommend") +class AdminRecommendSeriesController(private val service: AdminRecommendSeriesService) { + @GetMapping + fun getRecommendSeriesList(@RequestParam isFree: Boolean) = ApiResponse.ok( + service.getRecommendSeriesList(isFree = isFree) + ) + + @PostMapping + fun createRecommendSeries( + @RequestPart("image") image: MultipartFile, + @RequestPart("request") requestString: String + ) = ApiResponse.ok(service.createRecommendSeries(image, requestString)) + + @PutMapping + fun modifyRecommendSeries( + @RequestPart("image", required = false) image: MultipartFile? = null, + @RequestPart("request") requestString: String + ) = ApiResponse.ok(service.updateRecommendSeries(image, requestString)) + + @PutMapping("/orders") + fun updateRecommendSeriesOrders( + @RequestBody request: UpdateRecommendSeriesOrdersRequest + ) = ApiResponse.ok(service.updateRecommendSeriesOrders(request.ids)) +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt new file mode 100644 index 0000000..04b47f8 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt @@ -0,0 +1,43 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.main.tab.QRecommendSeries.recommendSeries +import kr.co.vividnext.sodalive.content.main.tab.RecommendSeries +import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series +import org.springframework.beans.factory.annotation.Value +import org.springframework.data.jpa.repository.JpaRepository + +interface AdminRecommendSeriesRepository : + JpaRepository, + AdminRecommendSeriesQueryRepository + +interface AdminRecommendSeriesQueryRepository { + fun getRecommendSeriesList(isFree: Boolean): List +} + +class AdminRecommendSeriesQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) : AdminRecommendSeriesQueryRepository { + override fun getRecommendSeriesList(isFree: Boolean): List { + return queryFactory + .select( + QGetAdminRecommendSeriesListResponse( + recommendSeries.id, + series.id, + series.title, + recommendSeries.imagePath.prepend("/").prepend(imageHost) + ) + ) + .from(recommendSeries) + .innerJoin(recommendSeries.series, series) + .where( + recommendSeries.isActive.isTrue + .and(series.isActive.isTrue) + .and(recommendSeries.isFree.eq(isFree)) + ) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesService.kt new file mode 100644 index 0000000..cf697e4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesService.kt @@ -0,0 +1,87 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +import com.fasterxml.jackson.databind.ObjectMapper +import kr.co.vividnext.sodalive.admin.content.series.AdminContentSeriesRepository +import kr.co.vividnext.sodalive.aws.s3.S3Uploader +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.content.main.tab.RecommendSeries +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 AdminRecommendSeriesService( + private val s3Uploader: S3Uploader, + private val repository: AdminRecommendSeriesRepository, + private val seriesRepository: AdminContentSeriesRepository, + private val objectMapper: ObjectMapper, + + @Value("\${cloud.aws.s3.bucket}") + private val bucket: String +) { + fun getRecommendSeriesList(isFree: Boolean): List { + return repository.getRecommendSeriesList(isFree = isFree) + } + + @Transactional + fun createRecommendSeries(image: MultipartFile, requestString: String) { + val request = objectMapper.readValue(requestString, CreateRecommendSeriesRequest::class.java) + val series = seriesRepository.findByIdOrNull(request.seriesId) + ?: throw SodaException("잘못된 요청입니다.") + + val recommendSeries = RecommendSeries(isFree = request.isFree) + recommendSeries.series = series + repository.save(recommendSeries) + + val fileName = generateFileName() + val imagePath = s3Uploader.upload( + inputStream = image.inputStream, + bucket = bucket, + filePath = "recommend_series/${recommendSeries.id}/$fileName" + ) + recommendSeries.imagePath = imagePath + } + + @Transactional + fun updateRecommendSeries(image: MultipartFile?, requestString: String) { + val request = objectMapper.readValue(requestString, UpdateRecommendSeriesRequest::class.java) + val recommendSeries = repository.findByIdOrNull(request.id) + ?: throw SodaException("잘못된 요청입니다.") + + if (image != null) { + val fileName = generateFileName() + val imagePath = s3Uploader.upload( + inputStream = image.inputStream, + bucket = bucket, + filePath = "recommend_series/${recommendSeries.id}/$fileName" + ) + recommendSeries.imagePath = imagePath + } + + if (request.isActive != null) { + recommendSeries.isActive = request.isActive + } + + if (request.seriesId != null) { + val series = seriesRepository.findByIdOrNull(request.seriesId) + + if (series != null) { + recommendSeries.series = series + } + } + } + + @Transactional + fun updateRecommendSeriesOrders(ids: List) { + for (index in ids.indices) { + val recommendSeries = repository.findByIdOrNull(ids[index]) + + if (recommendSeries != null) { + recommendSeries.orders = index + 1 + } + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/CreateRecommendSeriesRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/CreateRecommendSeriesRequest.kt new file mode 100644 index 0000000..7bb67be --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/CreateRecommendSeriesRequest.kt @@ -0,0 +1,6 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +data class CreateRecommendSeriesRequest( + val seriesId: Long, + val isFree: Boolean +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/GetAdminRecommendSeriesListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/GetAdminRecommendSeriesListResponse.kt new file mode 100644 index 0000000..ec9b039 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/GetAdminRecommendSeriesListResponse.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +import com.querydsl.core.annotations.QueryProjection + +data class GetAdminRecommendSeriesListResponse @QueryProjection constructor( + val id: Long, + val seriesId: Long, + val seriesTitle: String, + val imageUrl: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesOrdersRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesOrdersRequest.kt new file mode 100644 index 0000000..90038e0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesOrdersRequest.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +data class UpdateRecommendSeriesOrdersRequest( + val ids: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesRequest.kt new file mode 100644 index 0000000..d820c74 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/UpdateRecommendSeriesRequest.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.admin.content.series.recommend + +data class UpdateRecommendSeriesRequest( + val id: Long, + val seriesId: Long?, + val isActive: Boolean? +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeries.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeries.kt new file mode 100644 index 0000000..9d0290d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeries.kt @@ -0,0 +1,28 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import kr.co.vividnext.sodalive.common.BaseEntity +import kr.co.vividnext.sodalive.creator.admin.content.series.Series +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +@Entity +data class RecommendSeries( + @Column(nullable = false) + var imagePath: String = "", + + @Column(nullable = false) + var orders: Int = 1, + + @Column(nullable = false) + var isFree: Boolean = false, + + @Column(nullable = false) + var isActive: Boolean = true +) : BaseEntity() { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "series_id", nullable = false) + var series: Series? = null +} From 04eb416a73fbe9ac0d3ffd1f5209ada7f4ad4432 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 6 Feb 2025 19:06:27 +0900 Subject: [PATCH 06/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=ED=99=88=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/AudioContentRepository.kt | 8 +- .../content/main/AudioContentMainService.kt | 2 +- ...adCreator.kt => ContentCreatorResponse.kt} | 2 +- .../banner/AudioContentBannerRepository.kt | 41 +++ .../main/banner/AudioContentBannerService.kt | 67 ++++ .../home/AudioContentMainTabHomeController.kt | 22 ++ .../home/AudioContentMainTabHomeService.kt | 109 ++++++ .../tab/home/GetContentMainTabHomeResponse.kt | 23 ++ .../sodalive/notice/GetNoticeResponse.kt | 7 + .../sodalive/notice/ServiceNoticeService.kt | 4 + .../notice/ServiceServiceNoticeRepository.kt | 15 + .../sodalive/rank/RankingRepository.kt | 318 ++++++++++++++++++ .../vividnext/sodalive/rank/RankingService.kt | 151 +++++++++ 13 files changed, 763 insertions(+), 6 deletions(-) rename src/main/kotlin/kr/co/vividnext/sodalive/content/main/{GetNewContentUploadCreator.kt => ContentCreatorResponse.kt} (83%) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index f1ac4e8..d758d01 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -6,9 +6,9 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.category.QCategoryContent.categoryContent import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem -import kr.co.vividnext.sodalive.content.main.GetNewContentUploadCreator import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem import kr.co.vividnext.sodalive.content.main.banner.AudioContentBanner @@ -98,7 +98,7 @@ interface AudioContentQueryRepository { fun getNewContentUploadCreatorList( cloudfrontHost: String, isAdult: Boolean = false - ): List + ): List fun getAudioContentMainBannerList(isAdult: Boolean): List fun getAudioContentCurations(isAdult: Boolean): List @@ -533,7 +533,7 @@ class AudioContentQueryRepositoryImpl( override fun getNewContentUploadCreatorList( cloudfrontHost: String, isAdult: Boolean - ): List { + ): List { var where = audioContent.releaseDate.after(LocalDateTime.now().minusWeeks(2)) .and(audioContent.isActive.isTrue) .and(audioContent.duration.isNotNull) @@ -552,7 +552,7 @@ class AudioContentQueryRepositoryImpl( .limit(20) .fetch() .map { - GetNewContentUploadCreator( + ContentCreatorResponse( it.id!!, it.nickname, creatorProfileImageUrl = if (it.profileImage != null) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index 3e32d6d..2963ec5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -80,7 +80,7 @@ class AudioContentMainService( @Transactional(readOnly = true) @Cacheable(cacheNames = ["default"], key = "'newContentUploadCreatorList:' + #memberId + ':' + #isAdult") - fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List { + fun getNewContentUploadCreatorList(memberId: Long, isAdult: Boolean): List { return repository.getNewContentUploadCreatorList( cloudfrontHost = imageHost, isAdult = isAdult diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetNewContentUploadCreator.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/ContentCreatorResponse.kt similarity index 83% rename from src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetNewContentUploadCreator.kt rename to src/main/kotlin/kr/co/vividnext/sodalive/content/main/ContentCreatorResponse.kt index a53d1d3..c6ace0c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/GetNewContentUploadCreator.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/ContentCreatorResponse.kt @@ -3,7 +3,7 @@ package kr.co.vividnext.sodalive.content.main import com.fasterxml.jackson.annotation.JsonProperty import com.querydsl.core.annotations.QueryProjection -data class GetNewContentUploadCreator @QueryProjection constructor( +data class ContentCreatorResponse @QueryProjection constructor( @JsonProperty("creatorId") val creatorId: Long, @JsonProperty("creatorNickname") val creatorNickname: String, @JsonProperty("creatorProfileImageUrl") val creatorProfileImageUrl: String diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerRepository.kt new file mode 100644 index 0000000..0fd76c6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerRepository.kt @@ -0,0 +1,41 @@ +package kr.co.vividnext.sodalive.content.main.banner + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.main.banner.QAudioContentBanner.audioContentBanner +import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab +import kr.co.vividnext.sodalive.event.QEvent.event +import kr.co.vividnext.sodalive.member.QMember.member +import org.springframework.data.jpa.repository.JpaRepository + +interface AudioContentBannerRepository : JpaRepository, AudioContentBannerQueryRepository + +interface AudioContentBannerQueryRepository { + fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List +} + +class AudioContentBannerQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory +) : AudioContentBannerQueryRepository { + override fun getAudioContentMainBannerList(tabId: Long, isAdult: Boolean): List { + var where = audioContentBanner.isActive.isTrue + + where = if (tabId == 1L) { + where.and(audioContentBanner.tab.isNull) + } else { + where.and(audioContentBanner.tab.id.eq(tabId)) + } + + if (!isAdult) { + where = where.and(audioContentBanner.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContentBanner) + .leftJoin(audioContentBanner.tab, audioContentMainTab) + .leftJoin(audioContentBanner.event, event) + .leftJoin(audioContentBanner.creator, member) + .where(where) + .orderBy(audioContentBanner.orders.asc()) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerService.kt new file mode 100644 index 0000000..022a888 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerService.kt @@ -0,0 +1,67 @@ +package kr.co.vividnext.sodalive.content.main.banner + +import kr.co.vividnext.sodalive.event.EventItem +import kr.co.vividnext.sodalive.member.block.BlockMemberRepository +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class AudioContentBannerService( + private val repository: AudioContentBannerRepository, + private val blockMemberRepository: BlockMemberRepository, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) { + fun getBannerList(tabId: Long, memberId: Long, isAdult: Boolean): List { + return repository.getAudioContentMainBannerList(tabId, isAdult) + .filter { + if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { + !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.creator!!.id!!) + } else if (it.type == AudioContentBannerType.SERIES && it.series != null) { + !blockMemberRepository.isBlocked(blockedMemberId = memberId, memberId = it.series!!.member!!.id!!) + } else { + true + } + } + .map { + GetAudioContentBannerResponse( + type = it.type, + thumbnailImageUrl = "$imageHost/${it.thumbnailImage}", + eventItem = if (it.type == AudioContentBannerType.EVENT && it.event != null) { + EventItem( + id = it.event!!.id!!, + thumbnailImageUrl = if (!it.event!!.thumbnailImage.startsWith("https://")) { + "$imageHost/${it.event!!.thumbnailImage}" + } else { + it.event!!.thumbnailImage + }, + detailImageUrl = if ( + it.event!!.detailImage != null && + !it.event!!.detailImage!!.startsWith("https://") + ) { + "$imageHost/${it.event!!.detailImage}" + } else { + it.event!!.detailImage + }, + popupImageUrl = null, + link = it.event!!.link + ) + } else { + null + }, + creatorId = if (it.type == AudioContentBannerType.CREATOR && it.creator != null) { + it.creator!!.id + } else { + null + }, + seriesId = if (it.type == AudioContentBannerType.SERIES && it.series != null) { + it.series!!.id + } else { + null + }, + link = it.link + ) + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeController.kt new file mode 100644 index 0000000..601f82d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.home + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/home") +class AudioContentMainTabHomeController(private val service: AudioContentMainTabHomeService) { + @GetMapping + fun fetchContentMainHome( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt new file mode 100644 index 0000000..f4693f7 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt @@ -0,0 +1,109 @@ +package kr.co.vividnext.sodalive.content.main.tab.home + +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.notice.ServiceNoticeService +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabHomeService( + private val noticeService: ServiceNoticeService, + private val bannerService: AudioContentBannerService, + private val rankingService: RankingService, + private val eventService: EventService +) { + fun fetchData(member: Member): GetContentMainTabHomeResponse { + // 주간 랭킹 기간 + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(7) + + val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") + val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") + + val formattedLastMonday = startDate.format(startDateFormatter) + val formattedLastSunday = endDate.format(endDateFormatter) + + // 최근 공지사항 + val latestNotice = noticeService.getLatestNotice() + + // 메인 배너 (홈) + val contentBannerList = bannerService.getBannerList( + tabId = 1, + memberId = member.id!!, + isAdult = member.auth != null + ) + + // 인기 크리에이터 + val rankCreatorList = rankingService.getCreatorRanking( + memberId = member.id!!, + rankingDate = "$formattedLastMonday ~ $formattedLastSunday" + ) + + // 인기 시리즈 + val rankSeriesList = rankingService.getSeriesRanking( + memberId = member.id!!, + isAdult = member.auth != null, + startDate = startDate.minusDays(1), + endDate = endDate.minusDays(1) + ) + + // 인기 콘텐츠 + val rankContentList = rankingService.getContentRanking( + memberId = member.id!!, + isAdult = member.auth != null, + startDate = startDate.minusDays(1), + endDate = endDate.minusDays(1) + ) + + // 이벤트 배너 + val eventBannerList = eventService.getEventList(isAdult = member.auth != null) + + /* 채널별 인기 콘텐츠 + * - 콘텐츠를 4개 이상 등록한 채널 + * - 주간 콘텐츠 매출 Top 20 채널 + * - 해당 채널의 누적 매출 Top 2 + * - 해당 채널의 누적 판매 개수 Top 2 + */ + val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( + memberId = member.id!!, + startDate = startDate.minusDays(1), + endDate = endDate.minusDays(1) + ) + + val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + + val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + + return GetContentMainTabHomeResponse( + latestNotice = latestNotice, + bannerList = contentBannerList, + rankCreatorList = rankCreatorList, + rankSeriesList = rankSeriesList, + rankSortTypeList = listOf("매출", "댓글", "좋아요"), + rankContentList = rankContentList, + eventBannerList = eventBannerList, + contentRankCreatorList = contentRankCreatorList, + salesRankContentList = salesRankContentList, + salesCountRankContentList = salesCountRankContentList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt new file mode 100644 index 0000000..c104a77 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/GetContentMainTabHomeResponse.kt @@ -0,0 +1,23 @@ +package kr.co.vividnext.sodalive.content.main.tab.home + +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.event.GetEventResponse +import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse +import kr.co.vividnext.sodalive.notice.NoticeTitleItem + +data class GetContentMainTabHomeResponse( + val tabId: Long = 1, + val latestNotice: NoticeTitleItem?, + val bannerList: List, + val rankCreatorList: GetExplorerSectionResponse, + val rankSeriesList: List, + val rankSortTypeList: List, + val rankContentList: List, + val eventBannerList: GetEventResponse, + val contentRankCreatorList: List, + val salesRankContentList: List, + val salesCountRankContentList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/notice/GetNoticeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/notice/GetNoticeResponse.kt index aa35bbf..246846e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/notice/GetNoticeResponse.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/notice/GetNoticeResponse.kt @@ -1,5 +1,7 @@ package kr.co.vividnext.sodalive.notice +import com.querydsl.core.annotations.QueryProjection + data class GetNoticeResponse( val totalCount: Int, val noticeList: List @@ -11,3 +13,8 @@ data class NoticeItem( val content: String, val date: String ) + +data class NoticeTitleItem @QueryProjection constructor( + val id: Long, + val title: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceNoticeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceNoticeService.kt index 10d28c2..f76e57f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceNoticeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceNoticeService.kt @@ -63,4 +63,8 @@ class ServiceNoticeService(private val repository: ServiceServiceNoticeRepositor return GetNoticeResponse(totalCount, noticeList) } + + fun getLatestNotice(): NoticeTitleItem? { + return repository.getLatestNotice() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceServiceNoticeRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceServiceNoticeRepository.kt index 90d5a47..d5772f5 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceServiceNoticeRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/notice/ServiceServiceNoticeRepository.kt @@ -12,6 +12,7 @@ interface ServiceServiceNoticeRepository : JpaRepository, S interface ServiceNoticeQueryRepository { fun getNoticeTotalCount(): Int fun getNoticeList(pageable: Pageable): List + fun getLatestNotice(): NoticeTitleItem? } @Repository @@ -34,4 +35,18 @@ class ServiceNoticeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory .orderBy(serviceNotice.id.desc()) .fetch() } + + override fun getLatestNotice(): NoticeTitleItem? { + return queryFactory + .select( + QNoticeTitleItem( + serviceNotice.id, + serviceNotice.title + ) + ) + .from(serviceNotice) + .where(serviceNotice.isActive.isTrue) + .orderBy(serviceNotice.id.desc()) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt new file mode 100644 index 0000000..67d17a4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt @@ -0,0 +1,318 @@ +package kr.co.vividnext.sodalive.rank + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.QAudioContent.audioContent +import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment +import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.QContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.QGetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.order.QOrder.order +import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme +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.Series +import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberRole +import kr.co.vividnext.sodalive.member.QMember.member +import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class RankingRepository( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) { + fun getCreatorRankings(): List { + return queryFactory + .select(member) + .from(creatorRanking) + .innerJoin(creatorRanking.member, member) + .orderBy(creatorRanking.ranking.asc()) + .fetch() + } + + fun getAudioContentRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long, + limit: Long, + sortType: String + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = audioContent.isActive.isTrue + .and(audioContent.member.isActive.isTrue) + .and(audioContent.member.isNotNull) + .and(audioContent.member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContentTheme.isActive.isTrue) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + + if (!isAdult) { + where = where.and(audioContent.isAdult.isFalse) + } + + var select = queryFactory + .select( + QGetAudioContentRankingItem( + audioContent.id, + audioContent.title, + audioContent.coverImage.prepend("/").prepend(imageHost), + audioContentTheme.theme, + audioContent.price, + audioContent.duration, + member.id, + member.nickname + ) + ) + + select = when (sortType) { + "후원" -> { + select + .from(audioContentComment) + .innerJoin(audioContentComment.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .leftJoin(blockMember).on(blockMemberCondition) + .where( + where + .and(audioContentComment.isActive.isTrue) + .and(audioContentComment.donationCan.gt(0)) + .and(audioContentComment.createdAt.goe(startDate)) + .and(audioContentComment.createdAt.lt(endDate)) + ) + .groupBy(audioContent.id) + .orderBy(audioContentComment.donationCan.sum().desc(), audioContent.createdAt.asc()) + } + + "댓글" -> { + select + .from(audioContentComment) + .innerJoin(audioContentComment.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .leftJoin(blockMember).on(blockMemberCondition) + .where( + where + .and(audioContentComment.isActive.isTrue) + .and(audioContentComment.createdAt.goe(startDate)) + .and(audioContentComment.createdAt.lt(endDate)) + ) + .groupBy(audioContentComment.audioContent.id) + .orderBy(audioContentComment.id.count().desc(), audioContent.createdAt.asc()) + } + + "좋아요" -> { + select + .from(audioContentLike) + .innerJoin(audioContentLike.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .leftJoin(blockMember).on(blockMemberCondition) + .where( + where + .and(audioContentLike.isActive.isTrue) + .and(audioContentLike.createdAt.goe(startDate)) + .and(audioContentLike.createdAt.lt(endDate)) + ) + .groupBy(audioContentLike.audioContent.id) + .orderBy(audioContentLike.id.count().desc(), audioContent.createdAt.asc()) + } + + else -> { + select + .from(order) + .innerJoin(order.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .innerJoin(audioContent.theme, audioContentTheme) + .leftJoin(blockMember).on(blockMemberCondition) + .where( + where + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(endDate)) + ) + .groupBy(audioContent.id) + .orderBy(order.can.sum().desc(), audioContent.createdAt.asc()) + } + } + + return select + .offset(offset) + .limit(limit) + .fetch() + } + + fun getSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(audioContent.isActive.isTrue) + .and(member.isActive.isTrue) + .and(member.isNotNull) + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(endDate)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .innerJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(series.id) + .orderBy(order.can.sum().desc(), series.createdAt.asc()) + .offset(0) + .limit(10) + .fetch() + } + + fun fetchCreatorByContentRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + val ordersCondition = order.audioContent.id.eq(audioContent.id) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(startDate)) + + val where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + + return queryFactory + .select( + QContentCreatorResponse( + member.id, + member.nickname, + member.profileImage.prepend("/").prepend(imageHost) + ) + ) + .from(member) + .innerJoin(audioContent).on(member.id.eq(audioContent.member.id)) + .leftJoin(order).on(ordersCondition) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(member.id) + .having( + audioContent.id.count().goe(4) + .and(order.can.sum().gt(0)) + ) + .orderBy(order.can.sum().desc()) + .offset(0) + .limit(20) + .fetch() + } + + fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List { + var where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(order.isActive.isTrue) + .and(member.id.eq(creatorId)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select( + QGetAudioContentRankingItem( + audioContent.id, + audioContent.title, + audioContent.coverImage.prepend("/").prepend(imageHost), + audioContentTheme.theme, + audioContent.price, + audioContent.duration, + member.id, + member.nickname + ) + ) + .from(order) + .innerJoin(order.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .where(where) + .groupBy(audioContent.id) + .orderBy(order.can.sum().desc()) + .offset(0) + .limit(2) + .fetch() + } + + fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List { + var where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(order.isActive.isTrue) + .and(member.id.eq(creatorId)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select( + QGetAudioContentRankingItem( + audioContent.id, + audioContent.title, + audioContent.coverImage.prepend("/").prepend(imageHost), + audioContentTheme.theme, + audioContent.price, + audioContent.duration, + member.id, + member.nickname + ) + ) + .from(order) + .innerJoin(order.audioContent, audioContent) + .innerJoin(audioContent.member, member) + .where(where) + .groupBy(audioContent.id) + .orderBy(order.id.count().desc()) + .offset(0) + .limit(2) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt new file mode 100644 index 0000000..662a668 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt @@ -0,0 +1,151 @@ +package kr.co.vividnext.sodalive.rank + +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.content.series.content.ContentSeriesContentRepository +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.SeriesState +import kr.co.vividnext.sodalive.explorer.GetExplorerSectionResponse +import kr.co.vividnext.sodalive.member.MemberService +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class RankingService( + private val repository: RankingRepository, + private val memberService: MemberService, + private val seriesContentRepository: ContentSeriesContentRepository, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) { + fun getCreatorRanking(memberId: Long, rankingDate: String): GetExplorerSectionResponse { + val creatorRankings = repository + .getCreatorRankings() + .filter { !memberService.isBlocked(blockedMemberId = memberId, memberId = it.id!!) } + .map { it.toExplorerSectionCreator(imageHost) } + + return GetExplorerSectionResponse( + title = "인기 크리에이터", + coloredTitle = "인기", + color = "FF5C49", + desc = rankingDate, + creators = creatorRankings + ) + } + + fun getContentRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long = 0, + limit: Long = 12, + sortType: String = "매출" + ): List { + return repository.getAudioContentRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + offset = offset, + limit = limit, + sortType = sortType + ) + } + + fun getSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + val seriesList = repository.getSeriesRanking(memberId, isAdult, startDate, endDate) + return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) + } + + private fun seriesToSeriesListItem( + seriesList: List, + isAdult: Boolean + ): List { + return seriesList + .map { + GetSeriesListResponse.SeriesListItem( + seriesId = it.id!!, + title = it.title, + coverImage = "$imageHost/${it.coverImage!!}", + publishedDaysOfWeek = publishedDaysOfWeekText(it.publishedDaysOfWeek), + isComplete = it.state == SeriesState.COMPLETE, + creator = GetSeriesListResponse.SeriesListItemCreator( + creatorId = it.member!!.id!!, + nickname = it.member!!.nickname, + profileImage = "$imageHost/${it.member!!.profileImage!!}" + ) + ) + } + .map { + it.numberOfContent = seriesContentRepository.getContentCount( + seriesId = it.seriesId, + isAdult = isAdult + ) + + it + } + .map { + val nowDateTime = LocalDateTime.now() + + it.isNew = seriesContentRepository.isNewContent( + seriesId = it.seriesId, + isAdult = isAdult, + fromDate = nowDateTime.minusDays(7), + nowDate = nowDateTime + ) + + it + } + } + + private fun publishedDaysOfWeekText(publishedDaysOfWeek: Set): String { + val dayOfWeekText = publishedDaysOfWeek.toList().sortedBy { it.ordinal } + .map { + when (it) { + SeriesPublishedDaysOfWeek.SUN -> "일" + SeriesPublishedDaysOfWeek.MON -> "월" + SeriesPublishedDaysOfWeek.TUE -> "화" + SeriesPublishedDaysOfWeek.WED -> "수" + SeriesPublishedDaysOfWeek.THU -> "목" + SeriesPublishedDaysOfWeek.FRI -> "금" + SeriesPublishedDaysOfWeek.SAT -> "토" + SeriesPublishedDaysOfWeek.RANDOM -> "랜덤" + } + } + .joinToString(", ") { it } + + return if (publishedDaysOfWeek.contains(SeriesPublishedDaysOfWeek.RANDOM)) { + dayOfWeekText + } else if (publishedDaysOfWeek.size < 7) { + "매주 $dayOfWeekText" + } else { + "매일" + } + } + + fun fetchCreatorByContentRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + return repository.fetchCreatorByContentRevenueRankTop20(memberId, startDate, endDate) + } + + fun fetchCreatorContentBySalesTop2(creatorId: Long, isAdult: Boolean): List { + return repository.fetchCreatorContentBySalesTop2(creatorId, isAdult) + } + + fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List { + return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) + } +} From 27deff3ff3837f9b017339428684dfd8f2fa7055 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 6 Feb 2025 21:09:11 +0900 Subject: [PATCH 07/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=ED=99=88=20=ED=83=AD=20API=20-=20=EC=B1=84?= =?UTF-8?q?=EB=84=90=EB=B3=84=20=EC=9D=B8=EA=B8=B0=20=EC=BD=98=ED=85=90?= =?UTF-8?q?=EC=B8=A0=EC=9D=98=20=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EA=B0=80=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=EB=A5=BC=20=EB=B6=88=EB=9F=AC?= =?UTF-8?q?=EC=98=A4=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/AudioContentMainTabHomeService.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt index f4693f7..4a331e1 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt @@ -83,15 +83,23 @@ class AudioContentMainTabHomeService( endDate = endDate.minusDays(1) ) - val salesRankContentList = rankingService.fetchCreatorContentBySalesTop2( - creatorId = contentRankCreatorList[0].creatorId, - isAdult = member.auth != null - ) + val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorContentBySalesTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + } else { + emptyList() + } - val salesCountRankContentList = rankingService.fetchCreatorContentBySalesCountTop2( - creatorId = contentRankCreatorList[0].creatorId, - isAdult = member.auth != null - ) + val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorContentBySalesCountTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + } else { + emptyList() + } return GetContentMainTabHomeResponse( latestNotice = latestNotice, From b1f82f9abeafc40a3a91cfeb6c8d74c46cf9e4f6 Mon Sep 17 00:00:00 2001 From: Klaus Date: Thu, 6 Feb 2025 21:30:16 +0900 Subject: [PATCH 08/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=ED=99=88=20=ED=83=AD=20API=20-=20=EC=A3=BC?= =?UTF-8?q?=EA=B0=84=20=EB=9E=AD=ED=82=B9=20=EA=B8=B0=EA=B0=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/tab/home/AudioContentMainTabHomeService.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt index 4a331e1..8532a33 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/home/AudioContentMainTabHomeService.kt @@ -28,7 +28,7 @@ class AudioContentMainTabHomeService( .minusWeeks(1) .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) val endDate = startDate - .plusDays(7) + .plusDays(6) val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일") val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일") @@ -57,7 +57,7 @@ class AudioContentMainTabHomeService( memberId = member.id!!, isAdult = member.auth != null, startDate = startDate.minusDays(1), - endDate = endDate.minusDays(1) + endDate = endDate ) // 인기 콘텐츠 @@ -65,7 +65,7 @@ class AudioContentMainTabHomeService( memberId = member.id!!, isAdult = member.auth != null, startDate = startDate.minusDays(1), - endDate = endDate.minusDays(1) + endDate = endDate ) // 이벤트 배너 @@ -80,7 +80,7 @@ class AudioContentMainTabHomeService( val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( memberId = member.id!!, startDate = startDate.minusDays(1), - endDate = endDate.minusDays(1) + endDate = endDate ) val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { From 0f8fcbcaedc51eb1558b4d82b49a475c47af259f Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 7 Feb 2025 02:58:13 +0900 Subject: [PATCH 09/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EC=8B=9C=EB=A6=AC=EC=A6=88=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AudioContentCurationQueryRepository.kt | 18 ++ .../tab/GetRecommendSeriesListResponse.kt | 11 ++ .../main/tab/RecommendSeriesRepository.kt | 43 ++++ .../AudioContentMainTabSeriesController.kt | 22 +++ .../AudioContentMainTabSeriesService.kt | 149 ++++++++++++++ .../series/GetContentMainTabSeriesResponse.kt | 23 +++ .../tab/series/GetSeriesCurationResponse.kt | 8 + .../content/series/ContentSeriesRepository.kt | 66 +++++++ .../content/series/ContentSeriesService.kt | 22 +++ .../series/GetSeriesGenreListResponse.kt | 8 + .../sodalive/rank/RankingRepository.kt | 186 +++++++++++++++++- .../vividnext/sodalive/rank/RankingService.kt | 45 +++++ 12 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt index 916d860..84b63ed 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt @@ -6,6 +6,8 @@ import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.SortType import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem import kr.co.vividnext.sodalive.content.main.QGetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.curation.QAudioContentCuration.audioContentCuration +import kr.co.vividnext.sodalive.content.main.tab.QAudioContentMainTab.audioContentMainTab import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentTheme import kr.co.vividnext.sodalive.member.QMember.member import org.springframework.stereotype.Repository @@ -87,4 +89,20 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact .orderBy(orderBy) .fetch() } + + fun findByContentMainTabId(tabId: Long, isAdult: Boolean): List { + var where = audioContentCuration.isActive.isTrue + .and(audioContentMainTab.id.eq(tabId)) + + if (!isAdult) { + where = where.and(audioContentCuration.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContentCuration) + .innerJoin(audioContentCuration.tab, audioContentMainTab) + .where(where) + .orderBy(audioContentCuration.orders.asc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt new file mode 100644 index 0000000..a84a20d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetRecommendSeriesListResponse.kt @@ -0,0 +1,11 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import com.querydsl.core.annotations.QueryProjection + +data class GetRecommendSeriesListResponse @QueryProjection constructor( + val seriesId: Long, + val title: String, + val imageUrl: String, + val creatorId: Long, + val creatorNickname: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt new file mode 100644 index 0000000..4375d96 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt @@ -0,0 +1,43 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.content.main.tab.QRecommendSeries.recommendSeries +import kr.co.vividnext.sodalive.creator.admin.content.series.QSeries.series +import kr.co.vividnext.sodalive.member.QMember.member +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Repository + +@Repository +class RecommendSeriesRepository( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val imageHost: String +) { + fun getNewSeriesList(isAdult: Boolean): List { + var where = recommendSeries.isActive.isTrue + .and(recommendSeries.isFree.isFalse) + .and(series.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select( + QGetRecommendSeriesListResponse( + series.id, + series.title, + recommendSeries.imagePath.prepend("/").prepend(imageHost), + member.id, + member.nickname + ) + ) + .from(recommendSeries) + .innerJoin(recommendSeries.series, series) + .innerJoin(series.member, member) + .where(where) + .orderBy(recommendSeries.orders.asc()) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt new file mode 100644 index 0000000..1e27aa6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/series") +class AudioContentMainTabSeriesController(private val service: AudioContentMainTabSeriesService) { + @GetMapping + fun fetchContentMainSeries( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt new file mode 100644 index 0000000..681dd5f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/AudioContentMainTabSeriesService.kt @@ -0,0 +1,149 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository +import kr.co.vividnext.sodalive.content.series.ContentSeriesService +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabSeriesService( + private val bannerService: AudioContentBannerService, + private val seriesService: ContentSeriesService, + private val rankingService: RankingService, + private val recommendSeriesRepository: RecommendSeriesRepository, + private val eventService: EventService, + private val curationRepository: AudioContentCurationQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabSeriesResponse { + val isAdult = member.auth != null + + // 메인 배너 (시리즈) + val contentBannerList = bannerService.getBannerList( + tabId = 2, + memberId = member.id!!, + isAdult = isAdult + ) + + val originalAudioDrama = seriesService.getOriginalAudioDramaList( + isAdult = isAdult, + offset = 0, + limit = 20 + ) + + // 일간 랭킹 + val currentDateTime = LocalDateTime.now() + val dailyRankingStartDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusDays(2) + val dailyRankingEndDate = dailyRankingStartDate + .plusDays(1) + + val rankSeriesList = rankingService.getSeriesRanking( + memberId = member.id!!, + isAdult = isAdult, + startDate = dailyRankingStartDate, + endDate = dailyRankingEndDate + ) + + // 시리즈 장르 + val genreList = seriesService.getGenreList(isAdult = isAdult) + + // 장르별 추천 시리즈 + val recommendSeriesList = if (genreList.isNotEmpty()) { + rankingService.getSeriesAllRankingByGenre( + memberId = member.id!!, + isAdult = isAdult, + genreId = genreList[0].id + ) + } else { + emptyList() + } + + // 새로운 시리즈 + val newSeriesList = recommendSeriesRepository.getNewSeriesList(isAdult = isAdult) + + // 완결 시리즈 월간 랭킹 + val monthlyRankingStartDate = currentDateTime + .withDayOfMonth(1) + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusDays(1) + val monthlyRankingEndDate = monthlyRankingStartDate + .plusMonths(1) + + val rankCompleteSeriesList = rankingService.getCompleteSeriesRanking( + memberId = member.id!!, + isAdult = isAdult, + startDate = monthlyRankingStartDate, + endDate = monthlyRankingEndDate + ) + + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(6) + + val seriesRankCreatorList = rankingService.fetchCreatorBySeriesRevenueRankTop20( + memberId = member.id!!, + startDate = startDate.minusDays(1), + endDate = endDate + ) + + val salesRankContentList = if (seriesRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorSeriesBySales( + creatorId = seriesRankCreatorList[0].creatorId, + isAdult = isAdult + ) + } else { + emptyList() + } + + // 이벤트 배너 + val eventBannerList = eventService.getEventList(isAdult = isAdult) + + // 큐레이션 + val curationList = curationRepository.findByContentMainTabId(tabId = 2, isAdult = isAdult) + .map { + GetSeriesCurationResponse( + title = it.title, + items = seriesService.fetchSeriesByCurationId( + curationId = it.id!!, + memberId = member.id!!, + isAdult = isAdult + ) + ) + } + + return GetContentMainTabSeriesResponse( + contentBannerList = contentBannerList, + originalAudioDrama = if (originalAudioDrama.size >= 3) { + originalAudioDrama + } else { + emptyList() + }, + rankSeriesList = rankSeriesList, + genreList = genreList, + recommendSeriesList = recommendSeriesList, + newSeriesList = newSeriesList, + rankCompleteSeriesList = rankCompleteSeriesList, + seriesRankCreatorList = seriesRankCreatorList, + salesRankContentList = salesRankContentList, + eventBannerList = eventBannerList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt new file mode 100644 index 0000000..d7155b0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetContentMainTabSeriesResponse.kt @@ -0,0 +1,23 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse +import kr.co.vividnext.sodalive.content.series.GetSeriesGenreListResponse +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabSeriesResponse( + val tabId: Long = 2, + val contentBannerList: List, + val originalAudioDrama: List, + val rankSeriesList: List, + val genreList: List, + val recommendSeriesList: List, + val newSeriesList: List, + val rankCompleteSeriesList: List, + val seriesRankCreatorList: List, + val salesRankContentList: List, + val eventBannerList: GetEventResponse, + val curationList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt new file mode 100644 index 0000000..bb24415 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/series/GetSeriesCurationResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.content.main.tab.series + +import kr.co.vividnext.sodalive.content.series.GetSeriesListResponse + +data class GetSeriesCurationResponse( + val title: String, + val items: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt index 10b8f25..e87f059 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesRepository.kt @@ -2,15 +2,21 @@ package kr.co.vividnext.sodalive.content.series import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.QAudioContent.audioContent 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.main.curation.QAudioContentCurationItem.audioContentCurationItem import kr.co.vividnext.sodalive.content.series.content.GetSeriesContentMinMaxPriceResponse import kr.co.vividnext.sodalive.content.series.content.QGetSeriesContentMinMaxPriceResponse 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.Series 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.QMember.member +import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember import org.springframework.data.jpa.repository.JpaRepository interface ContentSeriesRepository : JpaRepository, ContentSeriesQueryRepository @@ -29,6 +35,9 @@ interface ContentSeriesQueryRepository { fun getKeywordList(seriesId: Long): List fun getSeriesContentMinMaxPrice(seriesId: Long): GetSeriesContentMinMaxPriceResponse fun getRecommendSeriesList(isAuth: Boolean, contentType: ContentType, limit: Long): List + fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long = 0, limit: Long = 20): List + fun getGenreList(isAdult: Boolean): List + fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List } class ContentSeriesQueryRepositoryImpl( @@ -133,4 +142,61 @@ class ContentSeriesQueryRepositoryImpl( .limit(limit) .fetch() } + + override fun getOriginalAudioDramaList(isAdult: Boolean, offset: Long, limit: Long): List { + var where = series.isOriginal.isTrue + .and(series.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .selectFrom(series) + .where(where) + .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc()) + .offset(offset) + .limit(limit) + .fetch() + } + + override fun getGenreList(isAdult: Boolean): List { + var where = seriesGenre.isActive.isTrue + + if (!isAdult) { + where = where.and(seriesGenre.isAdult.isFalse) + } + + return queryFactory + .select(QGetSeriesGenreListResponse(seriesGenre.id, seriesGenre.genre)) + .from(seriesGenre) + .where(where) + .orderBy(seriesGenre.orders.asc()) + .fetch() + } + + override fun findByCurationId(curationId: Long, memberId: Long, isAdult: Boolean): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(member.isActive.isTrue) + .and(member.role.eq(MemberRole.CREATOR)) + .and(blockMember.id.isNull) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(audioContentCurationItem) + .innerJoin(audioContentCurationItem.curation, audioContentCuration) + .innerJoin(audioContentCurationItem.series, series) + .innerJoin(series.member, member) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt index 2a60ae3..1e95021 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/ContentSeriesService.kt @@ -30,6 +30,19 @@ class ContentSeriesService( @Value("\${cloud.aws.cloud-front.host}") private val coverImageHost: String ) { + fun getOriginalAudioDramaList( + isAdult: Boolean, + offset: Long = 0, + limit: Long = 20 + ): List { + val originalAudioDramaList = repository.getOriginalAudioDramaList(isAdult, offset, limit) + return seriesToSeriesListItem(originalAudioDramaList, isAdult) + } + + fun getGenreList(isAdult: Boolean): List { + return repository.getGenreList(isAdult = isAdult) + } + fun getSeriesList( creatorId: Long, member: Member, @@ -170,6 +183,15 @@ class ContentSeriesService( return seriesToSeriesListItem(seriesList = seriesList, isAdult = member.auth != null) } + fun fetchSeriesByCurationId( + curationId: Long, + memberId: Long, + isAdult: Boolean + ): List { + val seriesList = repository.findByCurationId(curationId = curationId, memberId = memberId, isAdult = isAdult) + return seriesToSeriesListItem(seriesList, isAdult) + } + private fun seriesToSeriesListItem( seriesList: List, isAdult: Boolean diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt new file mode 100644 index 0000000..30aa078 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/series/GetSeriesGenreListResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.content.series + +import com.querydsl.core.annotations.QueryProjection + +data class GetSeriesGenreListResponse @QueryProjection constructor( + val id: Long, + val genre: String +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt index 67d17a4..235b034 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt @@ -1,6 +1,8 @@ package kr.co.vividnext.sodalive.rank +import com.querydsl.core.types.dsl.Expressions import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.admin.content.series.genre.QSeriesGenre.seriesGenre import kr.co.vividnext.sodalive.content.QAudioContent.audioContent import kr.co.vividnext.sodalive.content.comment.QAudioContentComment.audioContentComment import kr.co.vividnext.sodalive.content.like.QAudioContentLike.audioContentLike @@ -13,6 +15,7 @@ import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentThe 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.Series +import kr.co.vividnext.sodalive.creator.admin.content.series.SeriesState import kr.co.vividnext.sodalive.explorer.QCreatorRanking.creatorRanking import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.MemberRole @@ -187,16 +190,107 @@ class RankingRepository( .innerJoin(seriesContent.series, series) .innerJoin(seriesContent.content, audioContent) .innerJoin(series.member, member) - .innerJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) .leftJoin(blockMember).on(blockMemberCondition) .where(where) .groupBy(series.id) - .orderBy(order.can.sum().desc(), series.createdAt.asc()) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) .offset(0) .limit(10) .fetch() } + fun getCompleteSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long, + limit: Long + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(series.state.eq(SeriesState.COMPLETE)) + .and(audioContent.isActive.isTrue) + .and(member.isActive.isTrue) + .and(member.isNotNull) + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(endDate)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(offset) + .limit(limit) + .fetch() + } + + fun getSeriesAllRankingByGenre(memberId: Long, isAdult: Boolean, genreId: Long): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + var where = series.isActive.isTrue + .and(seriesGenre.id.eq(genreId)) + .and(audioContent.isActive.isTrue) + .and(member.isActive.isTrue) + .and(member.isNotNull) + .and(member.role.eq(MemberRole.CREATOR)) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + .and(order.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(series.genre, seriesGenre) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(20) + .fetch() + } + fun fetchCreatorByContentRevenueRankTop20( memberId: Long, startDate: LocalDateTime, @@ -232,11 +326,11 @@ class RankingRepository( .leftJoin(blockMember).on(blockMemberCondition) .where(where) .groupBy(member.id) - .having( - audioContent.id.count().goe(4) - .and(order.can.sum().gt(0)) + .having(audioContent.id.count().goe(4)) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() ) - .orderBy(order.can.sum().desc()) .offset(0) .limit(20) .fetch() @@ -315,4 +409,84 @@ class RankingRepository( .limit(2) .fetch() } + + fun fetchCreatorBySeriesRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + + val ordersCondition = order.audioContent.id.eq(audioContent.id) + .and(order.isActive.isTrue) + .and(order.createdAt.goe(startDate)) + .and(order.createdAt.lt(startDate)) + + val where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(series.isActive.isTrue) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(blockMember.id.isNull) + + return queryFactory + .select( + QContentCreatorResponse( + member.id, + member.nickname, + member.profileImage.prepend("/").prepend(imageHost) + ) + ) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(ordersCondition) + .leftJoin(blockMember).on(blockMemberCondition) + .where(where) + .groupBy(member.id) + .having(series.id.countDistinct().goe(3)) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(20) + .fetch() + } + + fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List { + var where = member.isActive.isTrue + .and(member.role.eq(MemberRole.CREATOR)) + .and(series.isActive.isTrue) + .and(audioContent.isActive.isTrue) + .and(audioContent.duration.isNotNull) + .and(audioContent.limited.isNull) + .and(order.isActive.isTrue) + .and(member.id.eq(creatorId)) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select(series) + .from(seriesContent) + .innerJoin(seriesContent.series, series) + .innerJoin(seriesContent.content, audioContent) + .innerJoin(series.member, member) + .leftJoin(order).on(audioContent.id.eq(order.audioContent.id)) + .where(where) + .groupBy(series.id) + .orderBy( + order.can.sum().desc(), + Expressions.numberTemplate(Double::class.java, "function('rand')").asc() + ) + .offset(0) + .limit(10) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt index 662a668..508da30 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt @@ -67,6 +67,38 @@ class RankingService( return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) } + fun getSeriesAllRankingByGenre( + memberId: Long, + isAdult: Boolean, + genreId: Long + ): List { + val seriesList = repository.getSeriesAllRankingByGenre( + memberId = memberId, + isAdult = isAdult, + genreId = genreId + ) + return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) + } + + fun getCompleteSeriesRanking( + memberId: Long, + isAdult: Boolean, + startDate: LocalDateTime, + endDate: LocalDateTime, + offset: Long = 0, + limit: Long = 10 + ): List { + val seriesList = repository.getCompleteSeriesRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + offset = offset, + limit = limit + ) + return seriesToSeriesListItem(seriesList = seriesList, isAdult = isAdult) + } + private fun seriesToSeriesListItem( seriesList: List, isAdult: Boolean @@ -148,4 +180,17 @@ class RankingService( fun fetchCreatorContentBySalesCountTop2(creatorId: Long, isAdult: Boolean): List { return repository.fetchCreatorContentBySalesCountTop2(creatorId, isAdult) } + + fun fetchCreatorBySeriesRevenueRankTop20( + memberId: Long, + startDate: LocalDateTime, + endDate: LocalDateTime + ): List { + return repository.fetchCreatorBySeriesRevenueRankTop20(memberId, startDate, endDate) + } + + fun fetchCreatorSeriesBySales(creatorId: Long, isAdult: Boolean): List { + val seriesList = repository.fetchCreatorSeriesBySales(creatorId = creatorId, isAdult = isAdult) + return seriesToSeriesListItem(seriesList, isAdult) + } } From c5539bc7e3f02279bdf809fb86b5cd6635ad7e51 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 7 Feb 2025 18:29:31 +0900 Subject: [PATCH 10/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=8B=A8=ED=8E=B8=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/AudioContentRepository.kt | 13 ++- .../content/main/AudioContentMainService.kt | 2 - .../AudioContentMainTabContentController.kt | 22 ++++ .../AudioContentMainTabContentService.kt | 110 ++++++++++++++++++ .../GetContentMainTabContentResponse.kt | 20 ++++ .../content/theme/AudioContentThemeService.kt | 12 +- 6 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index d758d01..245792e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -24,6 +24,7 @@ import kr.co.vividnext.sodalive.content.theme.QAudioContentTheme.audioContentThe import kr.co.vividnext.sodalive.event.QEvent.event import kr.co.vividnext.sodalive.member.MemberRole import kr.co.vividnext.sodalive.member.QMember.member +import kr.co.vividnext.sodalive.member.block.QBlockMember.blockMember import org.springframework.beans.factory.annotation.Value import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository @@ -61,7 +62,6 @@ interface AudioContentQueryRepository { ): List fun findByTheme( - cloudfrontHost: String, memberId: Long, theme: String = "", sortType: SortType = SortType.NEWEST, @@ -334,7 +334,6 @@ class AudioContentQueryRepositoryImpl( } override fun findByTheme( - cloudfrontHost: String, memberId: Long, theme: String, sortType: SortType, @@ -343,6 +342,10 @@ class AudioContentQueryRepositoryImpl( offset: Long, limit: Long ): List { + val blockMemberCondition = blockMember.member.id.eq(member.id) + .and(blockMember.isActive.isTrue) + .and(blockMember.blockedMember.id.eq(memberId)) + val orderBy = when (sortType) { SortType.NEWEST -> listOf(audioContent.releaseDate.desc(), audioContent.id.desc()) SortType.PRICE_HIGH -> listOf( @@ -365,6 +368,7 @@ class AudioContentQueryRepositoryImpl( .or(audioContent.releaseDate.loe(LocalDateTime.now())) .or(audioContent.member.id.eq(memberId)) ) + .and(blockMember.id.isNull) if (!isAdult) { where = where.and(audioContent.isAdult.isFalse) @@ -384,10 +388,10 @@ class AudioContentQueryRepositoryImpl( .select( QGetAudioContentMainItem( audioContent.id, - audioContent.coverImage.prepend("/").prepend(cloudfrontHost), + audioContent.coverImage.prepend("/").prepend(imageHost), audioContent.title, member.id, - member.profileImage.prepend("/").prepend(cloudfrontHost), + member.profileImage.prepend("/").prepend(imageHost), member.nickname, audioContent.price, audioContent.duration @@ -396,6 +400,7 @@ class AudioContentQueryRepositoryImpl( .from(audioContent) .innerJoin(audioContent.member, member) .innerJoin(audioContent.theme, audioContentTheme) + .leftJoin(blockMember).on(blockMemberCondition) .where(where) .offset(offset) .limit(limit) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index 2963ec5..8f03d29 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -39,7 +39,6 @@ class AudioContentMainService( pageable: Pageable ): List { return repository.findByTheme( - cloudfrontHost = imageHost, memberId = member.id!!, theme = theme, isAdult = member.auth != null && isAdultContentVisible, @@ -47,7 +46,6 @@ class AudioContentMainService( offset = pageable.offset, limit = pageable.pageSize.toLong() ) - .filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creatorId) } } @Transactional(readOnly = true) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt new file mode 100644 index 0000000..80762a1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.content + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/content") +class AudioContentMainTabContentController(private val service: AudioContentMainTabContentService) { + @GetMapping + fun fetchContentMainTabContent( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt new file mode 100644 index 0000000..182ae7c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt @@ -0,0 +1,110 @@ +package kr.co.vividnext.sodalive.content.main.tab.content + +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class AudioContentMainTabContentService( + private val bannerService: AudioContentBannerService, + private val audioContentRepository: AudioContentRepository, + private val audioContentThemeRepository: AudioContentThemeQueryRepository, + private val rankingService: RankingService, + private val eventService: EventService +) { + fun fetchData(member: Member): GetContentMainTabContentResponse { + /** + * 새로운 단편 + * 일간 랭킹 + * 채널별 추천 단편 + */ + val memberId = member.id!! + val isAdult = member.auth != null + + // 단편 배너 + val contentBannerList = bannerService.getBannerList( + tabId = 3, + memberId = memberId, + isAdult = isAdult + ) + + // 새로운 단편 테마 + val themeOfContentList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) + + // 새로운 단편 + val newContentList = if (themeOfContentList.isNotEmpty()) { + audioContentRepository.findByTheme( + memberId = member.id!!, + theme = themeOfContentList[0], + isAdult = member.auth != null, + contentType = ContentType.ALL, + offset = 0, + limit = 10 + ) + } else { + emptyList() + } + + // 일간 랭킹 + val currentDateTime = LocalDateTime.now() + val dailyRankingStartDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusDays(2) + val dailyRankingEndDate = dailyRankingStartDate + .plusDays(1) + + val rankContentList = rankingService.getContentRanking( + memberId = memberId, + isAdult = isAdult, + startDate = dailyRankingStartDate, + endDate = dailyRankingEndDate + ) + + // 이벤트 배너 + val eventBannerList = eventService.getEventList(isAdult = isAdult) + + val contentRankCreatorList = rankingService.fetchCreatorByContentRevenueRankTop20( + memberId = member.id!!, + startDate = dailyRankingStartDate.minusDays(1), + endDate = dailyRankingEndDate + ) + + val salesRankContentList = if (contentRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorContentBySalesTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + } else { + emptyList() + } + + val salesCountRankContentList = if (contentRankCreatorList.isNotEmpty()) { + rankingService.fetchCreatorContentBySalesCountTop2( + creatorId = contentRankCreatorList[0].creatorId, + isAdult = member.auth != null + ) + } else { + emptyList() + } + + return GetContentMainTabContentResponse( + bannerList = contentBannerList, + contentThemeList = themeOfContentList, + newContentList = newContentList, + rankSortTypeList = listOf("매출", "댓글", "좋아요"), + rankContentList = rankContentList, + contentRankCreatorList = contentRankCreatorList, + salesRankContentList = salesRankContentList, + salesCountRankContentList = salesCountRankContentList, + eventBannerList = eventBannerList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt new file mode 100644 index 0000000..721e960 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/GetContentMainTabContentResponse.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.content.main.tab.content + +import kr.co.vividnext.sodalive.content.main.ContentCreatorResponse +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabContentResponse( + val tabId: Long = 3, + val bannerList: List, + val contentThemeList: List, + val newContentList: List, + val rankSortTypeList: List, + val rankContentList: List, + val contentRankCreatorList: List, + val salesRankContentList: List, + val salesCountRankContentList: List, + val eventBannerList: GetEventResponse +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeService.kt index 5a3c6a9..b0ad97a 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/theme/AudioContentThemeService.kt @@ -6,19 +6,13 @@ import kr.co.vividnext.sodalive.content.ContentType import kr.co.vividnext.sodalive.content.SortType import kr.co.vividnext.sodalive.content.theme.content.GetContentByThemeResponse import kr.co.vividnext.sodalive.member.Member -import kr.co.vividnext.sodalive.member.block.BlockMemberRepository -import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @Service class AudioContentThemeService( private val queryRepository: AudioContentThemeQueryRepository, - private val blockMemberRepository: BlockMemberRepository, - private val contentRepository: AudioContentRepository, - - @Value("\${cloud.aws.cloud-front.host}") - private val imageHost: String + private val contentRepository: AudioContentRepository ) { @Transactional(readOnly = true) fun getThemes(): List { @@ -46,7 +40,6 @@ class AudioContentThemeService( ) val items = contentRepository.findByTheme( - cloudfrontHost = imageHost, memberId = member.id!!, theme = theme.theme, sortType = sortType, @@ -55,9 +48,6 @@ class AudioContentThemeService( offset = offset, limit = limit ) - .asSequence() - .filter { !blockMemberRepository.isBlocked(blockedMemberId = member.id!!, memberId = it.creatorId) } - .toList() return GetContentByThemeResponse( theme = theme.theme, From d1579126f375e600b98f2129851179503dd905e3 Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 7 Feb 2025 23:45:44 +0900 Subject: [PATCH 11/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=AA=A8=EB=8B=9D=EC=BD=9C=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/AudioContentRepository.kt | 6 +- .../content/main/AudioContentMainService.kt | 1 - .../main/tab/GetContentCurationResponse.kt | 8 ++ .../AudioContentMainTabAlarmController.kt | 22 +++++ .../alarm/AudioContentMainTabAlarmService.kt | 85 +++++++++++++++++++ .../alarm/GetContentMainTabAlarmResponse.kt | 17 ++++ .../AudioContentMainTabContentService.kt | 5 -- .../sodalive/rank/RankingRepository.kt | 7 +- .../vividnext/sodalive/rank/RankingService.kt | 6 +- 9 files changed, 144 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetContentCurationResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/GetContentMainTabAlarmResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index 245792e..980ab66 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -104,7 +104,6 @@ interface AudioContentQueryRepository { fun getAudioContentCurations(isAdult: Boolean): List fun findAudioContentByCurationId( curationId: Long, - cloudfrontHost: String, isAdult: Boolean, contentType: ContentType ): List @@ -601,7 +600,6 @@ class AudioContentQueryRepositoryImpl( override fun findAudioContentByCurationId( curationId: Long, - cloudfrontHost: String, isAdult: Boolean, contentType: ContentType ): List { @@ -625,12 +623,12 @@ class AudioContentQueryRepositoryImpl( .select( QGetAudioContentMainItem( audioContent.id, - audioContent.coverImage.prepend("/").prepend(cloudfrontHost), + audioContent.coverImage.prepend("/").prepend(imageHost), audioContent.title, member.id, member.profileImage .prepend("/") - .prepend(cloudfrontHost), + .prepend(imageHost), member.nickname, audioContent.price, audioContent.duration diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt index 8f03d29..b72b829 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/AudioContentMainService.kt @@ -162,7 +162,6 @@ class AudioContentMainService( description = it.description, contents = repository.findAudioContentByCurationId( curationId = it.id!!, - cloudfrontHost = imageHost, isAdult = isAdult, contentType = contentType ) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetContentCurationResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetContentCurationResponse.kt new file mode 100644 index 0000000..b020326 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/GetContentCurationResponse.kt @@ -0,0 +1,8 @@ +package kr.co.vividnext.sodalive.content.main.tab + +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem + +data class GetContentCurationResponse( + val title: String, + val items: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmController.kt new file mode 100644 index 0000000..68bae3c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.alarm + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/alarm") +class AudioContentMainTabAlarmController(private val service: AudioContentMainTabAlarmService) { + @GetMapping + fun fetchContentMainTabAlarm( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt new file mode 100644 index 0000000..e38d7bb --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt @@ -0,0 +1,85 @@ +package kr.co.vividnext.sodalive.content.main.tab.alarm + +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabAlarmService( + private val bannerService: AudioContentBannerService, + private val contentRepository: AudioContentRepository, + private val rankingService: RankingService, + private val eventService: EventService, + private val curationRepository: AudioContentCurationQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabAlarmResponse { + val isAdult = member.auth != null + val memberId = member.id!! + + // 메인 배너 (시리즈) + val contentBannerList = bannerService.getBannerList( + tabId = 4, + memberId = memberId, + isAdult = isAdult + ) + + val alarmThemeList = listOf("모닝콜", "슬립콜", "알람") + val newAlarmContentList = contentRepository.findByTheme( + memberId = memberId, + theme = alarmThemeList[0], + isAdult = isAdult, + contentType = ContentType.ALL, + limit = 10 + ) + + // 주간 랭킹 기간 + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(6) + + val rankAlarmContentList = rankingService.getContentRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + theme = alarmThemeList[0] + ) + + val eventBannerList = eventService.getEventList(isAdult = isAdult) + val curationList = curationRepository.findByContentMainTabId(tabId = 4, isAdult = isAdult) + .map { + GetContentCurationResponse( + title = it.title, + items = contentRepository.findAudioContentByCurationId( + curationId = it.id!!, + isAdult = isAdult, + contentType = ContentType.ALL + ) + ) + } + + return GetContentMainTabAlarmResponse( + contentBannerList = contentBannerList, + alarmThemeList = alarmThemeList, + newAlarmContentList = newAlarmContentList, + rankAlarmContentList = rankAlarmContentList, + eventBannerList = eventBannerList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/GetContentMainTabAlarmResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/GetContentMainTabAlarmResponse.kt new file mode 100644 index 0000000..46d61de --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/GetContentMainTabAlarmResponse.kt @@ -0,0 +1,17 @@ +package kr.co.vividnext.sodalive.content.main.tab.alarm + +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabAlarmResponse( + val tab: Long = 4, + val contentBannerList: List, + val alarmThemeList: List, + val newAlarmContentList: List, + val rankAlarmContentList: List, + val eventBannerList: GetEventResponse, + val curationList: List +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt index 182ae7c..6807e14 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/content/AudioContentMainTabContentService.kt @@ -19,11 +19,6 @@ class AudioContentMainTabContentService( private val eventService: EventService ) { fun fetchData(member: Member): GetContentMainTabContentResponse { - /** - * 새로운 단편 - * 일간 랭킹 - * 채널별 추천 단편 - */ val memberId = member.id!! val isAdult = member.auth != null diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt index 235b034..2250eee 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingRepository.kt @@ -48,7 +48,8 @@ class RankingRepository( endDate: LocalDateTime, offset: Long, limit: Long, - sortType: String + sortType: String, + theme: String = "" ): List { val blockMemberCondition = blockMember.member.id.eq(member.id) .and(blockMember.isActive.isTrue) @@ -67,6 +68,10 @@ class RankingRepository( where = where.and(audioContent.isAdult.isFalse) } + if (theme.isNotBlank()) { + where = where.and(audioContentTheme.theme.eq(theme)) + } + var select = queryFactory .select( QGetAudioContentRankingItem( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt index 508da30..a3504fc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/rank/RankingService.kt @@ -44,7 +44,8 @@ class RankingService( endDate: LocalDateTime, offset: Long = 0, limit: Long = 12, - sortType: String = "매출" + sortType: String = "매출", + theme: String = "" ): List { return repository.getAudioContentRanking( memberId = memberId, @@ -53,7 +54,8 @@ class RankingService( endDate = endDate, offset = offset, limit = limit, - sortType = sortType + sortType = sortType, + theme = theme ) } From 14b0eeec7e04f66c2df23776475acd24ec3e04f9 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 8 Feb 2025 00:01:14 +0900 Subject: [PATCH 12/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20ASMR=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alarm/AudioContentMainTabAlarmService.kt | 1 - .../asmr/AudioContentMainTabAsmrController.kt | 22 +++++ .../asmr/AudioContentMainTabAsmrService.kt | 84 +++++++++++++++++++ .../tab/asmr/GetContentMainTabAsmrResponse.kt | 16 ++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt index e38d7bb..81128fe 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/alarm/AudioContentMainTabAlarmService.kt @@ -25,7 +25,6 @@ class AudioContentMainTabAlarmService( val isAdult = member.auth != null val memberId = member.id!! - // 메인 배너 (시리즈) val contentBannerList = bannerService.getBannerList( tabId = 4, memberId = memberId, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrController.kt new file mode 100644 index 0000000..ad35a6d --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.asmr + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/asmr") +class AudioContentMainTabAsmrController(private val service: AudioContentMainTabAsmrService) { + @GetMapping + fun fetchContentMainTabAsmr( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt new file mode 100644 index 0000000..9748778 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/AudioContentMainTabAsmrService.kt @@ -0,0 +1,84 @@ +package kr.co.vividnext.sodalive.content.main.tab.asmr + +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabAsmrService( + private val bannerService: AudioContentBannerService, + private val contentRepository: AudioContentRepository, + private val rankingService: RankingService, + private val eventService: EventService, + private val curationRepository: AudioContentCurationQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabAsmrResponse { + val isAdult = member.auth != null + val memberId = member.id!! + val theme = "ASMR" + val tabId = 5L + + val contentBannerList = bannerService.getBannerList( + tabId = tabId, + memberId = memberId, + isAdult = isAdult + ) + + val newAsmrContentList = contentRepository.findByTheme( + memberId = memberId, + theme = theme, + isAdult = isAdult, + contentType = ContentType.ALL, + limit = 10 + ) + + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(6) + + val rankAsmrContentList = rankingService.getContentRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + theme = theme + ) + + val eventBannerList = eventService.getEventList(isAdult = isAdult) + + val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) + .map { + GetContentCurationResponse( + title = it.title, + items = contentRepository.findAudioContentByCurationId( + curationId = it.id!!, + isAdult = isAdult, + contentType = ContentType.ALL + ) + ) + } + + return GetContentMainTabAsmrResponse( + contentBannerList = contentBannerList, + newAsmrContentList = newAsmrContentList, + rankAsmrContentList = rankAsmrContentList, + eventBannerList = eventBannerList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt new file mode 100644 index 0000000..5b406e1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/asmr/GetContentMainTabAsmrResponse.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.content.main.tab.asmr + +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabAsmrResponse( + val tab: Long = 5, + val contentBannerList: List, + val newAsmrContentList: List, + val rankAsmrContentList: List, + val eventBannerList: GetEventResponse, + val curationList: List +) From 3410045f834e92b76d5b8d7bb169562e790637b0 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 8 Feb 2025 00:52:39 +0900 Subject: [PATCH 13/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=8B=A4=EC=8B=9C=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...AudioContentMainTabLiveReplayController.kt | 22 +++++ .../AudioContentMainTabLiveReplayService.kt | 84 +++++++++++++++++++ .../GetContentMainTabLiveReplayResponse.kt | 16 ++++ 3 files changed, 122 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayController.kt new file mode 100644 index 0000000..c9b3739 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.replay + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/replay") +class AudioContentMainTabLiveReplayController(private val service: AudioContentMainTabLiveReplayService) { + @GetMapping + fun fetchContentMainTabLiveReplay( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt new file mode 100644 index 0000000..560ca76 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/AudioContentMainTabLiveReplayService.kt @@ -0,0 +1,84 @@ +package kr.co.vividnext.sodalive.content.main.tab.replay + +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.EventService +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.rank.RankingService +import org.springframework.stereotype.Service +import java.time.DayOfWeek +import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters + +@Service +class AudioContentMainTabLiveReplayService( + private val bannerService: AudioContentBannerService, + private val contentRepository: AudioContentRepository, + private val rankingService: RankingService, + private val eventService: EventService, + private val curationRepository: AudioContentCurationQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabLiveReplayResponse { + val isAdult = member.auth != null + val memberId = member.id!! + val theme = "다시듣기" + val tabId = 6L + + val contentBannerList = bannerService.getBannerList( + tabId = tabId, + memberId = memberId, + isAdult = isAdult + ) + + val newLiveReplayContentList = contentRepository.findByTheme( + memberId = memberId, + theme = theme, + isAdult = isAdult, + contentType = ContentType.ALL, + limit = 10 + ) + + val currentDateTime = LocalDateTime.now() + val startDate = currentDateTime + .withHour(15) + .withMinute(0) + .withSecond(0) + .minusWeeks(1) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + val endDate = startDate + .plusDays(6) + + val rankLiveReplayContentList = rankingService.getContentRanking( + memberId = memberId, + isAdult = isAdult, + startDate = startDate, + endDate = endDate, + theme = theme + ) + + val eventBannerList = eventService.getEventList(isAdult = isAdult) + + val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) + .map { + GetContentCurationResponse( + title = it.title, + items = contentRepository.findAudioContentByCurationId( + curationId = it.id!!, + isAdult = isAdult, + contentType = ContentType.ALL + ) + ) + } + + return GetContentMainTabLiveReplayResponse( + contentBannerList = contentBannerList, + newLiveReplayContentList = newLiveReplayContentList, + rankLiveReplayContentList = rankLiveReplayContentList, + eventBannerList = eventBannerList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt new file mode 100644 index 0000000..eea8309 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/replay/GetContentMainTabLiveReplayResponse.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.content.main.tab.replay + +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.GetAudioContentRankingItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.event.GetEventResponse + +data class GetContentMainTabLiveReplayResponse( + val tabId: Long = 6, + val contentBannerList: List, + val newLiveReplayContentList: List, + val rankLiveReplayContentList: List, + val eventBannerList: GetEventResponse, + val curationList: List +) From 9be78062bef125441e8c0ce259f24d55920ac7ae Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 8 Feb 2025 02:40:25 +0900 Subject: [PATCH 14/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20-=20=EB=AC=B4=EB=A3=8C=20=ED=83=AD=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/AudioContentRepository.kt | 10 +- .../AudioContentCurationQueryRepository.kt | 22 +++++ .../main/tab/RecommendSeriesRepository.kt | 27 ++++++ .../free/AudioContentMainTabFreeController.kt | 22 +++++ .../free/AudioContentMainTabFreeService.kt | 91 +++++++++++++++++++ .../tab/free/GetContentMainTabFreeResponse.kt | 16 ++++ 6 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt index 980ab66..f855b57 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/AudioContentRepository.kt @@ -68,7 +68,8 @@ interface AudioContentQueryRepository { isAdult: Boolean = false, contentType: ContentType, offset: Long = 0, - limit: Long = 20 + limit: Long = 20, + isFree: Boolean = false ): List fun totalCountByTheme( @@ -339,7 +340,8 @@ class AudioContentQueryRepositoryImpl( isAdult: Boolean, contentType: ContentType, offset: Long, - limit: Long + limit: Long, + isFree: Boolean ): List { val blockMemberCondition = blockMember.member.id.eq(member.id) .and(blockMember.isActive.isTrue) @@ -383,6 +385,10 @@ class AudioContentQueryRepositoryImpl( where = where.and(audioContentTheme.theme.eq(theme)) } + if (isFree) { + where = where.and(audioContent.price.loe(0)) + } + return queryFactory .select( QGetAudioContentMainItem( diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt index 84b63ed..28c6985 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/curation/AudioContentCurationQueryRepository.kt @@ -105,4 +105,26 @@ class AudioContentCurationQueryRepository(private val queryFactory: JPAQueryFact .orderBy(audioContentCuration.orders.asc()) .fetch() } + + fun findByContentMainTabIdAndTitle( + tabId: Long, + title: String, + isAdult: Boolean, + offset: Long = 0, + limit: Long = 12 + ): List { + var where = audioContentCuration.isActive.isTrue + .and(audioContentMainTab.id.eq(tabId)) + + if (!isAdult) { + where = where.and(audioContentCuration.isAdult.isFalse) + } + + return queryFactory + .selectFrom(audioContentCuration) + .innerJoin(audioContentCuration.tab, audioContentMainTab) + .where(where) + .orderBy(audioContentCuration.orders.asc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt index 4375d96..b17a583 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/RecommendSeriesRepository.kt @@ -40,4 +40,31 @@ class RecommendSeriesRepository( .orderBy(recommendSeries.orders.asc()) .fetch() } + + fun getRecommendSeriesList(isAdult: Boolean): List { + var where = recommendSeries.isActive.isTrue + .and(recommendSeries.isFree.isTrue) + .and(series.isActive.isTrue) + + if (!isAdult) { + where = where.and(series.isAdult.isFalse) + } + + return queryFactory + .select( + QGetRecommendSeriesListResponse( + series.id, + series.title, + recommendSeries.imagePath.prepend("/").prepend(imageHost), + member.id, + member.nickname + ) + ) + .from(recommendSeries) + .innerJoin(recommendSeries.series, series) + .innerJoin(series.member, member) + .where(where) + .orderBy(recommendSeries.orders.asc()) + .fetch() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt new file mode 100644 index 0000000..e356865 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.content.main.tab.free + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/v2/audio-content/main/series") +class AudioContentMainTabFreeController(private val service: AudioContentMainTabFreeService) { + @GetMapping + fun fetchContentMainFree( + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok(service.fetchData(member)) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt new file mode 100644 index 0000000..b64a2ab --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeService.kt @@ -0,0 +1,91 @@ +package kr.co.vividnext.sodalive.content.main.tab.free + +import kr.co.vividnext.sodalive.content.AudioContentRepository +import kr.co.vividnext.sodalive.content.ContentType +import kr.co.vividnext.sodalive.content.main.banner.AudioContentBannerService +import kr.co.vividnext.sodalive.content.main.curation.AudioContentCurationQueryRepository +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.content.main.tab.RecommendSeriesRepository +import kr.co.vividnext.sodalive.content.theme.AudioContentThemeQueryRepository +import kr.co.vividnext.sodalive.member.Member +import org.springframework.stereotype.Service + +@Service +class AudioContentMainTabFreeService( + private val bannerService: AudioContentBannerService, + private val recommendSeriesRepository: RecommendSeriesRepository, + private val curationRepository: AudioContentCurationQueryRepository, + private val contentRepository: AudioContentRepository, + private val audioContentRepository: AudioContentRepository, + private val audioContentThemeRepository: AudioContentThemeQueryRepository +) { + fun fetchData(member: Member): GetContentMainTabFreeResponse { + val isAdult = member.auth != null + val memberId = member.id!! + val tabId = 7L + + val contentBannerList = bannerService.getBannerList( + tabId = tabId, + memberId = memberId, + isAdult = isAdult + ) + + val introduceCreator = curationRepository.findByContentMainTabIdAndTitle( + tabId = tabId, + title = "크리에이터 소개", + isAdult = isAdult + ) + .map { + GetContentCurationResponse( + title = it.title, + items = contentRepository.findAudioContentByCurationId( + curationId = it.id!!, + isAdult = isAdult, + contentType = ContentType.ALL + ) + ) + } + + val recommendSeriesList = recommendSeriesRepository.getRecommendSeriesList(isAdult = isAdult) + + val themeList = audioContentThemeRepository.getActiveThemeOfContent(isAdult = isAdult) + val newFreeContentList = if (themeList.isNotEmpty()) { + audioContentRepository.findByTheme( + memberId = member.id!!, + theme = themeList[0], + isAdult = member.auth != null, + contentType = ContentType.ALL, + offset = 0, + limit = 10, + isFree = true + ) + } else { + emptyList() + } + + val curationList = curationRepository.findByContentMainTabId(tabId = tabId, isAdult = isAdult) + .map { + GetContentCurationResponse( + title = it.title, + items = contentRepository.findAudioContentByCurationId( + curationId = it.id!!, + isAdult = isAdult, + contentType = ContentType.ALL + ) + ) + } + + return GetContentMainTabFreeResponse( + contentBannerList = contentBannerList, + introduceCreator = if (introduceCreator.isNotEmpty()) { + introduceCreator[0] + } else { + null + }, + recommendSeriesList = recommendSeriesList, + themeList = themeList, + newFreeContentList = newFreeContentList, + curationList = curationList + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt new file mode 100644 index 0000000..aed9911 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/GetContentMainTabFreeResponse.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.content.main.tab.free + +import kr.co.vividnext.sodalive.content.main.GetAudioContentMainItem +import kr.co.vividnext.sodalive.content.main.banner.GetAudioContentBannerResponse +import kr.co.vividnext.sodalive.content.main.tab.GetContentCurationResponse +import kr.co.vividnext.sodalive.content.main.tab.GetRecommendSeriesListResponse + +data class GetContentMainTabFreeResponse( + val tabId: Long = 7, + val contentBannerList: List, + val introduceCreator: GetContentCurationResponse?, + val recommendSeriesList: List, + val themeList: List, + val newFreeContentList: List, + val curationList: List +) From 04f2ac6815c9a121336704cfef9dfe772c981c96 Mon Sep 17 00:00:00 2001 From: Klaus Date: Sat, 8 Feb 2025 02:46:50 +0900 Subject: [PATCH 15/15] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AC=B4=EB=A3=8C=20=ED=83=AD=20API=20=3D=20url=20?= =?UTF-8?q?->=20/free=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content/main/tab/free/AudioContentMainTabFreeController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt index e356865..e588782 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/main/tab/free/AudioContentMainTabFreeController.kt @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("/v2/audio-content/main/series") +@RequestMapping("/v2/audio-content/main/free") class AudioContentMainTabFreeController(private val service: AudioContentMainTabFreeService) { @GetMapping fun fetchContentMainFree(