Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package hs.kr.entrydsm.domain.application.interfaces

import hs.kr.entrydsm.domain.application.aggregates.Application
import java.util.UUID

interface ApplicationCaseQueryApplicationContract {
fun queryApplicationByUserId(userId: UUID): Application?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package hs.kr.entrydsm.domain.application.interfaces

import hs.kr.entrydsm.domain.schedule.aggregates.Schedule
import hs.kr.entrydsm.domain.schedule.values.ScheduleType

interface ApplicationQueryScheduleContract {
suspend fun queryByScheduleType(scheduleType: ScheduleType): Schedule?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package hs.kr.entrydsm.domain.schedule.aggregates

import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import hs.kr.entrydsm.global.annotation.aggregates.Aggregate
import java.time.LocalDateTime

@Aggregate(context = "schedule")
data class Schedule(
val scheduleType: ScheduleType,
val date: LocalDateTime
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hs.kr.entrydsm.domain.schedule.exception

import hs.kr.entrydsm.global.exception.BusinessException

sealed class ScheduleExceptions(
override val status: Int,
override val message: String,
) : BusinessException(status, message) {

class ScoreNotFoundException(message: String = SCORE_NOT_FOUND_EXCEPTION):
ScheduleExceptions(404, message)

class AdmissionUnavailableException(message: String = ADMISSION_UNAVAILABLE):
ScheduleExceptions(404, message)

companion object {
private const val SCORE_NOT_FOUND_EXCEPTION = "점수가 존재하지 않습니다"
private const val ADMISSION_UNAVAILABLE = "합격여부를 확인할 수 없습니다"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hs.kr.entrydsm.domain.schedule.values

enum class ScheduleType {
START_DATE,
FIRST_ANNOUNCEMENT,
INTERVIEW,
SECOND_ANNOUNCEMENT,
END_DATE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package hs.kr.entrydsm.domain.status.exception

import hs.kr.entrydsm.global.exception.BusinessException

sealed class StatusExceptions(
override val status: Int,
override val message: String,
) : BusinessException(status, message) {
class StatusNotFoundException(message: String = STATUS_NOT_FOUND) :
StatusExceptions(404, message)

class AlreadySubmittedException(message: String = ALREADY_SUBMITTED) :
StatusExceptions(409, message)

companion object {
private const val STATUS_NOT_FOUND = "상태가 존재하지 않습니다"
private const val ALREADY_SUBMITTED = "이미 최종제출이 되어있습니다."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package hs.kr.entrydsm.application.domain.application.usecase

import hs.kr.entrydsm.application.domain.application.exception.ApplicationNotFoundException
import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsFirstRoundPassResponse
import hs.kr.entrydsm.application.global.annotation.usecase.ReadOnlyUseCase
import hs.kr.entrydsm.application.global.security.SecurityAdapter
import hs.kr.entrydsm.domain.application.interfaces.ApplicationContract
import hs.kr.entrydsm.domain.application.interfaces.ApplicationQueryScheduleContract
import hs.kr.entrydsm.domain.schedule.exception.ScheduleExceptions
import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import hs.kr.entrydsm.domain.status.exception.StatusExceptions
import hs.kr.entrydsm.domain.status.interfaces.ApplicationQueryStatusContract
import java.time.LocalDateTime

@ReadOnlyUseCase
class QueryIsFirstRoundPassUseCase(
private val securityAdapter: SecurityAdapter,
private val queryApplicationContract: ApplicationContract,
private val applicationQueryScheduleContract: ApplicationQueryScheduleContract,
private val applicationQueryStatusContract: ApplicationQueryStatusContract
) {
suspend fun execute(): QueryIsFirstRoundPassResponse {

val userId = securityAdapter.getCurrentUserId()
val application = queryApplicationContract.getApplicationByUserId(userId)
?: throw ApplicationNotFoundException()

val firstAnnounce = applicationQueryScheduleContract.queryByScheduleType(ScheduleType.FIRST_ANNOUNCEMENT)
?: throw ScheduleExceptions.ScoreNotFoundException()

if (LocalDateTime.now().isBefore(firstAnnounce.date))
throw ScheduleExceptions.AdmissionUnavailableException()

val status = applicationQueryStatusContract.queryStatusByReceiptCode(application.receiptCode)
?: throw StatusExceptions.StatusNotFoundException()

return QueryIsFirstRoundPassResponse(status.isFirstRoundPass)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package hs.kr.entrydsm.application.domain.application.usecase

import hs.kr.entrydsm.application.domain.application.exception.ApplicationNotFoundException
import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsSecondRoundPassResponse
import hs.kr.entrydsm.application.global.annotation.usecase.ReadOnlyUseCase
import hs.kr.entrydsm.application.global.security.SecurityAdapter
import hs.kr.entrydsm.domain.application.interfaces.ApplicationContract
import hs.kr.entrydsm.domain.application.interfaces.ApplicationQueryScheduleContract
import hs.kr.entrydsm.domain.schedule.exception.ScheduleExceptions
import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import hs.kr.entrydsm.domain.status.exception.StatusExceptions
import hs.kr.entrydsm.domain.status.interfaces.ApplicationQueryStatusContract
import java.time.LocalDateTime

@ReadOnlyUseCase
class QueryIsSecondRoundPassUseCase (
private val securityAdapter: SecurityAdapter,
private val queryApplicationContract: ApplicationContract,
private val applicationQueryScheduleContract: ApplicationQueryScheduleContract,
private val applicationQueryStatusContract: ApplicationQueryStatusContract
) {
suspend fun execute(): QueryIsSecondRoundPassResponse {
val userId = securityAdapter.getCurrentUserId()
val application = queryApplicationContract.getApplicationByUserId(userId)
?: throw ApplicationNotFoundException()

val secondAnnounce = applicationQueryScheduleContract.queryByScheduleType(ScheduleType.SECOND_ANNOUNCEMENT)
?: throw ScheduleExceptions.ScoreNotFoundException()

if (LocalDateTime.now().isBefore(secondAnnounce.date))
throw ScheduleExceptions.AdmissionUnavailableException()

val status = applicationQueryStatusContract.queryStatusByReceiptCode(application.receiptCode)
?: throw StatusExceptions.StatusNotFoundException()

return QueryIsSecondRoundPassResponse(status.isSecondRoundPass)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package hs.kr.entrydsm.application.domain.pass.presentation

import hs.kr.entrydsm.application.domain.application.usecase.QueryIsFirstRoundPassUseCase
import hs.kr.entrydsm.application.domain.application.usecase.QueryIsSecondRoundPassUseCase
import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsFirstRoundPassResponse
import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsSecondRoundPassResponse
import hs.kr.entrydsm.application.global.document.pass.PassApiDocument
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/pass")
class WebPassAdapter(
private val queryIsFirstRoundPassUseCase: QueryIsFirstRoundPassUseCase,
private val queryIsSecondRoundPassUseCase: QueryIsSecondRoundPassUseCase
) : PassApiDocument {
@GetMapping("/first-round")
override suspend fun queryIsFirstRound(): QueryIsFirstRoundPassResponse =
queryIsFirstRoundPassUseCase.execute()

@GetMapping("/second-round")
override suspend fun queryIsSecondRound(): QueryIsSecondRoundPassResponse =
queryIsSecondRoundPassUseCase.execute()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hs.kr.entrydsm.application.domain.pass.presentation.dto.response

data class QueryIsFirstRoundPassResponse(
val isFirstRoundPass: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hs.kr.entrydsm.application.domain.pass.presentation.dto.response

data class QueryIsSecondRoundPassResponse(
val finalPass: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package hs.kr.entrydsm.application.domain.schedule.domain

import hs.kr.entrydsm.application.global.grpc.client.schedule.ScheduleGrpcClient
import hs.kr.entrydsm.domain.application.interfaces.ApplicationQueryScheduleContract
import hs.kr.entrydsm.domain.schedule.aggregates.Schedule
import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import org.springframework.stereotype.Component

@Component
class SchedulePersistenceAdapterApplication(
private val scheduleGrpcClient: ScheduleGrpcClient
) : ApplicationQueryScheduleContract {
override suspend fun queryByScheduleType(scheduleType: ScheduleType): Schedule? {
return scheduleGrpcClient.getScheduleByType(scheduleType.name).let {
Schedule(
scheduleType = it.type,
date = it.date
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package hs.kr.entrydsm.application.global.document.pass

import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsFirstRoundPassResponse
import hs.kr.entrydsm.application.domain.pass.presentation.dto.response.QueryIsSecondRoundPassResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag

@Tag(name = "Pass", description = "합격 여부 조회 API")
interface PassApiDocument {
@Operation(
summary = "1차 전형 합격 여부 조회",
description = "현재 로그인한 사용자의 1차 전형 합격 여부를 조회합니다.",
)
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "합격 여부 조회 성공",
content = [Content(schema = Schema(implementation = QueryIsFirstRoundPassResponse::class))]
),
ApiResponse(responseCode = "403", description = "인증되지 않은 사용자"),
ApiResponse(responseCode = "404", description = "지원자 또는 전형 정보를 찾을 수 없음"),
]
)
suspend fun queryIsFirstRound(): QueryIsFirstRoundPassResponse

@Operation(
summary = "2차 전형 최종 합격 여부 조회",
description = "현재 로그인한 사용자의 2차 전형 최종 합격 여부를 조회합니다.",
)
@ApiResponses(
value = [
ApiResponse(
responseCode = "200",
description = "합격 여부 조회 성공",
content = [Content(schema = Schema(implementation = QueryIsSecondRoundPassResponse::class))]
),
ApiResponse(responseCode = "403", description = "인증되지 않은 사용자"),
ApiResponse(responseCode = "404", description = "지원자 또는 전형 정보를 찾을 수 없음"),
]
)
suspend fun queryIsSecondRound(): QueryIsSecondRoundPassResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package hs.kr.entrydsm.application.global.grpc.client.schedule

import hs.kr.entrydsm.application.global.extension.executeGrpcCallWithResilience
import hs.kr.entrydsm.application.global.grpc.dto.schedule.InternalScheduleResponse
import hs.kr.entrydsm.application.global.grpc.dto.schedule.ScheduleType
import hs.kr.entrydsm.casper.schedule.proto.ScheduleServiceGrpc
import hs.kr.entrydsm.casper.schedule.proto.ScheduleServiceProto
import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import io.github.resilience4j.circuitbreaker.CircuitBreaker
import io.github.resilience4j.retry.Retry
import io.grpc.Channel
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package hs.kr.entrydsm.application.global.grpc.dto.schedule

import hs.kr.entrydsm.domain.schedule.values.ScheduleType
import java.time.LocalDateTime

/**
Expand Down
Loading