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
@@ -1,13 +1,13 @@
package hs.kr.entrydsm.domain.status.aggregates

import hs.kr.entrydsm.domain.status.values.ApplicationStatus
import hs.kr.entrydsm.global.annotation.aggregates.Aggregate

@Aggregate(context = "status")
data class Status(
val id: Long? = 0,
val isPrintsArrived: Boolean = false,
val isSubmitted: Boolean = false,
val examCode: String? = null,
val applicationStatus: ApplicationStatus,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

마이그레이션/호환성 체크 필요

applicationStatus(디폴트 없음) 도입은 직렬화/캐시/GRPC 매핑, 생성 위치 전부의 변경을 요구합니다. 구버전 필드(isSubmitted, isPrintsArrived) 소비자가 외부에 있다면 디프리케이션 기간/변환 계층이 필요합니다.

다음 점을 확인해 주세요:

  • (de)serialization 스키마 변경 반영 여부(Jackson/Proto)
  • 캐시 엔티티/TTL, 조회 경로 정합성
  • 상태 전이 규칙과 isFirstRoundPass/isSecondRoundPass의 일관성

검증을 위한 간단 스크립트입니다:


🏁 Script executed:

#!/bin/bash
# 변경 전 필드 사용 흔적
rg -nP -C2 '\bisSubmitted\b|\bisPrintsArrived\b'

# Status 생성부 점검(네임드 인자 미사용 호출 탐지 용)
rg -nP --type=kt -C2 '\bStatus\s*\('

Length of output: 116


🏁 Script executed:

#!/bin/bash
# 이전 필드 사용 여부 확인 (KT 파일 전체 대상)
rg -nP -g '*.kt' -C2 '\bisSubmitted\b|\bisPrintsArrived\b'

# Status 생성부 탐지 (네임드 인자 미사용 호출 여부)
rg -nP -g '*.kt' -C2 '\bStatus\s*\('

Length of output: 3169


Status 생성 위치에 applicationStatus 인자 추가 필요

  • Status 생성 호출부(StatusPersistenceAdapter, ExcelTestController 등)에서 applicationStatus 인자를 누락해 컴파일 오류가 발생합니다. 모든 호출부에 적절한 ApplicationStatus 값을 전달하도록 수정하세요.
  • Jackson/Proto 직렬화 스키마, 캐시 엔티티·TTL, GRPC 매핑 등 변경된 스키마가 전체 시스템에 반영됐는지 검증해야 합니다.
  • 기존 isSubmitted·isPrintsArrived 필드 사용 흔적은 없으므로 디프리케이션 계층은 불필요합니다.

val isFirstRoundPass: Boolean = false,
val isSecondRoundPass: Boolean = false,
val receiptCode: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package hs.kr.entrydsm.domain.status.aggregates

import hs.kr.entrydsm.domain.status.values.ApplicationStatus

data class StatusCache(
val receiptCode: Long,
val examCode: String?,
val applicationStatus: ApplicationStatus,
val isFirstRoundPass: Boolean,
val isSecondRoundPass: Boolean,
val ttl: Long
)
Comment on lines +5 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

단일 소스 유지: applicationStatus와 boolean 동시 보유는 불일치 위험

중복 상태가 서로 엇갈릴 수 있습니다. 선택지:

  • A) boolean 제거하고 applicationStatus에서 계산(권장)
  • B) boolean 유지 시 생성 시점에 일관성 검증(require) 추가
 data class StatusCache(
     val receiptCode: Long,
     val examCode: String?,
     val applicationStatus: ApplicationStatus,
-    val isFirstRoundPass: Boolean,
-    val isSecondRoundPass: Boolean,
+    @Deprecated("applicationStatus에서 파생됩니다. 차기 버전에서 제거 예정.")
+    val isFirstRoundPass: Boolean,
+    @Deprecated("applicationStatus에서 파생됩니다. 차기 버전에서 제거 예정.")
+    val isSecondRoundPass: Boolean,
     val ttl: Long
 )

