Compare commits
No commits in common. "12a35db6cd9879497c99d147887baa35cd7595ac" and "9abbb05ad8ec8b332f6f11b97ff348d694b33eb2" have entirely different histories.
12a35db6cd
...
9abbb05ad8
|
@ -1,43 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
|
||||||
import org.springframework.data.domain.Pageable
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.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
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
@RequestMapping("/admin/audition")
|
|
||||||
class AdminAuditionController(private val service: AdminAuditionService) {
|
|
||||||
@PostMapping
|
|
||||||
fun createAudition(
|
|
||||||
@RequestPart("image") image: MultipartFile,
|
|
||||||
@RequestPart("request") requestString: String
|
|
||||||
) = ApiResponse.ok(service.createAudition(image, requestString), "등록되었습니다.")
|
|
||||||
|
|
||||||
@PutMapping
|
|
||||||
fun updateAudition(
|
|
||||||
@RequestPart("image", required = false) image: MultipartFile? = null,
|
|
||||||
@RequestPart("request") requestString: String
|
|
||||||
) = ApiResponse.ok(service.updateAudition(image, requestString), "수정되었습니다.")
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
fun getAuditionList(pageable: Pageable) = ApiResponse.ok(
|
|
||||||
service.getAuditionList(
|
|
||||||
offset = pageable.offset,
|
|
||||||
limit = pageable.pageSize.toLong()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
fun getAuditionDetail(@PathVariable id: Long) = ApiResponse.ok(
|
|
||||||
service.getAuditionDetail(auditionId = id)
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.audition.Audition
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAudition.audition
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
import org.springframework.stereotype.Repository
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
interface AdminAuditionRepository : JpaRepository<Audition, Long>, AdminAuditionQueryRepository
|
|
||||||
|
|
||||||
interface AdminAuditionQueryRepository {
|
|
||||||
fun getAuditionList(offset: Long, limit: Long): List<GetAuditionListItem>
|
|
||||||
fun getAuditionListCount(): Int
|
|
||||||
fun getAuditionDetail(auditionId: Long): GetAuditionDetailRawData
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdminAuditionQueryRepositoryImpl(
|
|
||||||
private val queryFactory: JPAQueryFactory,
|
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
|
||||||
private val coverImageHost: String
|
|
||||||
) : AdminAuditionQueryRepository {
|
|
||||||
override fun getAuditionList(offset: Long, limit: Long): List<GetAuditionListItem> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionListItem(
|
|
||||||
audition.id,
|
|
||||||
audition.title,
|
|
||||||
audition.imagePath.prepend("/").prepend(coverImageHost),
|
|
||||||
audition.isAdult,
|
|
||||||
audition.information,
|
|
||||||
audition.status,
|
|
||||||
audition.originalWorkUrl.coalesce("")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(audition)
|
|
||||||
.where(audition.isActive.isTrue)
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.orderBy(audition.isActive.desc(), audition.id.desc())
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionListCount(): Int {
|
|
||||||
return queryFactory
|
|
||||||
.select(audition.id)
|
|
||||||
.from(audition)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionDetail(auditionId: Long): GetAuditionDetailRawData {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionDetailRawData(
|
|
||||||
audition.id,
|
|
||||||
audition.title,
|
|
||||||
audition.imagePath.prepend("/").prepend(coverImageHost),
|
|
||||||
audition.information,
|
|
||||||
audition.originalWorkUrl.coalesce("")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(audition)
|
|
||||||
.where(audition.id.eq(auditionId))
|
|
||||||
.fetchFirst()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import kr.co.vividnext.sodalive.admin.audition.role.AdminAuditionRoleRepository
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
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 AdminAuditionService(
|
|
||||||
private val s3Uploader: S3Uploader,
|
|
||||||
private val objectMapper: ObjectMapper,
|
|
||||||
private val repository: AdminAuditionRepository,
|
|
||||||
private val roleRepository: AdminAuditionRoleRepository,
|
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.bucket}")
|
|
||||||
private val bucket: String
|
|
||||||
) {
|
|
||||||
@Transactional
|
|
||||||
fun createAudition(image: MultipartFile, requestString: String) {
|
|
||||||
val request = objectMapper.readValue(requestString, CreateAuditionRequest::class.java)
|
|
||||||
val audition = repository.save(request.toAudition())
|
|
||||||
|
|
||||||
val fileName = generateFileName("audition")
|
|
||||||
val imagePath = s3Uploader.upload(
|
|
||||||
inputStream = image.inputStream,
|
|
||||||
bucket = bucket,
|
|
||||||
filePath = "audition/production/${audition.id}/$fileName"
|
|
||||||
)
|
|
||||||
audition.imagePath = imagePath
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
fun updateAudition(image: MultipartFile?, requestString: String) {
|
|
||||||
val request = objectMapper.readValue(requestString, UpdateAuditionRequest::class.java)
|
|
||||||
val audition = repository.findByIdOrNull(id = request.id)
|
|
||||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
|
||||||
|
|
||||||
if (request.title != null) {
|
|
||||||
audition.title = request.title
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.information != null) {
|
|
||||||
audition.information = request.information
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.isAdult != null) {
|
|
||||||
audition.isAdult = request.isAdult
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.status != null) {
|
|
||||||
if (
|
|
||||||
(audition.status == AuditionStatus.COMPLETED || audition.status == AuditionStatus.IN_PROGRESS) &&
|
|
||||||
request.status == AuditionStatus.NOT_STARTED
|
|
||||||
) {
|
|
||||||
throw SodaException("모집전 상태로 변경할 수 없습니다.")
|
|
||||||
}
|
|
||||||
|
|
||||||
audition.status = request.status
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.originalWorkUrl != null) {
|
|
||||||
audition.originalWorkUrl = request.originalWorkUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image != null) {
|
|
||||||
val fileName = generateFileName("audition")
|
|
||||||
val imagePath = s3Uploader.upload(
|
|
||||||
inputStream = image.inputStream,
|
|
||||||
bucket = bucket,
|
|
||||||
filePath = "audition/production/${audition.id}/$fileName"
|
|
||||||
)
|
|
||||||
audition.imagePath = imagePath
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.isActive != null) {
|
|
||||||
audition.isActive = request.isActive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAuditionList(offset: Long, limit: Long): GetAuditionListResponse {
|
|
||||||
val totalCount = repository.getAuditionListCount()
|
|
||||||
val items = repository.getAuditionList(offset = offset, limit = limit)
|
|
||||||
return GetAuditionListResponse(totalCount, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAuditionDetail(auditionId: Long): GetAuditionDetailResponse {
|
|
||||||
val auditionDetail = repository.getAuditionDetail(auditionId = auditionId)
|
|
||||||
val roleList = roleRepository.getAuditionRoleListByAuditionId(auditionId = auditionId)
|
|
||||||
|
|
||||||
return GetAuditionDetailResponse(
|
|
||||||
id = auditionDetail.id,
|
|
||||||
title = auditionDetail.title,
|
|
||||||
imageUrl = auditionDetail.imageUrl,
|
|
||||||
information = auditionDetail.information,
|
|
||||||
originalWorkUrl = auditionDetail.originalWorkUrl,
|
|
||||||
roleList = roleList
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.Audition
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
|
||||||
|
|
||||||
data class CreateAuditionRequest(
|
|
||||||
val title: String,
|
|
||||||
val information: String,
|
|
||||||
val isAdult: Boolean = false,
|
|
||||||
val originalWorkUrl: String? = null
|
|
||||||
) {
|
|
||||||
init {
|
|
||||||
if (title.isBlank()) {
|
|
||||||
throw SodaException("오디션 제목을 입력하세요")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (information.isBlank() || information.length < 10) {
|
|
||||||
throw SodaException("오디션 정보는 최소 10글자 입니다")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toAudition(): Audition {
|
|
||||||
return Audition(
|
|
||||||
title = title,
|
|
||||||
information = information,
|
|
||||||
isAdult = isAdult,
|
|
||||||
originalWorkUrl = originalWorkUrl
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
|
|
||||||
data class GetAuditionDetailRawData @QueryProjection constructor(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val information: String,
|
|
||||||
val originalWorkUrl: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionDetailResponse(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val information: String,
|
|
||||||
val originalWorkUrl: String,
|
|
||||||
val roleList: List<GetAuditionRoleListData>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionRoleListData @QueryProjection constructor(
|
|
||||||
val id: Long,
|
|
||||||
val name: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val information: String,
|
|
||||||
val auditionScriptUrl: String,
|
|
||||||
val status: AuditionStatus
|
|
||||||
)
|
|
|
@ -1,19 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
|
|
||||||
data class GetAuditionListResponse(
|
|
||||||
val totalCount: Int,
|
|
||||||
val items: List<GetAuditionListItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionListItem @QueryProjection constructor(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val isAdult: Boolean,
|
|
||||||
val information: String,
|
|
||||||
val status: AuditionStatus,
|
|
||||||
val originalWorkUrl: String
|
|
||||||
)
|
|
|
@ -1,13 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
|
|
||||||
data class UpdateAuditionRequest(
|
|
||||||
val id: Long,
|
|
||||||
val title: String? = null,
|
|
||||||
val information: String? = null,
|
|
||||||
val isAdult: Boolean? = null,
|
|
||||||
val status: AuditionStatus? = null,
|
|
||||||
val originalWorkUrl: String? = null,
|
|
||||||
val isActive: Boolean? = null
|
|
||||||
)
|
|
|
@ -1,47 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition.role
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
|
||||||
import org.springframework.data.domain.Pageable
|
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.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
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
|
||||||
@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()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition.role
|
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.admin.audition.GetAuditionRoleListData
|
|
||||||
import kr.co.vividnext.sodalive.admin.audition.QGetAuditionRoleListData
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionRole
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAudition.audition
|
|
||||||
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 getAuditionRoleListByAuditionId(auditionId: Long): List<GetAuditionRoleListData>
|
|
||||||
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 getAuditionRoleListByAuditionId(auditionId: Long): List<GetAuditionRoleListData> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionRoleListData(
|
|
||||||
auditionRole.id,
|
|
||||||
auditionRole.name,
|
|
||||||
auditionRole.imagePath.prepend("/").prepend(cloudfrontHost),
|
|
||||||
auditionRole.information,
|
|
||||||
auditionRole.auditionScriptUrl,
|
|
||||||
auditionRole.status
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(auditionRole)
|
|
||||||
.innerJoin(auditionRole.audition, audition)
|
|
||||||
.where(
|
|
||||||
auditionRole.audition.id.eq(auditionId),
|
|
||||||
auditionRole.isActive.isTrue
|
|
||||||
)
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailResponse {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionRoleDetailResponse(
|
|
||||||
auditionRole.name,
|
|
||||||
auditionRole.imagePath.prepend("/").prepend(cloudfrontHost),
|
|
||||||
auditionRole.information,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
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,
|
|
||||||
information = request.information,
|
|
||||||
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()) {
|
|
||||||
if (request.name.length < 2) throw SodaException("배역 이름은 최소 2글자 입니다")
|
|
||||||
auditionRole.name = request.name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.information.isNullOrBlank()) {
|
|
||||||
if (request.information.length < 10) throw SodaException("오디션 배역 정보는 최소 10글자 입니다")
|
|
||||||
auditionRole.information = request.information
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!request.auditionScriptUrl.isNullOrBlank()) {
|
|
||||||
auditionRole.auditionScriptUrl = request.auditionScriptUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.status != null) {
|
|
||||||
auditionRole.status = request.status
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
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 information: 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을 입력하세요")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (information.isBlank() || information.length < 10) {
|
|
||||||
throw SodaException("오디션 캐릭터 정보는 최소 10글자 입니다")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
|
@ -1,10 +0,0 @@
|
||||||
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 information: String,
|
|
||||||
val auditionScriptUrl: String
|
|
||||||
)
|
|
|
@ -1,19 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.admin.audition.role
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
|
||||||
|
|
||||||
data class UpdateAuditionRoleRequest(
|
|
||||||
val id: Long,
|
|
||||||
val name: String? = null,
|
|
||||||
val information: String? = null,
|
|
||||||
val auditionScriptUrl: String? = null,
|
|
||||||
val status: AuditionStatus? = null,
|
|
||||||
val isActive: Boolean? = null
|
|
||||||
) {
|
|
||||||
init {
|
|
||||||
if (id < 0) {
|
|
||||||
throw SodaException("잘못된 요청입니다.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
|
||||||
import javax.persistence.Column
|
|
||||||
import javax.persistence.Entity
|
|
||||||
import javax.persistence.EnumType
|
|
||||||
import javax.persistence.Enumerated
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class Audition(
|
|
||||||
var title: String,
|
|
||||||
@Column(columnDefinition = "TEXT")
|
|
||||||
var information: String,
|
|
||||||
var isAdult: Boolean = false,
|
|
||||||
// 원작 URL
|
|
||||||
var originalWorkUrl: String? = null,
|
|
||||||
@Enumerated(value = EnumType.STRING)
|
|
||||||
var status: AuditionStatus = AuditionStatus.NOT_STARTED
|
|
||||||
) : BaseEntity() {
|
|
||||||
var isActive: Boolean = true
|
|
||||||
var imagePath: String? = null
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
|
||||||
import javax.persistence.Entity
|
|
||||||
import javax.persistence.FetchType
|
|
||||||
import javax.persistence.JoinColumn
|
|
||||||
import javax.persistence.ManyToOne
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class AuditionApplicant(
|
|
||||||
val phoneNumber: String,
|
|
||||||
var voicePath: String? = null,
|
|
||||||
var isActive: Boolean = true
|
|
||||||
) : BaseEntity() {
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "role_id", nullable = false)
|
|
||||||
var role: AuditionRole? = null
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "member_id", nullable = false)
|
|
||||||
var member: Member? = null
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
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.PathVariable
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/audition")
|
|
||||||
class AuditionController(private val service: AuditionService) {
|
|
||||||
@GetMapping
|
|
||||||
fun getAuditionList(
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
|
|
||||||
pageable: Pageable
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
|
|
||||||
ApiResponse.ok(
|
|
||||||
service.getAuditionList(
|
|
||||||
offset = pageable.offset,
|
|
||||||
limit = pageable.pageSize.toLong(),
|
|
||||||
isAdult = member.auth != null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
fun getAuditionDetail(
|
|
||||||
@PathVariable id: Long,
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
|
|
||||||
ApiResponse.ok(service.getAuditionDetail(auditionId = id))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAudition.audition
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
|
|
||||||
interface AuditionRepository : JpaRepository<Audition, Long>, AuditionQueryRepository
|
|
||||||
|
|
||||||
interface AuditionQueryRepository {
|
|
||||||
fun getInProgressAuditionCount(isAdult: Boolean): Int
|
|
||||||
fun getCompletedAuditionCount(isAdult: Boolean): Int
|
|
||||||
fun getAuditionList(offset: Long, limit: Long, isAdult: Boolean): List<GetAuditionListItem>
|
|
||||||
fun getAuditionDetail(auditionId: Long): GetAuditionDetailRawData
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuditionQueryRepositoryImpl(
|
|
||||||
private val queryFactory: JPAQueryFactory,
|
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
|
||||||
private val cloudFrontHost: String
|
|
||||||
) : AuditionQueryRepository {
|
|
||||||
override fun getInProgressAuditionCount(isAdult: Boolean): Int {
|
|
||||||
var where = audition.isActive.isTrue
|
|
||||||
.and(audition.status.eq(AuditionStatus.IN_PROGRESS))
|
|
||||||
|
|
||||||
if (!isAdult) {
|
|
||||||
where = where.and(audition.isAdult.isFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryFactory
|
|
||||||
.select(audition.id)
|
|
||||||
.from(audition)
|
|
||||||
.where(where)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCompletedAuditionCount(isAdult: Boolean): Int {
|
|
||||||
var where = audition.isActive.isTrue
|
|
||||||
.and(audition.status.eq(AuditionStatus.COMPLETED))
|
|
||||||
|
|
||||||
if (!isAdult) {
|
|
||||||
where = where.and(audition.isAdult.isFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryFactory
|
|
||||||
.select(audition.id)
|
|
||||||
.from(audition)
|
|
||||||
.where(where)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionList(offset: Long, limit: Long, isAdult: Boolean): List<GetAuditionListItem> {
|
|
||||||
var where = audition.isActive.isTrue
|
|
||||||
.and(
|
|
||||||
audition.status.eq(AuditionStatus.COMPLETED)
|
|
||||||
.or(audition.status.eq(AuditionStatus.IN_PROGRESS))
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!isAdult) {
|
|
||||||
where = where.and(audition.isAdult.isFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionListItem(
|
|
||||||
audition.id,
|
|
||||||
audition.title,
|
|
||||||
audition.imagePath.prepend("/").prepend(cloudFrontHost),
|
|
||||||
audition.status.eq(AuditionStatus.COMPLETED)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(audition)
|
|
||||||
.where(where)
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.orderBy(audition.status.desc())
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionDetail(auditionId: Long): GetAuditionDetailRawData {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionDetailRawData(
|
|
||||||
audition.id,
|
|
||||||
audition.title,
|
|
||||||
audition.imagePath.prepend("/").prepend(cloudFrontHost),
|
|
||||||
audition.information
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(audition)
|
|
||||||
.where(audition.id.eq(auditionId))
|
|
||||||
.fetchFirst()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
|
||||||
import javax.persistence.Column
|
|
||||||
import javax.persistence.Entity
|
|
||||||
import javax.persistence.EnumType
|
|
||||||
import javax.persistence.Enumerated
|
|
||||||
import javax.persistence.FetchType
|
|
||||||
import javax.persistence.JoinColumn
|
|
||||||
import javax.persistence.ManyToOne
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
data class AuditionRole(
|
|
||||||
var name: String,
|
|
||||||
@Column(columnDefinition = "TEXT")
|
|
||||||
var information: String,
|
|
||||||
// 오디션 대본 URL
|
|
||||||
var auditionScriptUrl: String? = null,
|
|
||||||
@Enumerated(value = EnumType.STRING)
|
|
||||||
var status: AuditionStatus = AuditionStatus.IN_PROGRESS
|
|
||||||
) : BaseEntity() {
|
|
||||||
var isActive: Boolean = true
|
|
||||||
var imagePath: String? = null
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "audition_id", nullable = false)
|
|
||||||
var audition: Audition? = null
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.role.AuditionRoleRepository
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class AuditionService(
|
|
||||||
private val repository: AuditionRepository,
|
|
||||||
private val roleRepository: AuditionRoleRepository
|
|
||||||
) {
|
|
||||||
fun getAuditionList(offset: Long, limit: Long, isAdult: Boolean): GetAuditionListResponse {
|
|
||||||
val inProgressCount = repository.getInProgressAuditionCount(isAdult = isAdult)
|
|
||||||
val completedCount = repository.getCompletedAuditionCount(isAdult = isAdult)
|
|
||||||
val items = repository.getAuditionList(offset = offset, limit = limit, isAdult = isAdult)
|
|
||||||
|
|
||||||
return GetAuditionListResponse(inProgressCount, completedCount, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAuditionDetail(auditionId: Long): GetAuditionDetailResponse {
|
|
||||||
val auditionDetail = repository.getAuditionDetail(auditionId = auditionId)
|
|
||||||
val roleList = roleRepository.getAuditionRoleListByAuditionId(auditionId = auditionId)
|
|
||||||
|
|
||||||
return GetAuditionDetailResponse(
|
|
||||||
auditionId = auditionId,
|
|
||||||
title = auditionDetail.title,
|
|
||||||
imageUrl = auditionDetail.imageUrl,
|
|
||||||
information = auditionDetail.information,
|
|
||||||
roleList = roleList
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
enum class AuditionStatus {
|
|
||||||
NOT_STARTED,
|
|
||||||
IN_PROGRESS,
|
|
||||||
COMPLETED
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
|
||||||
import javax.persistence.Entity
|
|
||||||
import javax.persistence.JoinColumn
|
|
||||||
import javax.persistence.ManyToOne
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
class AuditionVote : BaseEntity() {
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "member_id", nullable = false)
|
|
||||||
var member: Member? = null
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "applicant_id", nullable = false)
|
|
||||||
var applicant: AuditionApplicant? = null
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
|
|
||||||
data class GetAuditionDetailRawData @QueryProjection constructor(
|
|
||||||
val auditionId: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val information: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionDetailResponse(
|
|
||||||
val auditionId: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val information: String,
|
|
||||||
val roleList: List<GetAuditionRoleListData>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionRoleListData @QueryProjection constructor(
|
|
||||||
val roleId: Long,
|
|
||||||
val name: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val isComplete: Boolean
|
|
||||||
)
|
|
|
@ -1,16 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
|
|
||||||
data class GetAuditionListResponse(
|
|
||||||
val inProgressCount: Int,
|
|
||||||
val completedCount: Int,
|
|
||||||
val items: List<GetAuditionListItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionListItem @QueryProjection constructor(
|
|
||||||
val id: Long,
|
|
||||||
val title: String,
|
|
||||||
val imageUrl: String,
|
|
||||||
val isOff: Boolean
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.applicant
|
|
||||||
|
|
||||||
data class ApplyAuditionRoleRequest(
|
|
||||||
val roleId: Long,
|
|
||||||
val phoneNumber: String
|
|
||||||
)
|
|
|
@ -1,55 +0,0 @@
|
||||||
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.PostMapping
|
|
||||||
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
|
|
||||||
@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()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
fun applyAuditionRole(
|
|
||||||
@RequestPart("contentFile")
|
|
||||||
contentFile: MultipartFile?,
|
|
||||||
@RequestPart("request") requestString: String,
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
|
|
||||||
ApiResponse.ok(
|
|
||||||
service.applyAuditionRole(
|
|
||||||
contentFile = contentFile,
|
|
||||||
requestString = requestString,
|
|
||||||
member = member
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
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<AuditionApplicant, Long>, 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<GetAuditionRoleApplicantItem>
|
|
||||||
|
|
||||||
fun findActiveApplicantByMemberIdAndRoleId(memberId: Long, roleId: Long): AuditionApplicant?
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
auditionApplicant.isActive.isTrue
|
|
||||||
)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAuditionApplicantList(
|
|
||||||
auditionRoleId: Long,
|
|
||||||
sortType: AuditionApplicantSortType,
|
|
||||||
offset: Long,
|
|
||||||
limit: Long
|
|
||||||
): List<GetAuditionRoleApplicantItem> {
|
|
||||||
val orderBy = if (sortType == AuditionApplicantSortType.LIKES) {
|
|
||||||
auditionVote.id.count().desc()
|
|
||||||
} else {
|
|
||||||
auditionApplicant.id.desc()
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionRoleApplicantItem(
|
|
||||||
auditionApplicant.id,
|
|
||||||
member.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),
|
|
||||||
auditionApplicant.isActive.isTrue
|
|
||||||
)
|
|
||||||
.groupBy(auditionApplicant.id)
|
|
||||||
.orderBy(orderBy)
|
|
||||||
.offset(offset)
|
|
||||||
.limit(limit)
|
|
||||||
.fetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findActiveApplicantByMemberIdAndRoleId(memberId: Long, roleId: Long): AuditionApplicant? {
|
|
||||||
return queryFactory
|
|
||||||
.selectFrom(auditionApplicant)
|
|
||||||
.where(
|
|
||||||
auditionApplicant.isActive.isTrue
|
|
||||||
.and(auditionApplicant.member.id.eq(memberId))
|
|
||||||
.and(auditionApplicant.role.id.eq(roleId))
|
|
||||||
)
|
|
||||||
.fetchFirst()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.applicant
|
|
||||||
|
|
||||||
import com.amazonaws.services.s3.model.ObjectMetadata
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
|
||||||
import kr.co.vividnext.sodalive.audition.role.AuditionRoleRepository
|
|
||||||
import kr.co.vividnext.sodalive.aws.s3.S3Uploader
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
|
||||||
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 AuditionApplicantService(
|
|
||||||
private val repository: AuditionApplicantRepository,
|
|
||||||
private val roleRepository: AuditionRoleRepository,
|
|
||||||
|
|
||||||
private val s3Uploader: S3Uploader,
|
|
||||||
private val objectMapper: ObjectMapper,
|
|
||||||
|
|
||||||
@Value("\${cloud.aws.s3.bucket}")
|
|
||||||
private val bucket: String
|
|
||||||
) {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
fun applyAuditionRole(contentFile: MultipartFile?, requestString: String, member: Member) {
|
|
||||||
if (contentFile == null) throw SodaException("녹음 파일을 확인해 주세요.")
|
|
||||||
val request = objectMapper.readValue(requestString, ApplyAuditionRoleRequest::class.java)
|
|
||||||
|
|
||||||
val auditionRole = roleRepository.findByIdOrNull(id = request.roleId)
|
|
||||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
|
||||||
|
|
||||||
val existingApplicant = repository.findActiveApplicantByMemberIdAndRoleId(
|
|
||||||
memberId = member.id!!,
|
|
||||||
roleId = auditionRole.id!!
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existingApplicant != null) {
|
|
||||||
existingApplicant.isActive = false
|
|
||||||
repository.save(existingApplicant)
|
|
||||||
}
|
|
||||||
|
|
||||||
val applicant = AuditionApplicant(phoneNumber = request.phoneNumber)
|
|
||||||
applicant.role = auditionRole
|
|
||||||
applicant.member = member
|
|
||||||
repository.save(applicant)
|
|
||||||
|
|
||||||
val contentFileName = generateFileName(prefix = "${applicant.id}-applicant")
|
|
||||||
|
|
||||||
val metadata = ObjectMetadata()
|
|
||||||
metadata.contentLength = contentFile.size
|
|
||||||
|
|
||||||
val contentPath = s3Uploader.upload(
|
|
||||||
inputStream = contentFile.inputStream,
|
|
||||||
bucket = bucket,
|
|
||||||
filePath = "audition/applicant/${applicant.id}/$contentFileName",
|
|
||||||
metadata = metadata
|
|
||||||
)
|
|
||||||
applicant.voicePath = contentPath
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.applicant
|
|
||||||
|
|
||||||
enum class AuditionApplicantSortType {
|
|
||||||
NEWEST, LIKES
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.applicant
|
|
||||||
|
|
||||||
import com.querydsl.core.annotations.QueryProjection
|
|
||||||
|
|
||||||
data class GetAuditionApplicantListResponse(
|
|
||||||
val totalCount: Int,
|
|
||||||
val items: List<GetAuditionRoleApplicantItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GetAuditionRoleApplicantItem @QueryProjection constructor(
|
|
||||||
val applicantId: Long,
|
|
||||||
val memberId: Long,
|
|
||||||
val nickname: String,
|
|
||||||
val profileImageUrl: String,
|
|
||||||
val voiceUrl: String,
|
|
||||||
val voteCount: Long
|
|
||||||
)
|
|
|
@ -1,29 +0,0 @@
|
||||||
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!!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.role
|
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionRole
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionStatus
|
|
||||||
import kr.co.vividnext.sodalive.audition.GetAuditionRoleListData
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAudition.audition
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAuditionRole.auditionRole
|
|
||||||
import kr.co.vividnext.sodalive.audition.QGetAuditionRoleListData
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
|
|
||||||
interface AuditionRoleRepository : JpaRepository<AuditionRole, Long>, AuditionRoleQueryRepository
|
|
||||||
|
|
||||||
interface AuditionRoleQueryRepository {
|
|
||||||
fun getAuditionRoleListByAuditionId(auditionId: Long): List<GetAuditionRoleListData>
|
|
||||||
fun getAuditionRoleDetail(auditionRoleId: Long): GetAuditionRoleDetailData?
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuditionRoleQueryRepositoryImpl(
|
|
||||||
private val queryFactory: JPAQueryFactory,
|
|
||||||
|
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
|
||||||
private val cloudfrontHost: String
|
|
||||||
) : AuditionRoleQueryRepository {
|
|
||||||
override fun getAuditionRoleListByAuditionId(auditionId: Long): List<GetAuditionRoleListData> {
|
|
||||||
return queryFactory
|
|
||||||
.select(
|
|
||||||
QGetAuditionRoleListData(
|
|
||||||
auditionRole.id,
|
|
||||||
auditionRole.name,
|
|
||||||
auditionRole.imagePath.prepend("/").prepend(cloudfrontHost),
|
|
||||||
auditionRole.status.eq(AuditionStatus.COMPLETED)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.from(auditionRole)
|
|
||||||
.innerJoin(auditionRole.audition, audition)
|
|
||||||
.where(
|
|
||||||
audition.id.eq(auditionId),
|
|
||||||
auditionRole.isActive.isTrue
|
|
||||||
)
|
|
||||||
.orderBy(auditionRole.status.desc(), auditionRole.id.desc())
|
|
||||||
.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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.vote
|
|
||||||
|
|
||||||
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.PostMapping
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import org.springframework.web.bind.annotation.RestController
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/audition/vote")
|
|
||||||
class AuditionVoteController(
|
|
||||||
private val service: AuditionVoteService
|
|
||||||
) {
|
|
||||||
@PostMapping
|
|
||||||
fun voteAuditionApplicant(
|
|
||||||
@RequestBody request: VoteAuditionApplicantRequest,
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
|
|
||||||
ApiResponse.ok(
|
|
||||||
service.voteAuditionApplicant(
|
|
||||||
applicantId = request.applicantId,
|
|
||||||
timezone = request.timezone,
|
|
||||||
container = request.container,
|
|
||||||
member = member
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.vote
|
|
||||||
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionVote
|
|
||||||
import kr.co.vividnext.sodalive.audition.QAuditionVote.auditionVote
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
|
|
||||||
interface AuditionVoteRepository : JpaRepository<AuditionVote, Long>, AuditionVoteQueryRepository
|
|
||||||
|
|
||||||
interface AuditionVoteQueryRepository {
|
|
||||||
fun countByMemberIdAndVoteDateRange(
|
|
||||||
memberId: Long,
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime
|
|
||||||
): Int
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuditionVoteQueryRepositoryImpl(
|
|
||||||
private val queryFactory: JPAQueryFactory
|
|
||||||
) : AuditionVoteQueryRepository {
|
|
||||||
override fun countByMemberIdAndVoteDateRange(
|
|
||||||
memberId: Long,
|
|
||||||
startDate: LocalDateTime,
|
|
||||||
endDate: LocalDateTime
|
|
||||||
): Int {
|
|
||||||
return queryFactory
|
|
||||||
.select(auditionVote.id)
|
|
||||||
.from(auditionVote)
|
|
||||||
.where(
|
|
||||||
auditionVote.member.id.eq(memberId)
|
|
||||||
.and(auditionVote.createdAt.between(startDate, endDate))
|
|
||||||
)
|
|
||||||
.fetch()
|
|
||||||
.size
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.vote
|
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionVote
|
|
||||||
import kr.co.vividnext.sodalive.audition.applicant.AuditionApplicantRepository
|
|
||||||
import kr.co.vividnext.sodalive.can.payment.CanPaymentService
|
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import java.time.ZoneId
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class AuditionVoteService(
|
|
||||||
private val repository: AuditionVoteRepository,
|
|
||||||
private val applicantRepository: AuditionApplicantRepository,
|
|
||||||
private val canPaymentService: CanPaymentService
|
|
||||||
) {
|
|
||||||
fun voteAuditionApplicant(applicantId: Long, timezone: String, container: String, member: Member) {
|
|
||||||
val applicant = applicantRepository.findByIdOrNull(applicantId)
|
|
||||||
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
|
|
||||||
|
|
||||||
val defaultZoneId = ZoneId.of("Asia/Seoul")
|
|
||||||
val clientZoneId = try {
|
|
||||||
ZoneId.of(timezone)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultZoneId
|
|
||||||
}
|
|
||||||
|
|
||||||
val nowInClientZone = ZonedDateTime.now(clientZoneId)
|
|
||||||
val startOfDayClient = nowInClientZone.toLocalDate().atStartOfDay(clientZoneId)
|
|
||||||
val endOfDayClient = startOfDayClient.plusDays(1).minusSeconds(1)
|
|
||||||
|
|
||||||
val startDate = startOfDayClient.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
|
||||||
val endDate = endOfDayClient.withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
|
|
||||||
|
|
||||||
val voteCount = repository.countByMemberIdAndVoteDateRange(
|
|
||||||
memberId = member.id!!,
|
|
||||||
startDate = startDate,
|
|
||||||
endDate = endDate
|
|
||||||
)
|
|
||||||
|
|
||||||
if (voteCount > 10) {
|
|
||||||
throw SodaException("오늘 응원은 여기까지!\n하루 최대 10회까지 응원이 가능합니다.\n내일 다시 이용해주세요.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (voteCount > 0) {
|
|
||||||
canPaymentService.spendCan(
|
|
||||||
memberId = member.id!!,
|
|
||||||
needCan = 1,
|
|
||||||
canUsage = CanUsage.AUDITION_VOTE,
|
|
||||||
auditionApplicant = applicant,
|
|
||||||
container = container
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val auditionVote = AuditionVote()
|
|
||||||
auditionVote.applicant = applicant
|
|
||||||
auditionVote.member = member
|
|
||||||
repository.save(auditionVote)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package kr.co.vividnext.sodalive.audition.vote
|
|
||||||
|
|
||||||
data class VoteAuditionApplicantRequest(
|
|
||||||
val applicantId: Long,
|
|
||||||
val timezone: String,
|
|
||||||
val container: String
|
|
||||||
)
|
|
|
@ -71,7 +71,6 @@ class CanService(private val repository: CanRepository) {
|
||||||
CanUsage.ALARM_SLOT -> "알람 슬롯 구매"
|
CanUsage.ALARM_SLOT -> "알람 슬롯 구매"
|
||||||
CanUsage.ORDER_CONTENT -> "[콘텐츠 구매] ${it.audioContent!!.title}"
|
CanUsage.ORDER_CONTENT -> "[콘텐츠 구매] ${it.audioContent!!.title}"
|
||||||
CanUsage.PAID_COMMUNITY_POST -> "[게시글 보기] ${it.communityPost?.member?.nickname ?: ""}"
|
CanUsage.PAID_COMMUNITY_POST -> "[게시글 보기] ${it.communityPost?.member?.nickname ?: ""}"
|
||||||
CanUsage.AUDITION_VOTE -> "[오디션 투표] ${it.auditionApplicant?.role?.audition?.title ?: ""}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val createdAt = it.createdAt!!
|
val createdAt = it.createdAt!!
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package kr.co.vividnext.sodalive.can.payment
|
package kr.co.vividnext.sodalive.can.payment
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
|
||||||
import kr.co.vividnext.sodalive.can.CanRepository
|
import kr.co.vividnext.sodalive.can.CanRepository
|
||||||
import kr.co.vividnext.sodalive.can.charge.Charge
|
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||||
|
@ -42,7 +41,6 @@ class CanPaymentService(
|
||||||
order: Order? = null,
|
order: Order? = null,
|
||||||
audioContent: AudioContent? = null,
|
audioContent: AudioContent? = null,
|
||||||
communityPost: CreatorCommunity? = null,
|
communityPost: CreatorCommunity? = null,
|
||||||
auditionApplicant: AuditionApplicant? = null,
|
|
||||||
container: String
|
container: String
|
||||||
) {
|
) {
|
||||||
val member = memberRepository.findByIdOrNull(id = memberId)
|
val member = memberRepository.findByIdOrNull(id = memberId)
|
||||||
|
@ -102,9 +100,6 @@ class CanPaymentService(
|
||||||
useCan.member = member
|
useCan.member = member
|
||||||
} else if (canUsage == CanUsage.ALARM_SLOT) {
|
} else if (canUsage == CanUsage.ALARM_SLOT) {
|
||||||
useCan.member = member
|
useCan.member = member
|
||||||
} else if (canUsage == CanUsage.AUDITION_VOTE && auditionApplicant != null) {
|
|
||||||
useCan.auditionApplicant = auditionApplicant
|
|
||||||
useCan.member = member
|
|
||||||
} else if (canUsage == CanUsage.HEART && liveRoom != null) {
|
} else if (canUsage == CanUsage.HEART && liveRoom != null) {
|
||||||
recipientId = liveRoom.member!!.id!!
|
recipientId = liveRoom.member!!.id!!
|
||||||
useCan.room = liveRoom
|
useCan.room = liveRoom
|
||||||
|
|
|
@ -8,6 +8,5 @@ enum class CanUsage {
|
||||||
ORDER_CONTENT,
|
ORDER_CONTENT,
|
||||||
SPIN_ROULETTE,
|
SPIN_ROULETTE,
|
||||||
PAID_COMMUNITY_POST,
|
PAID_COMMUNITY_POST,
|
||||||
ALARM_SLOT,
|
ALARM_SLOT
|
||||||
AUDITION_VOTE
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package kr.co.vividnext.sodalive.can.use
|
package kr.co.vividnext.sodalive.can.use
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.audition.AuditionApplicant
|
|
||||||
import kr.co.vividnext.sodalive.common.BaseEntity
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
import kr.co.vividnext.sodalive.content.AudioContent
|
import kr.co.vividnext.sodalive.content.AudioContent
|
||||||
import kr.co.vividnext.sodalive.content.order.Order
|
import kr.co.vividnext.sodalive.content.order.Order
|
||||||
|
@ -54,10 +53,6 @@ data class UseCan(
|
||||||
@JoinColumn(name = "creator_community_id", nullable = true)
|
@JoinColumn(name = "creator_community_id", nullable = true)
|
||||||
var communityPost: CreatorCommunity? = null
|
var communityPost: CreatorCommunity? = null
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "audition_applicant_id", nullable = true)
|
|
||||||
var auditionApplicant: AuditionApplicant? = null
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "useCan", cascade = [CascadeType.ALL])
|
@OneToMany(mappedBy = "useCan", cascade = [CascadeType.ALL])
|
||||||
val useCanCalculates: MutableList<UseCanCalculate> = mutableListOf()
|
val useCanCalculates: MutableList<UseCanCalculate> = mutableListOf()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,6 @@ import org.springframework.web.bind.annotation.RestController
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/explorer")
|
@RequestMapping("/explorer")
|
||||||
class ExplorerController(private val service: ExplorerService) {
|
class ExplorerController(private val service: ExplorerService) {
|
||||||
@GetMapping("/creator-rank")
|
|
||||||
fun getCreatorRank(
|
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
|
||||||
) = run {
|
|
||||||
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
|
||||||
|
|
||||||
ApiResponse.ok(service.getCreatorRank(memberId = member.id!!))
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getExplorer(
|
fun getExplorer(
|
||||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
|
|
@ -47,34 +47,6 @@ class ExplorerService(
|
||||||
@Value("\${cloud.aws.cloud-front.host}")
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
private val cloudFrontHost: String
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
fun getCreatorRank(memberId: Long): GetExplorerSectionResponse {
|
|
||||||
val creatorRankings = queryRepository
|
|
||||||
.getCreatorRankings()
|
|
||||||
.filter { !memberService.isBlocked(blockedMemberId = memberId, memberId = it.id!!) }
|
|
||||||
.map { it.toExplorerSectionCreator(cloudFrontHost) }
|
|
||||||
|
|
||||||
val currentDateTime = LocalDateTime.now()
|
|
||||||
val lastMonday = currentDateTime
|
|
||||||
.minusWeeks(1)
|
|
||||||
.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
|
|
||||||
val lastSunday = lastMonday
|
|
||||||
.plusDays(6)
|
|
||||||
|
|
||||||
val startDateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")
|
|
||||||
val endDateFormatter = DateTimeFormatter.ofPattern("MM월 dd일")
|
|
||||||
|
|
||||||
val formattedLastMonday = lastMonday.format(startDateFormatter)
|
|
||||||
val formattedLastSunday = lastSunday.format(endDateFormatter)
|
|
||||||
|
|
||||||
return GetExplorerSectionResponse(
|
|
||||||
title = "인기 크리에이터",
|
|
||||||
coloredTitle = "인기",
|
|
||||||
color = "FF5C49",
|
|
||||||
desc = "$formattedLastMonday ~ $formattedLastSunday",
|
|
||||||
creators = creatorRankings
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse {
|
fun getExplorer(member: Member, growthRankingCreatorsLimit: Long = 20): GetExplorerResponse {
|
||||||
val sections = mutableListOf<GetExplorerSectionResponse>()
|
val sections = mutableListOf<GetExplorerSectionResponse>()
|
||||||
|
|
||||||
|
@ -172,16 +144,12 @@ class ExplorerService(
|
||||||
val isBlocked = memberService.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
|
val isBlocked = memberService.isBlocked(blockedMemberId = member.id!!, memberId = creatorId)
|
||||||
if (isBlocked) throw SodaException("${creatorAccount.nickname}님의 요청으로 채널 접근이 제한됩니다.")
|
if (isBlocked) throw SodaException("${creatorAccount.nickname}님의 요청으로 채널 접근이 제한됩니다.")
|
||||||
|
|
||||||
val isCreator = creatorAccount.role == MemberRole.CREATOR
|
|
||||||
|
|
||||||
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
val notificationUserIds = queryRepository.getNotificationUserIds(creatorId)
|
||||||
val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!)
|
val creatorFollowing = queryRepository.getCreatorFollowing(creatorId = creatorId, memberId = member.id!!)
|
||||||
val notificationRecipientCount = notificationUserIds.size
|
val notificationRecipientCount = notificationUserIds.size
|
||||||
|
|
||||||
// 후원랭킹
|
// 후원랭킹
|
||||||
val memberDonationRanking = if (
|
val memberDonationRanking = if (creatorId == member.id!! || creatorAccount.isVisibleDonationRank) {
|
||||||
isCreator && (creatorId == member.id!! || creatorAccount.isVisibleDonationRank)
|
|
||||||
) {
|
|
||||||
queryRepository.getMemberDonationRanking(
|
queryRepository.getMemberDonationRanking(
|
||||||
creatorId,
|
creatorId,
|
||||||
10,
|
10,
|
||||||
|
@ -192,47 +160,30 @@ class ExplorerService(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 추천 크리에이터
|
// 추천 크리에이터
|
||||||
val similarCreatorList = if (isCreator) {
|
val similarCreatorList = queryRepository.getSimilarCreatorList(creatorId)
|
||||||
queryRepository.getSimilarCreatorList(creatorId)
|
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 라이브
|
// 라이브
|
||||||
val liveRoomList = if (isCreator) {
|
val liveRoomList = queryRepository.getLiveRoomList(
|
||||||
queryRepository.getLiveRoomList(
|
|
||||||
creatorId,
|
creatorId,
|
||||||
userMember = member,
|
userMember = member,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
limit = 3
|
limit = 3
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 오디오 콘텐츠
|
// 오디오 콘텐츠
|
||||||
val contentList = if (isCreator) {
|
val contentList = audioContentService.getAudioContentList(
|
||||||
audioContentService.getAudioContentList(
|
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
sortType = SortType.NEWEST,
|
sortType = SortType.NEWEST,
|
||||||
member = member,
|
member = member,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 3
|
limit = 3
|
||||||
).items
|
).items
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 공지사항
|
// 공지사항
|
||||||
val notice = if (isCreator) {
|
val notice = queryRepository.getNoticeString(creatorId)
|
||||||
queryRepository.getNoticeString(creatorId)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 커뮤니티
|
// 커뮤니티
|
||||||
val communityPostList = if (isCreator) {
|
val communityPostList = communityService.getCommunityPostList(
|
||||||
communityService.getCommunityPostList(
|
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
timezone = timezone,
|
timezone = timezone,
|
||||||
|
@ -240,9 +191,6 @@ class ExplorerService(
|
||||||
limit = 3,
|
limit = 3,
|
||||||
isAdult = member.auth != null
|
isAdult = member.auth != null
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 응원
|
// 응원
|
||||||
val cheers = queryRepository.getCheersList(creatorId, timezone = timezone, offset = 0, limit = 4)
|
val cheers = queryRepository.getCheersList(creatorId, timezone = timezone, offset = 0, limit = 4)
|
||||||
|
@ -250,29 +198,15 @@ class ExplorerService(
|
||||||
// 차단한 크리에이터 인지 체크
|
// 차단한 크리에이터 인지 체크
|
||||||
val isBlock = memberService.isBlocked(blockedMemberId = creatorId, memberId = member.id!!)
|
val isBlock = memberService.isBlocked(blockedMemberId = creatorId, memberId = member.id!!)
|
||||||
|
|
||||||
val activitySummary = if (isCreator) {
|
|
||||||
// 활동요약 (라이브 횟수, 라이브 시간, 라이브 참여자, 콘텐츠 수)
|
// 활동요약 (라이브 횟수, 라이브 시간, 라이브 참여자, 콘텐츠 수)
|
||||||
val liveCount = queryRepository.getLiveCount(creatorId) ?: 0
|
val liveCount = queryRepository.getLiveCount(creatorId) ?: 0
|
||||||
val liveTime = queryRepository.getLiveTime(creatorId)
|
val liveTime = queryRepository.getLiveTime(creatorId)
|
||||||
val liveContributorCount = queryRepository.getLiveContributorCount(creatorId) ?: 0
|
val liveContributorCount = queryRepository.getLiveContributorCount(creatorId) ?: 0
|
||||||
val contentCount = queryRepository.getContentCount(creatorId) ?: 0
|
val contentCount = queryRepository.getContentCount(creatorId) ?: 0
|
||||||
GetCreatorActivitySummary(
|
|
||||||
liveCount = liveCount,
|
|
||||||
liveTime = liveTime,
|
|
||||||
liveContributorCount = liveContributorCount,
|
|
||||||
contentCount = contentCount
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
GetCreatorActivitySummary(0, 0, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val seriesList = if (isCreator) {
|
val seriesList = seriesService
|
||||||
seriesService
|
|
||||||
.getSeriesList(creatorId = creatorId, member = member)
|
.getSeriesList(creatorId = creatorId, member = member)
|
||||||
.items
|
.items
|
||||||
} else {
|
|
||||||
listOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetCreatorProfileResponse(
|
return GetCreatorProfileResponse(
|
||||||
creator = CreatorResponse(
|
creator = CreatorResponse(
|
||||||
|
@ -301,10 +235,14 @@ class ExplorerService(
|
||||||
notice = notice,
|
notice = notice,
|
||||||
communityPostList = communityPostList,
|
communityPostList = communityPostList,
|
||||||
cheers = cheers,
|
cheers = cheers,
|
||||||
activitySummary = activitySummary,
|
activitySummary = GetCreatorActivitySummary(
|
||||||
|
liveCount = liveCount,
|
||||||
|
liveTime = liveTime,
|
||||||
|
liveContributorCount = liveContributorCount,
|
||||||
|
contentCount = contentCount
|
||||||
|
),
|
||||||
seriesList = seriesList,
|
seriesList = seriesList,
|
||||||
isBlock = isBlock,
|
isBlock = isBlock
|
||||||
isCreatorRole = isCreator
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ data class GetCreatorProfileResponse(
|
||||||
val cheers: GetCheersResponse,
|
val cheers: GetCheersResponse,
|
||||||
val activitySummary: GetCreatorActivitySummary,
|
val activitySummary: GetCreatorActivitySummary,
|
||||||
val seriesList: List<GetSeriesListResponse.SeriesListItem>,
|
val seriesList: List<GetSeriesListResponse.SeriesListItem>,
|
||||||
val isBlock: Boolean,
|
val isBlock: Boolean
|
||||||
val isCreatorRole: Boolean
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class GetCreatorActivitySummary(
|
data class GetCreatorActivitySummary(
|
||||||
|
|
Loading…
Reference in New Issue