Skip to content

Commit b3298f5

Browse files
Merge pull request #23 from EntryDSM/feature/19-fix-global
feature/19-fix-global
2 parents 6d43e46 + f4d2636 commit b3298f5

17 files changed

Lines changed: 757 additions & 50 deletions

File tree

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
root = true
2+
3+
[*.kt]
4+
ktlint_standard_package-name = disabled
5+
ktlint_standard_filename = disabled

build.gradle.kts

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,39 @@
1-
import org.gradle.kotlin.dsl.withType
2-
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3-
import kotlin.collections.plus
4-
51
plugins {
6-
kotlin("jvm") version "1.9.25"
7-
kotlin("plugin.spring") version "1.9.25"
8-
id("org.springframework.boot") version "3.4.4"
9-
id("io.spring.dependency-management") version "1.1.7"
10-
id("org.jlleitschuh.gradle.ktlint").version("12.1.1")
11-
id("io.gitlab.arturbosch.detekt") version "1.23.6"
12-
id("casper.documentation-convention")
2+
id(Plugin.KOTLIN_JVM) version PluginVersion.KOTLIN_VERSION apply false
3+
id(Plugin.DETEKT) version PluginVersion.DETEKT_VERSION
4+
id(Plugin.KTLINT) version PluginVersion.KTLINT_VERSION apply false
135
}
146

15-
// 서브프로젝트 설정
167
subprojects {
17-
// 서브프로젝트에 공통 설정 적용
18-
repositories {
19-
mavenCentral()
8+
group = "hs.kr.entrydsm"
9+
version = "0.0.1"
10+
11+
apply(plugin = Plugin.KTLINT)
12+
13+
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
14+
filter {
15+
exclude("**/build/generated/**")
16+
exclude("**/*Grpc*.kt")
17+
exclude("**/*Proto*.kt")
18+
}
2019
}
2120
}
2221

2322
tasks.register("checkAll") {
2423
group = "verification"
2524
description = "모든 모듈(includeBuild 포함)에 대해 check 태스크를 실행합니다"
2625

27-
// 루트 프로젝트의 check 태스크에 의존
28-
dependsOn(tasks.named("check"))
29-
30-
// 모든 서브프로젝트의 check 태스크에 의존
3126
subprojects.forEach { subproject ->
3227
dependsOn(subproject.tasks.matching { it.name.startsWith("check") })
3328
}
3429

35-
// build-logic, convention 등 includeBuild 모듈의 check 태스크에 의존
3630
dependsOn(gradle.includedBuilds.map { it.task(":check") })
3731
}
3832

39-
group = "hs.kr.entrydsm"
40-
version = "0.0.1-SNAPSHOT"
41-
42-
dependencies {
43-
implementation("org.springframework.boot:spring-boot-starter")
44-
implementation("org.jetbrains.kotlin:kotlin-reflect")
45-
testImplementation("org.springframework.boot:spring-boot-starter-test")
46-
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
47-
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
48-
}
49-
50-
kotlin {
51-
jvmToolchain(17)
52-
}
53-
54-
tasks.withType<KotlinCompile> {
55-
kotlinOptions {
56-
freeCompilerArgs += "-Xjsr305=strict"
57-
}
58-
}
59-
60-
tasks.withType<Test> {
61-
useJUnitPlatform()
62-
}
63-
6433
detekt {
6534
config.setFrom(files("detekt.yml"))
66-
buildUponDefaultConfig = false // yml에서 설정한 룰만 허용
67-
parallel = true // 병렬 실행으로 성능 최적화
35+
buildUponDefaultConfig = false
36+
parallel = true
6837
}
6938

7039
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
@@ -73,5 +42,5 @@ tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
7342
txt.required.set(false)
7443
}
7544