원하시면 A안(계산 프로퍼티/함수로 노출)으로 마이그레이션 플랜과 코드까지 드리겠습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data class StatusCache(
val receiptCode: Long,
val examCode: String?,
val applicationStatus: ApplicationStatus,
val isFirstRoundPass: Boolean,
val isSecondRoundPass: Boolean,
val ttl: Long
)
data class StatusCache(
val receiptCode: Long,
val examCode: String?,
val applicationStatus: ApplicationStatus,
@Deprecated("applicationStatus에서 파생됩니다. 차기 버전에서 제거 예정.")
val isFirstRoundPass: Boolean,
@Deprecated("applicationStatus에서 파생됩니다. 차기 버전에서 제거 예정.")
val isSecondRoundPass: Boolean,
val ttl: Long
)
🤖 Prompt for AI Agents
In
casper-application-domain/src/main/kotlin/hs/kr/entrydsm/domain/status/aggregates/StatusCache.kt
around lines 5–12, the data class stores applicationStatus plus two booleans
(isFirstRoundPass, isSecondRoundPass) creating a duplicate source of truth;
remove the booleans and expose them via computed properties/functions derived
from applicationStatus (recommended), or if you must keep them add require
checks in the primary constructor (or init block) to validate consistency with
applicationStatus at creation; implement one approach only and remove the other
fields/logic to ensure a single source of truth.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hs.kr.entrydsm.domain.status.interfaces

