앱 - 오디션 투표 API

This commit is contained in:
Klaus 2025-01-03 00:09:53 +09:00
parent 7a395a9906
commit cd0c066978
5 changed files with 146 additions and 1 deletions

View File

@ -0,0 +1,33 @@
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
)
)
}
}

View File

@ -0,0 +1,40 @@
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 countByMemberIdAndApplicantIdAndVoteDateRange(
memberId: Long,
applicantId: Long,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int
}
class AuditionVoteQueryRepositoryImpl(
private val queryFactory: JPAQueryFactory
) : AuditionVoteQueryRepository {
override fun countByMemberIdAndApplicantIdAndVoteDateRange(
memberId: Long,
applicantId: Long,
startDate: LocalDateTime,
endDate: LocalDateTime
): Int {
return queryFactory
.select(auditionVote.id)
.from(auditionVote)
.where(
auditionVote.member.id.eq(memberId)
.and(auditionVote.applicant.id.eq(applicantId))
.and(auditionVote.createdAt.between(startDate, endDate))
)
.fetch()
.size
}
}

View File

@ -0,0 +1,64 @@
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.countByMemberIdAndApplicantIdAndVoteDateRange(
memberId = member.id!!,
applicantId = applicantId,
startDate = startDate,
endDate = endDate
)
if (voteCount > 10) {
throw SodaException("오늘 해당 지원자에게 할 수 있는 최대 투표수를 초과하였습니다.\n내일 다시 투표해 주세요.")
}
if (voteCount > 0) {
canPaymentService.spendCan(
memberId = member.id!!,
needCan = 1,
canUsage = CanUsage.AUDITION_VOTE,
container = container
)
}
val auditionVote = AuditionVote()
auditionVote.applicant = applicant
auditionVote.member = member
repository.save(auditionVote)
}
}

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.audition.vote
data class VoteAuditionApplicantRequest(
val applicantId: Long,
val timezone: String,
val container: String
)

View File

@ -8,5 +8,6 @@ enum class CanUsage {
ORDER_CONTENT,
SPIN_ROULETTE,
PAID_COMMUNITY_POST,
ALARM_SLOT
ALARM_SLOT,
AUDITION_VOTE
}