관리자 오디션 캐릭터
- 등록, 수정, 상세, 지원리스트 API
This commit is contained in:
		| @@ -0,0 +1,45 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.ApiResponse | ||||
| import org.springframework.data.domain.Pageable | ||||
| import org.springframework.web.bind.annotation.GetMapping | ||||
| import org.springframework.web.bind.annotation.PathVariable | ||||
| import org.springframework.web.bind.annotation.PostMapping | ||||
| import org.springframework.web.bind.annotation.PutMapping | ||||
| import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RequestPart | ||||
| import org.springframework.web.bind.annotation.RestController | ||||
| import org.springframework.web.multipart.MultipartFile | ||||
|  | ||||
| @RestController | ||||
| @RequestMapping("/admin/audition/role") | ||||
| class AdminAuditionRoleController(private val service: AdminAuditionRoleService) { | ||||
|     @PostMapping | ||||
|     fun createAuditionRole( | ||||
|         @RequestPart("image") image: MultipartFile, | ||||
|         @RequestPart("request") requestString: String | ||||
|     ) = ApiResponse.ok(service.createAuditionRole(image, requestString), "등록되었습니다.") | ||||
|  | ||||
|     @PutMapping | ||||
|     fun updateAuditionRole( | ||||
|         @RequestPart("image", required = false) image: MultipartFile? = null, | ||||
|         @RequestPart("request") requestString: String | ||||
|     ) = ApiResponse.ok(service.updateAuditionRole(image, requestString), "수정되었습니다.") | ||||
|  | ||||
|     @GetMapping("/{id}") | ||||
|     fun getAuditionRoleDetail(@PathVariable id: Long) = ApiResponse.ok( | ||||
|         service.getAuditionRoleDetail(auditionRoleId = id) | ||||
|     ) | ||||
|  | ||||
|     @GetMapping("/{id}/applicant") | ||||
|     fun getAuditionApplicantList( | ||||
|         @PathVariable id: Long, | ||||
|         pageable: Pageable | ||||
|     ) = ApiResponse.ok( | ||||
|         service.getAuditionApplicantList( | ||||
|             auditionRoleId = id, | ||||
|             offset = pageable.offset, | ||||
|             limit = pageable.pageSize.toLong() | ||||
|         ) | ||||
|     ) | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import com.querydsl.jpa.impl.JPAQueryFactory | ||||
| import kr.co.vividnext.sodalive.audition.AuditionRole | ||||
| 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 AdminAuditionRoleRepository : JpaRepository<AuditionRole, Long>, AdminAuditionRoleQueryRepository | ||||
|  | ||||
| interface AdminAuditionRoleQueryRepository { | ||||
|     fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailResponse | ||||
|     fun getAuditionApplicantList(auditionRoleId: Long, offset: Long, limit: Long): List<GetAuditionRoleApplicantItem> | ||||
|     fun getAuditionApplicantTotalCount(auditionRoleId: Long): Int | ||||
| } | ||||
|  | ||||
| class AdminAuditionRoleQueryRepositoryImpl( | ||||
|     private val queryFactory: JPAQueryFactory, | ||||
|  | ||||
|     @Value("\${cloud.aws.cloud-front.host}") | ||||
|     private val cloudfrontHost: String | ||||
| ) : AdminAuditionRoleQueryRepository { | ||||
|     override fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailResponse { | ||||
|         return queryFactory | ||||
|             .select( | ||||
|                 QGetAuditionRoleDetailResponse( | ||||
|                     auditionRole.name, | ||||
|                     auditionRole.imagePath.prepend("/").prepend(cloudfrontHost), | ||||
|                     auditionRole.auditionScriptUrl | ||||
|                 ) | ||||
|             ) | ||||
|             .from(auditionRole) | ||||
|             .where(auditionRole.id.eq(auditionRoleId)) | ||||
|             .fetchFirst() | ||||
|     } | ||||
|  | ||||
|     override fun getAuditionApplicantList( | ||||
|         auditionRoleId: Long, | ||||
|         offset: Long, | ||||
|         limit: Long | ||||
|     ): List<GetAuditionRoleApplicantItem> { | ||||
|         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)) | ||||
|             .groupBy(auditionApplicant.id) | ||||
|             .fetch() | ||||
|     } | ||||
|  | ||||
|     override fun getAuditionApplicantTotalCount(auditionRoleId: Long): Int { | ||||
|         return queryFactory | ||||
|             .select(auditionApplicant.id) | ||||
|             .from(auditionApplicant) | ||||
|             .innerJoin(auditionApplicant.role, auditionRole) | ||||
|             .where(auditionRole.id.eq(auditionRoleId)) | ||||
|             .fetch() | ||||
|             .size | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import com.fasterxml.jackson.databind.ObjectMapper | ||||
| import kr.co.vividnext.sodalive.admin.audition.AdminAuditionRepository | ||||
| import kr.co.vividnext.sodalive.audition.AuditionRole | ||||
| import kr.co.vividnext.sodalive.aws.s3.S3Uploader | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
| 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 AdminAuditionRoleService( | ||||
|     private val s3Uploader: S3Uploader, | ||||
|     private val objectMapper: ObjectMapper, | ||||
|     private val repository: AdminAuditionRoleRepository, | ||||
|     private val auditionRepository: AdminAuditionRepository, | ||||
|  | ||||
|     @Value("\${cloud.aws.s3.bucket}") | ||||
|     private val bucket: String | ||||
| ) { | ||||
|     @Transactional | ||||
|     fun createAuditionRole(image: MultipartFile, requestString: String) { | ||||
|         val request = objectMapper.readValue(requestString, CreateAuditionRoleRequest::class.java) | ||||
|         val auditionRole = AuditionRole(name = request.name, auditionScriptUrl = request.auditionScriptUrl) | ||||
|         val audition = auditionRepository.findByIdOrNull(id = request.auditionId) | ||||
|             ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") | ||||
|         auditionRole.audition = audition | ||||
|         repository.save(auditionRole) | ||||
|  | ||||
|         val fileName = generateFileName("audition_role") | ||||
|         val imagePath = s3Uploader.upload( | ||||
|             inputStream = image.inputStream, | ||||
|             bucket = bucket, | ||||
|             filePath = "audition/role/${auditionRole.id}/$fileName" | ||||
|         ) | ||||
|         auditionRole.imagePath = imagePath | ||||
|     } | ||||
|  | ||||
|     @Transactional | ||||
|     fun updateAuditionRole(image: MultipartFile?, requestString: String) { | ||||
|         val request = objectMapper.readValue(requestString, UpdateAuditionRoleRequest::class.java) | ||||
|         val auditionRole = repository.findByIdOrNull(id = request.id) | ||||
|             ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") | ||||
|  | ||||
|         if (!request.name.isNullOrBlank() && request.name.length > 2) { | ||||
|             auditionRole.name = request.name | ||||
|         } | ||||
|  | ||||
|         if (!request.auditionScriptUrl.isNullOrBlank()) { | ||||
|             auditionRole.auditionScriptUrl = request.auditionScriptUrl | ||||
|         } | ||||
|  | ||||
|         if (request.isActive != null) { | ||||
|             auditionRole.isActive = request.isActive | ||||
|         } | ||||
|  | ||||
|         if (image != null) { | ||||
|             val fileName = generateFileName("audition_role") | ||||
|             val imagePath = s3Uploader.upload( | ||||
|                 inputStream = image.inputStream, | ||||
|                 bucket = bucket, | ||||
|                 filePath = "audition/role/${auditionRole.id}/$fileName" | ||||
|             ) | ||||
|             auditionRole.imagePath = imagePath | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailResponse { | ||||
|         return repository.getAuditionRoleDetail(auditionRoleId = auditionRoleId) | ||||
|     } | ||||
|  | ||||
|     fun getAuditionApplicantList(auditionRoleId: Long, offset: Long, limit: Long): GetAuditionRoleApplicantResponse { | ||||
|         val totalCount = repository.getAuditionApplicantTotalCount(auditionRoleId = auditionRoleId) | ||||
|         val items = repository.getAuditionApplicantList(auditionRoleId = auditionRoleId, offset = offset, limit = limit) | ||||
|         return GetAuditionRoleApplicantResponse(totalCount, items) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
|  | ||||
| data class CreateAuditionRoleRequest( | ||||
|     val auditionId: Long, | ||||
|     val name: String, | ||||
|     val auditionScriptUrl: String | ||||
| ) { | ||||
|     init { | ||||
|         if (auditionId < 0) { | ||||
|             throw SodaException("캐릭터가 등록될 오디션을 선택하세요") | ||||
|         } | ||||
|  | ||||
|         if (name.isBlank() || name.length < 2) { | ||||
|             throw SodaException("캐릭터명을 입력하세요") | ||||
|         } | ||||
|  | ||||
|         if (auditionScriptUrl.isBlank() || auditionScriptUrl.length < 10) { | ||||
|             throw SodaException("오디션 대본 URL을 입력하세요") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,16 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAuditionRoleApplicantResponse( | ||||
|     val totalCount: Int, | ||||
|     val items: List<GetAuditionRoleApplicantItem> | ||||
| ) | ||||
|  | ||||
| data class GetAuditionRoleApplicantItem @QueryProjection constructor( | ||||
|     val applicantId: Long, | ||||
|     val nickname: String, | ||||
|     val profileImageUrl: String, | ||||
|     val voiceUrl: String, | ||||
|     val voteCount: Long | ||||
| ) | ||||
| @@ -0,0 +1,9 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import com.querydsl.core.annotations.QueryProjection | ||||
|  | ||||
| data class GetAuditionRoleDetailResponse @QueryProjection constructor( | ||||
|     val name: String, | ||||
|     val imageUrl: String, | ||||
|     val auditionScriptUrl: String | ||||
| ) | ||||
| @@ -0,0 +1,16 @@ | ||||
| package kr.co.vividnext.sodalive.admin.audition.role | ||||
|  | ||||
| import kr.co.vividnext.sodalive.common.SodaException | ||||
|  | ||||
| data class UpdateAuditionRoleRequest( | ||||
|     val id: Long, | ||||
|     val name: String? = null, | ||||
|     val auditionScriptUrl: String? = null, | ||||
|     val isActive: Boolean? = null | ||||
| ) { | ||||
|     init { | ||||
|         if (id < 0) { | ||||
|             throw SodaException("잘못된 요청입니다.") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -8,12 +8,13 @@ import javax.persistence.ManyToOne | ||||
|  | ||||
| @Entity | ||||
| data class AuditionRole( | ||||
|     val name: String, | ||||
|     val imagePath: String, | ||||
|     var name: String, | ||||
|     // 오디션 대본 URL | ||||
|     val auditionScriptUrl: String, | ||||
|     val isActive: Boolean | ||||
|     var auditionScriptUrl: String? = null | ||||
| ) : BaseEntity() { | ||||
|     var isActive: Boolean = true | ||||
|     var imagePath: String? = null | ||||
|  | ||||
|     @ManyToOne(fetch = FetchType.LAZY) | ||||
|     @JoinColumn(name = "audition_id", nullable = false) | ||||
|     var audition: Audition? = null | ||||
|   | ||||
		Reference in New Issue
	
	Block a user