76-
jvmTarget = ("17") // Detekt가 사용하는 JVM 타겟을 Java 17로 지정
77-
}
45+
jvmTarget = "17"
46+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package hs.kr.entrydsm.user.global.base
2+
3+
import jakarta.persistence.EntityListeners
4+
import jakarta.persistence.MappedSuperclass
5+
import org.springframework.data.annotation.CreatedDate
6+
import org.springframework.data.annotation.LastModifiedDate
7+
import org.springframework.data.jpa.domain.support.AuditingEntityListener
8+
import java.time.LocalDateTime
9+
10+
/**
11+
* 생성 및 수정 시간을 자동으로 관리하는 JPA 엔티티의 기본 클래스입니다.
12+
* Spring Data JPA의 Auditing 기능을 사용하여 엔티티의 생성/수정 시간을 자동으로 설정합니다.
13+
*
14+
* @property createdAt 엔티티 생성 시간
15+
* @property modifiedAt 엔티티 최종 수정 시간
16+
*/
17+
@MappedSuperclass
18+
@EntityListeners(AuditingEntityListener::class)
19+
abstract class BaseTimeEntity(
20+
@CreatedDate
21+
private val createdAt: LocalDateTime? = LocalDateTime.now(),
22+
@LastModifiedDate
23+
private val modifiedAt: LocalDateTime? = LocalDateTime.now(),
24+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package hs.kr.entrydsm.user.global.base
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.GeneratedValue
5+
import jakarta.persistence.GenerationType
6+
import jakarta.persistence.Id
7+
import jakarta.persistence.MappedSuperclass
8+
import java.util.UUID
9+
10+
/**
11+
* UUID 기본키를 사용하는 JPA 엔티티의 기본 클래스입니다.
12+
* 모든 엔티티가 공통으로 사용하는 UUID 기본키 필드를 정의합니다.
13+
*
14+
* @property id UUID 타입의 기본키
15+
*/
16+
@MappedSuperclass
17+
abstract class BaseUUIDEntity(
18+
@Id
19+
@GeneratedValue(strategy = GenerationType.UUID)
20+
@Column(
21+
columnDefinition = "BINARY(16)",
22+
nullable = false,
23+
)
24+
val id: UUID?,
25+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hs.kr.entrydsm.user.global.config
2+
3+
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
4+
import org.springframework.context.annotation.Configuration
5+
6+
/**
7+
* Configuration Properties 스캔 설정 클래스입니다.
8+
* 애플리케이션 전반의 설정 프로퍼티들을 자동으로 스캔하여 등록합니다.
9+
*/
10+
@Configuration
11+
@ConfigurationPropertiesScan("hs.kr.entrydsm")
12+
class ConfigurationProperties
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package hs.kr.entrydsm.user.global.config
2+
3+
import org.springframework.context.annotation.Configuration
4+
import org.springframework.scheduling.annotation.EnableScheduling
5+
6+
/**
7+
* 스케줄링 기능을 활성화하는 설정 클래스입니다.
8+
* Spring의 @Scheduled 어노테이션을 사용한 작업을 가능하게 합니다.
9+
*/
10+
@Configuration
11+
@EnableScheduling
12+
class EnableScheduling
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package hs.kr.entrydsm.user.global.converter
2+
3+
import hs.kr.entrydsm.user.global.utils.encryption.EncryptionUtil
4+
import jakarta.persistence.AttributeConverter
5+
import jakarta.persistence.Converter
6+
import org.springframework.stereotype.Component
7+
8+
/**
9+
* JPA 엔티티의 문자열 필드를 암호화하여 저장하는 컨버터 클래스입니다.
10+
* 민감한 개인정보를 데이터베이스에 안전하게 저장하기 위해 사용됩니다.
11+
*
12+
* @property encryptionUtil 암호화/복호화를 담당하는 유틸리티
13+
*/
14+
@Component
15+
@Converter
16+
class EncryptedStringConverter(
17+
private val encryptionUtil: EncryptionUtil,
18+
) : AttributeConverter<String, String> {
19+
/**
20+
* 엔티티 속성을 데이터베이스 컬럼으로 변환할 때 암호화를 수행합니다.
21+
*
22+
* @param attributes 암호화할 엔티티 속성값
23+
* @return 암호화된 문자열 (null이면 null 반환)
24+
*/
25+
override fun convertToDatabaseColumn(attributes: String?): String? {
26+
return attributes?.let { encryptionUtil.encrypt(it) }
27+
}
28+
29+
/**
30+
* 데이터베이스 컬럼을 엔티티 속성으로 변환할 때 복호화를 수행합니다.
31+
*
32+
* @param dbData 복호화할 데이터베이스 값
33+
* @return 복호화된 문자열 (null이면 null 반환)
34+
*/
35+
override fun convertToEntityAttribute(dbData: String?): String? {
36+
return dbData?.let { encryptionUtil.decrypt(it) }
37+
}
38+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package hs.kr.entrydsm.user.global.document.admin
2+
3+
import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.request.AdminLoginRequest
4+
import hs.kr.entrydsm.user.domain.admin.adapter.`in`.web.dto.response.InternalAdminResponse
5+
import hs.kr.entrydsm.user.global.utils.token.dto.TokenResponse
6+
import io.swagger.v3.oas.annotations.Operation
7+
import io.swagger.v3.oas.annotations.Parameter
8+
import io.swagger.v3.oas.annotations.media.Content
9+
import io.swagger.v3.oas.annotations.media.Schema
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses
12+
import io.swagger.v3.oas.annotations.security.SecurityRequirement
13+
import io.swagger.v3.oas.annotations.tags.Tag
14+
import jakarta.validation.Valid
15+
import org.springframework.web.bind.annotation.PathVariable
16+
import org.springframework.web.bind.annotation.RequestBody
17+
import org.springframework.web.bind.annotation.RequestHeader
18+
import java.util.UUID
19+
20+
/**
21+
* 관리자 API 문서화를 위한 인터페이스입니다.
22+
*/
23+
@Tag(name = "Admin", description = "관리자 API")
24+
interface AdminApiDocument {
25+
/**
26+
* 관리자 로그인을 처리합니다.
27+
*/
28+
@Operation(
29+
summary = "관리자 로그인",
30+
description = "관리자 ID와 비밀번호로 로그인하여 JWT 토큰을 발급받습니다. 성공 시 UserInfo도 Redis에 저장됩니다.",
31+
)
32+
@ApiResponses(
33+
ApiResponse(
34+
responseCode = "200",
35+
description = "로그인 성공",
36+
content = arrayOf(Content(schema = Schema(implementation = TokenResponse::class))),
37+
),
38+
ApiResponse(
39+
responseCode = "401",
40+
description = "비밀번호가 일치하지 않음 - Invalid User Password",
41+
content = arrayOf(Content()),
42+
),
43+
ApiResponse(
44+
responseCode = "404",
45+
description = "존재하지 않는 관리자 ID - Admin Not Found",
46+
content = arrayOf(Content()),
47+
),
48+
)
49+
fun login(
50+
@RequestBody @Valid adminLoginRequest: AdminLoginRequest,
51+
): TokenResponse
52+
53+
/**
54+
* 관리자 토큰을 갱신합니다.
55+
*/
56+
@Operation(
57+
summary = "관리자 토큰 갱신",
58+
description = "Refresh Token을 사용하여 새로운 Access Token을 발급받습니다.",
59+
)
60+
@ApiResponses(
61+
ApiResponse(
62+
responseCode = "200",
63+
description = "토큰 갱신 성공",
64+
content = arrayOf(Content(schema = Schema(implementation = TokenResponse::class))),
65+
),
66+
ApiResponse(
67+
responseCode = "401",
68+
description = "유효하지 않거나 만료된 토큰 - Invalid Token | Expired Token",
69+
content = arrayOf(Content()),
70+
),
71+
)
72+
fun tokenRefresh(
73+
@Parameter(description = "갱신할 Refresh Token", required = true)
74+
@RequestHeader("X-Refresh-Token") refreshToken: String,
75+
): TokenResponse
76+
77+
@Operation(
78+
summary = "모든 테이블 삭제 (Kafka 메시지 발송)",
79+
description = "Kafka를 통해 모든 테이블 삭제 메시지를 발송합니다. 실제 삭제는 Consumer에서 처리됩니다.",
80+
)
81+
@ApiResponses(
82+
ApiResponse(
83+
responseCode = "204",
84+
description = "삭제 메시지 발송 완료",
85+
content = arrayOf(Content()),
86+
),
87+
ApiResponse(
88+
responseCode = "401",
89+
description = "관리자 권한 없음 - Admin UnAuthorized",
90+
content = arrayOf(Content()),
91+
),
92+
)
93+
/**
94+
* 모든 테이블 삭제 메시지를 발송합니다.
95+
*/
96+
@SecurityRequirement(name = "bearerAuth")
97+
fun deleteAllTable()
98+
99+
/**
100+
* UUID로 관리자 정보를 조회합니다.
101+
*/
102+
@Operation(
103+
summary = "관리자 정보 조회",
104+
description = "UUID로 특정 관리자의 정보를 조회합니다.",
105+
)
106+
@ApiResponses(
107+
ApiResponse(
108+
responseCode = "200",
109+
description = "조회 성공",
110+
content = arrayOf(Content(schema = Schema(implementation = InternalAdminResponse::class))),
111+
),
112+
ApiResponse(
113+
responseCode = "404",
114+
description = "존재하지 않는 관리자 - Admin Not Found",
115+
content = arrayOf(Content()),
116+
),
117+
)
118+
fun findAdminById(
119+
@Parameter(description = "조회할 관리자의 UUID", required = true)
120+
@PathVariable adminId: UUID,
121+
): InternalAdminResponse
122+
}

0 commit comments

Comments
 (0)