From affbb3eba323f06effadbc7b8d018a4928fc6e22 Mon Sep 17 00:00:00 2001 From: Klaus Date: Tue, 31 Dec 2024 02:46:19 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=B1=20-=20=EB=B0=B0=EC=97=AD=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20API,=20=EC=A7=80=EC=9B=90=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicant/AuditionApplicantController.kt | 34 +++++++ .../applicant/AuditionApplicantRepository.kt | 93 +++++++++++++++++++ .../applicant/AuditionApplicantService.kt | 26 ++++++ .../applicant/AuditionApplicantSortType.kt | 5 + .../GetAuditionApplicantListResponse.kt | 16 ++++ .../audition/role/AuditionRoleController.kt | 29 ++++++ .../audition/role/AuditionRoleRepository.kt | 22 +++++ .../audition/role/AuditionRoleService.kt | 23 +++++ .../role/GetAuditionRoleDetailResponse.kt | 32 +++++++ 9 files changed, 280 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantSortType.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleController.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleService.kt create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantController.kt new file mode 100644 index 0000000..ff03e54 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantController.kt @@ -0,0 +1,34 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.member.Member +import org.springframework.data.domain.Pageable +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.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/audition/applicant") +class AuditionApplicantController(private val service: AuditionApplicantService) { + @GetMapping + fun getAuditionApplicantList( + @RequestParam auditionRoleId: Long, + @RequestParam sortType: AuditionApplicantSortType, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, + pageable: Pageable + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok( + service.getAuditionApplicantList( + auditionRoleId = auditionRoleId, + sortType = sortType, + offset = pageable.offset, + limit = pageable.pageSize.toLong() + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt new file mode 100644 index 0000000..23f88ac --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantRepository.kt @@ -0,0 +1,93 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import com.querydsl.jpa.impl.JPAQueryFactory +import kr.co.vividnext.sodalive.audition.AuditionApplicant +import kr.co.vividnext.sodalive.audition.QAuditionApplicant.auditionApplicant +import kr.co.vividnext.sodalive.audition.QAuditionRole.auditionRole +import kr.co.vividnext.sodalive.audition.QAuditionVote.auditionVote +import kr.co.vividnext.sodalive.member.QMember.member +import org.springframework.beans.factory.annotation.Value +import org.springframework.data.jpa.repository.JpaRepository + +interface AuditionApplicantRepository : JpaRepository, AuditionApplicantQueryRepository + +interface AuditionApplicantQueryRepository { + fun isAlreadyApplicant(auditionRoleId: Long, memberId: Long): Boolean + fun getAuditionApplicantTotalCount(auditionRoleId: Long): Int + fun getAuditionApplicantList( + auditionRoleId: Long, + sortType: AuditionApplicantSortType, + offset: Long, + limit: Long + ): List +} + +class AuditionApplicantQueryRepositoryImpl( + private val queryFactory: JPAQueryFactory, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String +) : AuditionApplicantQueryRepository { + override fun isAlreadyApplicant(auditionRoleId: Long, memberId: Long): Boolean { + return queryFactory + .select(auditionApplicant.id) + .from(auditionApplicant) + .innerJoin(auditionApplicant.role, auditionRole) + .innerJoin(auditionApplicant.member, member) + .where( + auditionRole.id.eq(auditionRoleId), + member.id.eq(memberId), + auditionApplicant.isActive.isTrue + ) + .fetch() + .size > 0 + } + + override fun getAuditionApplicantTotalCount(auditionRoleId: Long): Int { + return queryFactory + .select(auditionApplicant.id) + .from(auditionApplicant) + .innerJoin(auditionApplicant.role, auditionRole) + .where( + auditionRole.id.eq(auditionRoleId), + auditionRole.isActive.isTrue + ) + .fetch() + .size + } + + override fun getAuditionApplicantList( + auditionRoleId: Long, + sortType: AuditionApplicantSortType, + offset: Long, + limit: Long + ): List { + val orderBy = if (sortType == AuditionApplicantSortType.LIKES) { + auditionVote.id.count().desc() + } else { + auditionApplicant.id.desc() + } + + return queryFactory + .select( + QGetAuditionRoleApplicantItem( + auditionApplicant.id, + member.nickname, + member.profileImage.prepend("/").prepend(cloudFrontHost), + auditionApplicant.voicePath.prepend("/").prepend(cloudFrontHost), + auditionVote.id.count() + ) + ) + .from(auditionApplicant) + .innerJoin(auditionApplicant.member, member) + .innerJoin(auditionApplicant.role, auditionRole) + .leftJoin(auditionVote).on(auditionApplicant.id.eq(auditionVote.applicant.id)) + .where( + auditionRole.id.eq(auditionRoleId), + auditionRole.isActive.isTrue + ) + .groupBy(auditionApplicant.id) + .orderBy(orderBy) + .fetch() + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantService.kt new file mode 100644 index 0000000..edc1c94 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantService.kt @@ -0,0 +1,26 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import org.springframework.stereotype.Service + +@Service +class AuditionApplicantService(private val repository: AuditionApplicantRepository) { + fun getAuditionApplicantList( + auditionRoleId: Long, + sortType: AuditionApplicantSortType, + offset: Long, + limit: Long + ): GetAuditionApplicantListResponse { + val totalCount = repository.getAuditionApplicantTotalCount(auditionRoleId = auditionRoleId) + val items = repository.getAuditionApplicantList( + auditionRoleId = auditionRoleId, + sortType = sortType, + offset = offset, + limit = limit + ) + + return GetAuditionApplicantListResponse( + totalCount = totalCount, + items = items + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantSortType.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantSortType.kt new file mode 100644 index 0000000..649dc1e --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/AuditionApplicantSortType.kt @@ -0,0 +1,5 @@ +package kr.co.vividnext.sodalive.audition.applicant + +enum class AuditionApplicantSortType { + NEWEST, LIKES +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt new file mode 100644 index 0000000..d86ff49 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/applicant/GetAuditionApplicantListResponse.kt @@ -0,0 +1,16 @@ +package kr.co.vividnext.sodalive.audition.applicant + +import com.querydsl.core.annotations.QueryProjection + +data class GetAuditionApplicantListResponse( + val totalCount: Int, + val items: List +) + +data class GetAuditionRoleApplicantItem @QueryProjection constructor( + val applicantId: Long, + val nickname: String, + val profileImageUrl: String, + val voiceUrl: String, + val voteCount: Long +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleController.kt new file mode 100644 index 0000000..5f7a48b --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleController.kt @@ -0,0 +1,29 @@ +package kr.co.vividnext.sodalive.audition.role + +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.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/audition/role") +class AuditionRoleController(private val service: AuditionRoleService) { + @GetMapping("/{id}") + fun getAuditionRoleDetail( + @PathVariable id: Long, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + + ApiResponse.ok( + service.getAuditionRoleDetail( + auditionRoleId = id, + memberId = member.id!! + ) + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleRepository.kt index b46f4df..735dffc 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleRepository.kt @@ -14,6 +14,7 @@ interface AuditionRoleRepository : JpaRepository, AuditionRo interface AuditionRoleQueryRepository { fun getAuditionRoleListByAuditionId(auditionId: Long): List + fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailData? } class AuditionRoleQueryRepositoryImpl( @@ -40,4 +41,25 @@ class AuditionRoleQueryRepositoryImpl( ) .fetch() } + + override fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailData? { + return queryFactory + .select( + QGetAuditionRoleDetailData( + auditionRole.id, + auditionRole.name, + auditionRole.imagePath.prepend("/").prepend(cloudfrontHost), + auditionRole.information, + audition.originalWorkUrl, + auditionRole.auditionScriptUrl + ) + ) + .from(auditionRole) + .innerJoin(auditionRole.audition, audition) + .where( + auditionRole.id.eq(auditionRoleId), + auditionRole.isActive.isTrue + ) + .fetchFirst() + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleService.kt new file mode 100644 index 0000000..c1e05d3 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/AuditionRoleService.kt @@ -0,0 +1,23 @@ +package kr.co.vividnext.sodalive.audition.role + +import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantRepository +import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.stereotype.Service + +@Service +class AuditionRoleService( + private val repository: AuditionRoleRepository, + private val applicantRepository: AuditionApplicantRepository +) { + fun getAuditionRoleDetail(auditionRoleId: Long, memberId: Long): GetAuditionRoleDetailResponse { + val roleDetailData = repository.getAuditionRoleDetail(auditionRoleId = auditionRoleId) + ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") + + val isAlreadyApplicant = applicantRepository.isAlreadyApplicant( + auditionRoleId = auditionRoleId, + memberId = memberId + ) + + return roleDetailData.toDetailResponse(isAlreadyApplicant = isAlreadyApplicant) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt new file mode 100644 index 0000000..c49287c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/audition/role/GetAuditionRoleDetailResponse.kt @@ -0,0 +1,32 @@ +package kr.co.vividnext.sodalive.audition.role + +import com.querydsl.core.annotations.QueryProjection + +data class GetAuditionRoleDetailResponse( + val auditionRoleId: Long, + val name: String, + val imageUrl: String, + val information: String, + val originalWorkUrl: String, + val auditionScriptUrl: String, + val isAlreadyApplicant: Boolean = false +) + +data class GetAuditionRoleDetailData @QueryProjection constructor( + val auditionRoleId: Long, + val name: String, + val imageUrl: String, + val information: String, + val originalWorkUrl: String, + val auditionScriptUrl: String +) { + fun toDetailResponse(isAlreadyApplicant: Boolean) = GetAuditionRoleDetailResponse( + auditionRoleId = auditionRoleId, + name = name, + imageUrl = imageUrl, + information = information, + originalWorkUrl = originalWorkUrl, + auditionScriptUrl = auditionScriptUrl, + isAlreadyApplicant = isAlreadyApplicant + ) +}