Skip to content

Commit a6b0fd3

Browse files
authored
Merge pull request #46 from EntryDSM/feature/원서-접수-기능
Feature/원서 접수 기능
2 parents bd8c1c2 + cdc5d3e commit a6b0fd3

162 files changed

Lines changed: 6795 additions & 502 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

API_TEST.md

Lines changed: 515 additions & 0 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 547 additions & 0 deletions
Large diffs are not rendered by default.

buildSrc/src/main/kotlin/Dependencies.kt

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,41 @@ object Dependencies {
22
//kotlin
33
const val KOTLIN_REFLECT = "org.jetbrains.kotlin:kotlin-reflect"
44
const val KOTLIN_TEST = "org.jetbrains.kotlin:kotlin-test"
5-
65
//springframework
76
const val SPRING_BOOT_STARTER = "org.springframework.boot:spring-boot-starter"
87
const val SPRING_BOOT_STARTER_WEB = "org.springframework.boot:spring-boot-starter-web"
98
const val SPRING_BOOT_STARTER_TEST = "org.springframework.boot:spring-boot-starter-test"
109
const val SPRING_BOOT_STARTER_ACTUATOR = "org.springframework.boot:spring-boot-starter-actuator"
1110
const val SPRING_BOOT_STARTER_SECURITY = "org.springframework.boot:spring-boot-starter-security"
1211
const val SPRING_CONTEXT = "org.springframework:spring-context"
13-
12+
const val SPRING_BOOT_STARTER_VALIDATION = "org.springframework.boot:spring-boot-starter-validation"
13+
const val SPRING_BOOT_STARTER_DATA_JPA = "org.springframework.boot:spring-boot-starter-data-jpa"
14+
//database
15+
const val H2_DATABASE = "com.h2database:h2"
1416
//jexl
1517
const val APACHE_COMMONS_JEXL = "org.apache.commons:commons-jexl3:${DependencyVersions.APACHE_COMMONS_JEXL_VERSION}"
16-
1718
//kotlinx serialization
1819
const val KOTLINX_SERIALIZATION_JSON = "org.jetbrains.kotlinx:kotlinx-serialization-json:${DependencyVersions.KOTLINX_SERIALIZATION_VERSION}"
19-
2020
//kotlinx coroutines
2121
const val KOTLINX_COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${DependencyVersions.KOTLINX_COROUTINES_VERSION}"
22-
const val KOTLINX_COROUTINES_REACTOR = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${DependencyVersions.KOTLINX_COROUTINES_VERSION}"
23-
2422
//junit
2523
const val JUNIT = "org.jetbrains.kotlin:kotlin-test-junit5"
2624
const val JUNIT_PLATFORM_LAUNCHER = "org.junit.platform:junit-platform-launcher"
27-
2825
//poi
2926
const val POI = "org.apache.poi:poi:${DependencyVersions.POI_VERSION}"
3027
const val POI_OOXML = "org.apache.poi:poi-ooxml:${DependencyVersions.POI_VERSION}"
31-
3228
//Pdf
3329
const val PDF_ITEXT = "com.itextpdf:itext7-fonts:${DependencyVersions.PDF_ITEXT}"
3430
const val PDF_HTML = "com.itextpdf:html2pdf:${DependencyVersions.PDF_HTML}"
35-
3631
const val THYMELEAF = "org.springframework.boot:spring-boot-starter-thymeleaf"
37-
3832
//commons io
3933
const val COMMONS_IO = "commons-io:commons-io:${DependencyVersions.COMMONS_IO}"
40-
4134
// Feign Client
4235
const val OPEN_FEIGN = "org.springframework.cloud:spring-cloud-starter-openfeign:${DependencyVersions.OPEN_FEIGN_VERSION}"
43-
4436
// Spring Cloud BOM
4537
const val SPRING_CLOUD = "org.springframework.cloud:spring-cloud-dependencies:${DependencyVersions.SPRING_CLOUD_VERSION}"
46-
4738
// WebFlux
4839
const val WEB_FLUX = "org.springframework.boot:spring-boot-starter-webflux"
49-
5040
// gRPC
5141
const val GRPC_NETTY_SHADED = "io.grpc:grpc-netty-shaded:${DependencyVersions.GRPC}"
5242
const val GRPC_PROTOBUF = "io.grpc:grpc-protobuf:${DependencyVersions.GRPC}"
@@ -56,65 +46,46 @@ object Dependencies {
5646
const val GRPC_TESTING = "io.grpc:grpc-testing:${DependencyVersions.GRPC}"
5747
const val GRPC_CLIENT = "net.devh:grpc-client-spring-boot-starter:${DependencyVersions.GRPC_CLIENT}"
5848
const val GOOGLE_PROTOBUF = "com.google.protobuf:protobuf-java:${DependencyVersions.GOOGLE_PROTOBUF}"
59-
6049
// Coroutines
6150
const val COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${DependencyVersions.COROUTINES}"
62-
6351
// MapStruct
6452
const val MAPSTRUCT = "org.mapstruct:mapstruct:${DependencyVersions.MAPSTRUCT}"
6553
const val MAPSTRUCT_PROCESSOR = "org.mapstruct:mapstruct-processor:${DependencyVersions.MAPSTRUCT}"
66-
6754
// MySQL
6855
const val MYSQL_CONNECTOR = "com.mysql:mysql-connector-j"
69-
7056
// QueryDSL
7157
const val QUERYDSL_JPA = "com.querydsl:querydsl-jpa:${DependencyVersions.QUERYDSL}:jakarta"
7258
const val QUERYDSL_APT = "com.querydsl:querydsl-apt:${DependencyVersions.QUERYDSL}:jakarta"
73-
7459
// Jakarta APIs for kapt
7560
const val JAKARTA_PERSISTENCE_API = "jakarta.persistence:jakarta.persistence-api:${DependencyVersions.JAKARTA_PERSISTENCE}"
7661
const val JAKARTA_ANNOTATION_API = "jakarta.annotation:jakarta.annotation-api:${DependencyVersions.JAKARTA_ANNOTATION}"
77-
7862
// Caffeine
7963
const val CAFFEINE = "com.github.ben-manes.caffeine:caffeine:${DependencyVersions.CAFFEINE}"
80-
8164
// Jackson Kotlin module
8265
const val JACKSON_MODULE_KOTLIN = "com.fasterxml.jackson.module:jackson-module-kotlin"
83-
66+
const val JACKSON_DATATYPE_JSR310 = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
8467
// Coroutines Reactor bridge
8568
const val COROUTINES_REACTOR = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor"
86-
8769
// Reactor Netty
8870
const val REACTOR_NETTY = "io.projectreactor.netty:reactor-netty"
89-
90-
// JPA
91-
const val SPRING_BOOT_STARTER_DATA_JPA = "org.springframework.boot:spring-boot-starter-data-jpa"
92-
9371
// transaction
9472
const val SPRING_TRANSACTION = "org.springframework:spring-tx"
95-
9673
//spring cache
9774
// Redis (캐시)
9875
const val REDIS = "org.springframework.boot:spring-boot-starter-data-redis"
99-
10076
// Cache (스프링 캐시)
10177
const val SPRING_CACHE = "org.springframework.boot:spring-boot-starter-cache"
102-
10378
//Resilience4j
10479
const val RESILIENCE4J_CIRCUITBREAKER = "io.github.resilience4j:resilience4j-circuitbreaker:${DependencyVersions.RESILIENCE4J}"
10580
const val RESILIENCE4J_RETRY = "io.github.resilience4j:resilience4j-retry:${DependencyVersions.RESILIENCE4J}"
10681
const val RESILIENCE4J_SPRING_BOOT = "io.github.resilience4j:resilience4j-spring-boot3:${DependencyVersions.RESILIENCE4J}"
10782
const val RESILIENCE4J_KOTLIN = "io.github.resilience4j:resilience4j-kotlin:${DependencyVersions.RESILIENCE4J}"
108-
10983
// Netty
11084
const val NETTY = "io.netty:netty-resolver-dns-native-macos:${DependencyVersions.NETTY}"
111-
11285
//kafka
11386
const val KAFKA = "org.springframework.kafka:spring-kafka"
114-
11587
// Spring Cloud Config
11688
const val SPRING_CLOUD_STARTER_CONFIG = "org.springframework.cloud:spring-cloud-starter-config"
117-
11889
//swagger
11990
const val SWAGGER = "org.springdoc:springdoc-openapi-starter-webmvc-ui:${DependencyVersions.SWAGGER}"
12091
}

buildSrc/src/main/kotlin/DependencyVersions.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
object DependencyVersions {
2+
// Database
3+
const val MYSQL_CONNECTOR_VERSION = "9.2.0"
4+
5+
// QueryDSL
6+
const val QUERYDSL_VERSION = "5.1.0"
7+
8+
// MapStruct
9+
const val MAPSTRUCT_VERSION = "1.6.3"
10+
211
// JEXL
312
const val APACHE_COMMONS_JEXL_VERSION = "3.5.0"
413

buildSrc/src/main/kotlin/Plugins.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ object Plugins {
22
const val KOTLIN_JVM = "jvm"
33
const val JETBRAINS_KOTLIN_JVM = "org.jetbrains.kotlin.jvm"
44
const val KOTLIN_SPRING = "plugin.spring"
5+
const val KOTLIN_JPA = "plugin.jpa"
6+
const val KOTLIN_ALLOPEN = "plugin.allopen"
7+
const val KOTLIN_NOARG = "plugin.noarg"
8+
const val KOTLIN_KAPT = "kapt"
59
const val KOTLIN_SERIALIZATION = "plugin.serialization"
610
const val JETBRAINS_KOTLIN_SERIALIZATION = "org.jetbrains.kotlin.plugin.serialization"
711
const val SPRING_BOOT = "org.springframework.boot"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package hs.kr.entrydsm.domain.application.entities
2+
3+
import hs.kr.entrydsm.domain.application.values.*
4+
import hs.kr.entrydsm.global.annotation.entities.Entity
5+
import hs.kr.entrydsm.global.constants.ErrorCodes
6+
import hs.kr.entrydsm.global.exception.DomainException
7+
import hs.kr.entrydsm.global.interfaces.AggregateRoot
8+
import java.util.UUID
9+
10+
@Entity(aggregateRoot = ApplicationPrototype::class, context = "application")
11+
data class ApplicationPrototype(
12+
private val prototypeId: PrototypeId,
13+
val applicationType: String,
14+
val educationalStatus: String,
15+
val region: String?,
16+
val application: Map<String, Map<String, FieldDefinition>>,
17+
val score: Map<String, Map<String, FieldDefinition>>,
18+
val formula: List<FormulaStep>,
19+
val constant: Map<String, Double>
20+
) : AggregateRoot<PrototypeId>() {
21+
22+
val id: PrototypeId
23+
@JvmName("getPrototypeId")
24+
get() = prototypeId
25+
26+
init {
27+
if (applicationType.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
28+
if (educationalStatus.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
29+
if (application.isEmpty()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
30+
if (score.isEmpty()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
31+
if (formula.isEmpty()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
32+
}
33+
34+
override fun getId(): PrototypeId = prototypeId
35+
override fun getType(): String = "ApplicationPrototype"
36+
override fun checkInvariants(): Boolean {
37+
return applicationType.isNotBlank() &&
38+
educationalStatus.isNotBlank() &&
39+
application.isNotEmpty() &&
40+
score.isNotEmpty() &&
41+
formula.isNotEmpty()
42+
}
43+
44+
fun getRequiredApplicationFields(): Set<String> {
45+
return application.flatMap { (groupName, fieldGroup) ->
46+
fieldGroup.filter { it.value.required }.keys.map { fieldName ->
47+
"$groupName.$fieldName"
48+
}
49+
}.toSet()
50+
}
51+
52+
fun getRequiredScoreFields(): Set<String> {
53+
return score.flatMap { (groupName, fieldGroup) ->
54+
fieldGroup.filter { it.value.required }.keys.map { fieldName ->
55+
"$groupName.$fieldName"
56+
}
57+
}.toSet()
58+
}
59+
60+
fun validateApplicationData(applicationData: Map<String, Any>): Boolean {
61+
val requiredFields = getRequiredApplicationFields()
62+
val providedFields = extractAllFieldKeys(applicationData)
63+
return requiredFields.all { providedFields.contains(it) }
64+
}
65+
66+
fun validateScoreData(scoreData: Map<String, Any>): Boolean {
67+
val requiredFields = getRequiredScoreFields()
68+
val providedFields = extractAllFieldKeys(scoreData)
69+
return requiredFields.all { providedFields.contains(it) }
70+
}
71+
72+
private fun extractAllFieldKeys(data: Map<String, Any>, prefix: String = ""): Set<String> {
73+
val result = mutableSetOf<String>()
74+
data.forEach { (key, value) ->
75+
val fullKey = if (prefix.isEmpty()) key else "${prefix}.${key}"
76+
result.add(fullKey)
77+
if (value is Map<*, *>) {
78+
@Suppress("UNCHECKED_CAST")
79+
result.addAll(extractAllFieldKeys(value as Map<String, Any>, fullKey))
80+
}
81+
}
82+
return result
83+
}
84+
85+
companion object {
86+
fun create(
87+
prototypeId: PrototypeId,
88+
applicationType: String,
89+
educationalStatus: String,
90+
region: String?,
91+
applicationFields: Map<String, Map<String, FieldDefinition>>,
92+
scoreFields: Map<String, Map<String, FieldDefinition>>,
93+
formulas: List<FormulaStep>,
94+
constants: Map<String, Double>
95+
): ApplicationPrototype {
96+
return ApplicationPrototype(
97+
prototypeId = prototypeId,
98+
applicationType = applicationType,
99+
educationalStatus = educationalStatus,
100+
region = region,
101+
application = applicationFields,
102+
score = scoreFields,
103+
formula = formulas,
104+
constant = constants
105+
)
106+
}
107+
}
108+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package hs.kr.entrydsm.domain.application.entities
2+
3+
import hs.kr.entrydsm.domain.application.values.ApplicationTypeInfo
4+
import hs.kr.entrydsm.domain.application.values.EducationalStatusInfo
5+
import hs.kr.entrydsm.global.annotation.entities.Entity
6+
import hs.kr.entrydsm.global.constants.ErrorCodes
7+
import hs.kr.entrydsm.global.exception.DomainException
8+
import hs.kr.entrydsm.global.interfaces.EntityMarker
9+
10+
@Entity(aggregateRoot = SupportedApplicationTypes::class, context = "application")
11+
data class SupportedApplicationTypes(
12+
val applicationTypes: List<ApplicationTypeInfo>,
13+
val educationalStatuses: List<EducationalStatusInfo>
14+
) : EntityMarker {
15+
16+
init {
17+
if (applicationTypes.isEmpty()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
18+
if (educationalStatuses.isEmpty()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
19+
}
20+
21+
override fun getDomainContext(): String = "application"
22+
23+
override fun getIdentifier(): String = "supported-types"
24+
25+
fun isValidApplicationType(code: String): Boolean {
26+
return applicationTypes.any { it.code == code }
27+
}
28+
29+
fun isValidEducationalStatus(code: String): Boolean {
30+
return educationalStatuses.any { it.code == code }
31+
}
32+
33+
companion object {
34+
fun create(
35+
applicationTypes: List<ApplicationTypeInfo>,
36+
educationalStatuses: List<EducationalStatusInfo>
37+
): SupportedApplicationTypes {
38+
return SupportedApplicationTypes(
39+
applicationTypes = applicationTypes,
40+
educationalStatuses = educationalStatuses
41+
)
42+
}
43+
}
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hs.kr.entrydsm.domain.application.spi
2+
3+
import hs.kr.entrydsm.domain.application.entities.ApplicationPrototype
4+
import hs.kr.entrydsm.domain.application.entities.SupportedApplicationTypes
5+
import hs.kr.entrydsm.domain.application.values.ApplicationTypeFilter
6+
import hs.kr.entrydsm.domain.application.values.ValidationResult
7+
8+
interface PrototypePort {
9+
fun findPrototypeByApplicationType(filter: ApplicationTypeFilter): ApplicationPrototype?
10+
fun findSupportedTypes(): SupportedApplicationTypes
11+
fun validateScoreData(prototype: ApplicationPrototype, scoreData: Map<String, Any>): ValidationResult
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package hs.kr.entrydsm.domain.application.values
2+
3+
import hs.kr.entrydsm.global.constants.ErrorCodes
4+
import hs.kr.entrydsm.global.exception.DomainException
5+
6+
data class ApplicationTypeFilter(
7+
val applicationType: String,
8+
val educationalStatus: String,
9+
val region: String?
10+
) {
11+
init {
12+
if (applicationType.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
13+
if (educationalStatus.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package hs.kr.entrydsm.domain.application.values
2+
3+
import hs.kr.entrydsm.global.constants.ErrorCodes
4+
import hs.kr.entrydsm.global.exception.DomainException
5+
6+
data class ApplicationTypeInfo(
7+
val code: String,
8+
val name: String
9+
) {
10+
init {
11+
if (code.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
12+
if (name.isBlank()) throw DomainException(ErrorCodes.Common.VALIDATION_FAILED)
13+
}
14+
}

0 commit comments

Comments
 (0)