interface ApplicationCommandStatusContract {
fun updateExamCode(receiptCode: Long, examCode: String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hs.kr.entrydsm.domain.status.interfaces

import hs.kr.entrydsm.domain.status.aggregates.Status
import hs.kr.entrydsm.domain.status.aggregates.StatusCache

interface ApplicationQueryStatusContract {
fun queryStatusByReceiptCode(receiptCode: Long): Status?
fun queryStatusByReceiptCodeInCache(receiptCode: Long): StatusCache?
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
package hs.kr.entrydsm.domain.status.interfaces

interface StatusContract :
SaveExamCodeContract
interface StatusContract : ApplicationQueryStatusContract, ApplicationCommandStatusContract
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
package hs.kr.entrydsm.domain.status.values

/**
* 지원서 상태를 나타내는 열거형 클래스입니다.
* gRPC 프로토콜과 매핑되는 지원 상태를 정의합니다.
*/
enum class ApplicationStatus {
/** 미지원 - 지원자가 원서를 작성하지 않은 상태 */
/**
* 미지원 - 지원자가 원서를 작성하지 않은 상태
*/
NOT_APPLIED,

/** 원서 작성 중 - 원서가 저장되었으나 제출되지 않은 상태 */
/**
* 원서 작성 중 - 원서가 저장되었으나 제출되지 않은 상태
*/
WRITING,

/** 지원 완료 - 온라인 원서 접수 완료 상태 */
/**
* 지원 완료 - 온라인 원서 접수 완료 상태
*/
SUBMITTED,

/** 서류 도착 대기 - 원서 제출 후 학교에서 접수 확인 전 상태 */
/**
* 서류 도착 대기 - 원서 제출 후 학교에서 접수 확인 전 상태
*/
WAITING_DOCUMENTS,

/** 서류 접수 완료 - 학교에서 원서 및 증빙 서류가 정상적으로 도착하여 검토 완료된 상태 */
/**
* 서류 접수 완료 - 학교에서 원서 및 증빙 서류가 정상적으로 도착하여 검토 완료된 상태
*/
DOCUMENTS_RECEIVED,

/** 전형 진행 중 - 1차 또는 2차 전형이 진행 중인 상태 */
/**
* 전형 진행 중 - 1차 또는 2차 전형이 진행 중인 상태
*/
SCREENING_IN_PROGRESS,

/** 합격 여부 확인 - 최종 합격자 발표 완료 상태 */
RESULT_ANNOUNCED
/**
* 합격 여부 확인 - 최종 합격자 발표 완료 상태
*/
RESULT_ANNOUNCED,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package hs.kr.entrydsm.domain.user.aggregates

import hs.kr.entrydsm.global.annotation.aggregates.Aggregate
import java.util.UUID

@Aggregate(context = "user")
data class User(
val id: UUID,
val phoneNumber: String,
val name: String,
val isParent: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package hs.kr.entrydsm.domain.user.interfaces

import hs.kr.entrydsm.domain.user.aggregates.User
import java.util.UUID

interface ApplicationQueryUserContract {
fun queryUserByUserId(userId: UUID): User
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package hs.kr.entrydsm.domain.user.interfaces

interface UserContract : ApplicationQueryUserContract {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hs.kr.entrydsm.application.global.grpc.dto.user
package hs.kr.entrydsm.domain.user.value

/**
* 사용자 역할을 나타내는 열거형 클래스입니다.
Expand All @@ -19,4 +19,4 @@ enum class UserRole {
* 관리자 권한
*/
ADMIN,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package hs.kr.entrydsm.application.domain.examcode.usecase
import hs.kr.entrydsm.domain.application.interfaces.ApplicationContract
import hs.kr.entrydsm.domain.examcode.interfaces.GrantExamCodesContract
import hs.kr.entrydsm.domain.examcode.values.ExamCodeInfo
import hs.kr.entrydsm.domain.status.interfaces.StatusContract
import hs.kr.entrydsm.application.global.annotation.usecase.UseCase
import hs.kr.entrydsm.domain.application.aggregates.Application
import hs.kr.entrydsm.domain.application.values.ApplicationType
Expand All @@ -12,6 +11,7 @@ import hs.kr.entrydsm.domain.examcode.interfaces.BaseLocationContract
import hs.kr.entrydsm.domain.examcode.interfaces.KakaoGeocodeContract
import hs.kr.entrydsm.domain.examcode.values.DistanceGroup
import hs.kr.entrydsm.application.domain.examcode.util.DistanceUtil
import hs.kr.entrydsm.domain.status.interfaces.SaveExamCodeContract
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

Expand All @@ -27,9 +27,9 @@ import kotlinx.coroutines.coroutineScope
@UseCase
class GrantExamCodesUseCase(
private val applicationContract: ApplicationContract,
private val saveExamCodeContract: SaveExamCodeContract,
private val kakaoGeocodeContract: KakaoGeocodeContract,
private val baseLocationContract: BaseLocationContract,
private val statusContract: StatusContract,
private val distanceUtil: DistanceUtil,
) : GrantExamCodesContract {

Expand Down Expand Up @@ -145,7 +145,7 @@ class GrantExamCodesUseCase(
private suspend fun saveExamCodes(examCodeInfos: List<ExamCodeInfo>) {
examCodeInfos.forEach { info ->
info.examCode?.let { examCode ->
statusContract.updateExamCode(info.receiptCode, examCode)
saveExamCodeContract.updateExamCode(info.receiptCode, examCode)
}
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package hs.kr.entrydsm.application.domain.status.domain

import hs.kr.entrydsm.application.domain.status.domain.repository.StatusCacheRepository
import hs.kr.entrydsm.application.global.grpc.client.status.StatusGrpcClient
import hs.kr.entrydsm.domain.status.aggregates.Status
import hs.kr.entrydsm.domain.status.aggregates.StatusCache
import hs.kr.entrydsm.domain.status.interfaces.StatusContract
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Component

@Component
class StatusPersistenceAdapter(
private val statusGrpcClient: StatusGrpcClient,
private val statusCacheRepository: StatusCacheRepository
) : StatusContract {

override fun queryStatusByReceiptCode(receiptCode: Long): Status? = runBlocking {
statusGrpcClient.getStatusByReceiptCode(receiptCode)?.let {
Status(
id = it.id,
examCode = it.examCode,
applicationStatus = it.applicationStatus,
isFirstRoundPass = it.isFirstRoundPass,
isSecondRoundPass = it.isSecondRoundPass,
receiptCode = it.receiptCode
)
}
}

override fun queryStatusByReceiptCodeInCache(receiptCode: Long): StatusCache? {
return statusCacheRepository.findById(receiptCode)
.map {
StatusCache(
receiptCode = it.receiptCode,
applicationStatus = it.applicationStatus,
examCode = it.examCode,
isFirstRoundPass = it.isFirstRoundPass,
isSecondRoundPass = it.isSecondRoundPass,
ttl = it.ttl
)
}.orElse(null)
}

override fun updateExamCode(receiptCode: Long, examCode: String) = runBlocking {
statusGrpcClient.updateExamCode(receiptCode, examCode)
}
Comment on lines +44 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

캐시 일관성 깨짐 — 시험번호 갱신 후 캐시 무효화/갱신 필요.

gRPC로 examCode를 갱신해도 Redis 캐시는 그대로입니다. 이후 조회가 캐시에서 나가면 구값을 돌려줍니다. 최소한 무효화해 주세요.

-    override fun updateExamCode(receiptCode: Long, examCode: String) = runBlocking {
-        statusGrpcClient.updateExamCode(receiptCode, examCode)
-    }
+    override fun updateExamCode(receiptCode: Long, examCode: String) = runBlocking {
+        statusGrpcClient.updateExamCode(receiptCode, examCode)
+        // 캐시 일관성 보장: 갱신 직후 캐시 무효화(또는 재적재 로직 선택)
+        statusCacheRepository.deleteById(receiptCode)
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun updateExamCode(receiptCode: Long, examCode: String) = runBlocking {
statusGrpcClient.updateExamCode(receiptCode, examCode)
}
override fun updateExamCode(receiptCode: Long, examCode: String) = runBlocking {
statusGrpcClient.updateExamCode(receiptCode, examCode)
// 캐시 일관성 보장: 갱신 직후 캐시 무효화(또는 재적재 로직 선택)
statusCacheRepository.deleteById(receiptCode)
}
🤖 Prompt for AI Agents
In
casper-application-infrastructure/src/main/kotlin/hs/kr/entrydsm/application/domain/status/domain/StatusPersistenceAdapter.kt
around lines 44–46, the method updates examCode via gRPC but does not touch the
Redis cache, causing stale reads; after calling
statusGrpcClient.updateExamCode(receiptCode, examCode) you must invalidate (or
refresh) the corresponding Redis entry for that receiptCode (or the cache key
pattern used for Status) — either delete the cache key or read the updated
status and write it back to Redis; perform this cache eviction/refresh inside
the same runBlocking block (with error handling/logging) so cache consistency is
ensured after the gRPC update.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hs.kr.entrydsm.application.domain.status.domain.entity

import hs.kr.entrydsm.domain.status.values.ApplicationStatus
import org.springframework.data.annotation.Id
import org.springframework.data.redis.core.RedisHash
import org.springframework.data.redis.core.TimeToLive

Comment on lines +4 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

누락된 import 및 TTL 단위 명시

ApplicationStatus import 가 없어 컴파일 실패합니다. 또한 TTL 단위를 명시하면 오해를 줄일 수 있습니다(기본은 seconds).

 package hs.kr.entrydsm.application.domain.status.domain.entity
@@
+import hs.kr.entrydsm.domain.status.values.ApplicationStatus
 import org.springframework.data.annotation.Id
 import org.springframework.data.redis.core.RedisHash
 import org.springframework.data.redis.core.TimeToLive
+import java.util.concurrent.TimeUnit
@@
-    @TimeToLive
+    @TimeToLive(unit = TimeUnit.SECONDS)
     val ttl: Long

Also applies to: 12-16

@RedisHash("status_cache")
class StatusCacheRedisEntity(
@Id
val receiptCode: Long,
val examCode: String?,
val applicationStatus: ApplicationStatus,
val isFirstRoundPass: Boolean,
val isSecondRoundPass: Boolean,
@TimeToLive
val ttl: Long
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package hs.kr.entrydsm.application.domain.status.domain.repository

import hs.kr.entrydsm.application.domain.status.domain.entity.StatusCacheRedisEntity
import org.springframework.data.repository.CrudRepository

interface StatusCacheRepository : CrudRepository<StatusCacheRedisEntity, Long>{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package hs.kr.entrydsm.application.domain.user.domain

import hs.kr.entrydsm.application.global.grpc.client.user.UserGrpcClient
import hs.kr.entrydsm.domain.user.aggregates.User
import hs.kr.entrydsm.domain.user.interfaces.UserContract
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Component
import java.util.UUID

@Component
class UserPersistenceAdapter(
private val userGrpcClient: UserGrpcClient
) : UserContract {

override fun queryUserByUserId(userId: UUID): User = runBlocking {
userGrpcClient.getUserInfoByUserId(userId).run {
User(
id = id,
phoneNumber = phoneNumber,
name = name,
isParent = isParent,
)
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,23 @@ import com.itextpdf.io.font.FontProgramFactory
import org.springframework.stereotype.Component
import java.io.IOException

/**
* PDF 변환을 위한 변환 속성을 생성하는 클래스입니다.
*
* iText PDF 라이브러리에서 HTML을 PDF로 변환할 때 필요한 설정을 관리합니다.
* 특히 한글 폰트 설정을 담당하여 PDF에서 한글이 정상적으로 표시되도록 합니다.
*/
@Component
class ConverterPropertiesCreator {
private var fontPath: String = "/fonts/"

/**
* PDF 변환을 위한 ConverterProperties를 생성합니다.
* 한글 폰트 설정을 포함하여 PDF 생성 시 필요한 모든 속성을 구성합니다.
*
* @return 설정된 ConverterProperties 객체
* @throws IllegalStateException 폰트 파일을 찾을 수 없는 경우
*/
fun createConverterProperties(): ConverterProperties {
val properties = ConverterProperties()
val fontProvider = DefaultFontProvider(false, false, false)
Expand Down
Loading