46 Commits

Author SHA1 Message Date
Hanbin Lee
7b5135c109 [#38] feat: 정리
- flyway README 파일 따로 구성
- flyway 관련 기존 gradle 설정 제거
- Jenkinsfile 내용 복구 및 리팩토링
2023-03-12 19:42:06 +09:00
Hanbin Lee
2c93cc3dd2 [#38] feat: feature/33 내용 merge 2023-03-12 19:24:00 +09:00
Hanbin Lee
be217c6b01 Merge branch 'feature/33' into feature/38 2023-03-12 19:23:27 +09:00
Hanbin Lee
dbf8b046b0 [#33] test: 불필요한 요소 제거 테스트 2023-03-12 19:20:39 +09:00
Hanbin Lee
6f183e38ec [#33] fix: jenkinsfile 수정 2023-03-12 19:15:50 +09:00
Hanbin Lee
2c8b4c9a09 [#33] fix: jenkinsfile, flyway 설정내용 수정 2023-03-12 18:57:09 +09:00
Hanbin Lee
c0fcefa8c0 [#33] test: info만 실행 테스트 2023-03-12 18:23:58 +09:00
Hanbin Lee
6036bd63ad [#33] test: info, validate 진행 테스트(build 생략) 2023-03-12 18:11:31 +09:00
Hanbin Lee
cb5179abd7 [#33] test: info, validate 진행 테스트 2023-03-12 18:11:06 +09:00
Hanbin Lee
9e376bfefb [#33] fix: locations workspace 기준으로 변경 2023-03-12 18:06:58 +09:00
Hanbin Lee
f4b11c755c [#33] fix: flyway credentialsId 추가 2023-03-12 17:55:46 +09:00
Hanbin Lee
af1a95b1c7 [#33] fix: flyway runner commandLineArgs 수정 2023-03-12 17:48:19 +09:00
Hanbin Lee
8cc64c879f [#33] fix: flyway runner installation name setting 2023-03-12 17:46:21 +09:00
Hanbin Lee
e08c02a99a [#33] fix: flywayrunner plugin 문법 수정 2023-03-12 17:39:30 +09:00
Hanbin Lee
13566e1f86 [#33] test: jenkins flyway runner 테스트 2023-03-12 17:38:15 +09:00
Hanbin Lee
e9115e5322 Merge pull request #37 from beaniejoy/feature/33
Jenkins Pipeline에 flyway migration 단계 추가 적용
2023-03-04 00:38:03 +09:00
Hanbin Lee
0210499e66 [#33] modify: migration shell script 수정 2023-03-04 00:31:14 +09:00
Hanbin Lee
42c8b3df14 [#33] test: migration > test > build 테스트 2023-03-03 01:28:34 +09:00
Hanbin Lee
54c4481031 [#33] test: DB Migration test 2023-03-03 00:58:50 +09:00
Hanbin Lee
2d142eb829 [#33] test: jenkins 출력 테스트 2023-03-03 00:19:30 +09:00
Hanbin Lee
09b6beee53 [#33] test: flyway migration 작업 테스트 2023-03-02 02:07:06 +09:00
Hanbin Lee
a39084a11a [#33] test: jenkins cloud server 구축 및 테스트2 2023-03-02 02:01:18 +09:00
Hanbin Lee
1beaaad422 [#33] test: jenkins cloud server 구축 및 테스트 2023-03-02 01:57:54 +09:00
Hanbin Lee
826cfb0eaa Merge branch 'main' into feature/33 2023-02-23 00:30:15 +09:00
Hanbin Lee
37169355da Merge pull request #35 from beaniejoy/feature/34
JPA Auditing 기능 수정
2023-02-23 00:28:28 +09:00
Hanbin Lee
3e7b928d8f [#34] feat: JPA Auditing 기능 수정
- auditor에 Security Authentication 적용(createdBy, updatedBy)
- BaseEntity, BaseTimeEntity 분리
- AuditingConfig 파일 common 모듈 내 공통화(including AuditorAware)
- DDL created_by, updated_by type 변경 (varchar(20) > varchar(320), email 최대크기로 설정)
2023-02-20 00:40:37 +09:00
Hanbin Lee
4585c797ca Merge branch 'main' into feature/31 2023-02-19 17:34:55 +09:00
Hanbin Lee
3a4f79fbdb [#31] feat: Jenkinsfile 수정 진행중 2023-02-19 17:32:58 +09:00
Hanbin Lee
88e162fa43 [#31] feat: add DB migration stage 2023-02-06 01:21:12 +09:00
Hanbin Lee
af67ae124b [#31] feat: add DB migration stage 2023-02-06 01:20:45 +09:00
Hanbin Lee
d3af3ac81b Jenkinsfile test를 위한 git clone 주석 2023-02-06 00:01:57 +09:00
Hanbin Lee
6601641875 Merge pull request #32 from beaniejoy/feature/31
Jenkins CI 구성 및 gradle test logging 적용
2023-01-26 01:05:56 +09:00
Hanbin Lee
8bae2da14a [#31] feat: 테스트 결과 colorMode 적용
- buildSrc plugin 내용 클래스 패키지 분리
- colorMode 적용(추후 profile에 따라 처리 필요)
2023-01-25 02:02:58 +09:00
Hanbin Lee
5445a54335 [#31] feat: test logging 출력 기능을 위한 Gradle Custom Plugin 구성
- Custom Gradle Plugin 적용(OperationCompletionListener)
- buildSrc 상수명 변경
- TestListener afterTest 적용(각 테스트 결과 로깅)
2023-01-25 01:21:14 +09:00
Hanbin Lee
f4951990cd [#31] feat: BuildService 이용한 gradle lifecycle 테스트
- test finished 경우에 result logging 출력을 위한 gradle lifecycle 테스트 진행중
- 전반적인 리팩토링
2023-01-24 02:23:05 +09:00
Hanbin Lee
15744f1a9a jdk tool 추가 2023-01-24 00:29:39 +09:00
Hanbin Lee
b814a782b8 update 2023-01-24 00:04:16 +09:00
Hanbin Lee
83239c39ec [#31] feat: add test log event level 2023-01-23 23:34:58 +09:00
Hanbin Lee
35d951a92d [#31] feat: test logging format 추가 2023-01-23 23:25:31 +09:00
Hanbin Lee
826badd638 merge 2023-01-23 22:36:43 +09:00
Hanbin Lee
8b7a39aea9 merge 2023-01-23 22:31:46 +09:00
Hanbin Lee
3e7b225778 Merge branch 'feature/31' into alpha 2023-01-23 22:30:36 +09:00
Hanbin Lee
833cc25c4a [#31] feat: gradle build까지 적용 2023-01-23 21:45:11 +09:00
Hanbin Lee
45e99554d4 [#31] feat: gradle 플러그인 적용 2023-01-23 21:37:12 +09:00
Hanbin Lee
504430bcd1 Jenkinsfile test 2023-01-23 03:03:50 +09:00
Hanbin Lee
c5dfa90ab3 [#31] feat: Jenkinsfile 테스트 2023-01-23 03:02:14 +09:00
44 changed files with 441 additions and 196 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ bin/
out/
!**/src/main/**/out/
!**/src/test/**/out/
lib/
### NetBeans ###
/nbproject/private/

39
Jenkinsfile-service Normal file
View File

@@ -0,0 +1,39 @@
pipeline {
agent any
stages {
stage('Init') {
steps {
script {
sh 'whoami'
sh 'printenv'
FLYWAY_CONFIG = '/home/ec2-user/flyway/flyway.conf'
}
}
}
stage('DB Migrate') {
steps {
flywayrunner installationName: 'flywaytool-jenkins',
flywayCommand: 'info migrate validate',
commandLineArgs: "-configFiles=${FLYWAY_CONFIG}",
credentialsId: 'ecb29499-7272-4e8b-b3ab-a7a3ab7eafab',
url: '',
locations: "filesystem:${WORKSPACE}/db/migration"
}
}
stage('Test') {
steps {
sh './gradlew clean :dongne-service-api:test'
}
}
stage('Build') {
steps {
sh './gradlew clean :dongne-service-api:build -x test'
}
}
}
}

View File

@@ -5,11 +5,19 @@
<br>
## Specification
- java 17
- kotlin 1.6.21
- Spring Boot 2.7.0
- MySQL 8.0.21
## :pushpin: Specification
- Lang
- java 17
- kotlin 1.6.21
- Framework
- Spring Boot 2.7.0
- DB
- MySQL 8.0.21
- Flyway(migration)
- CI/CD
- Jenkins
- Cloud Server
- AWS Lightsail(Amazon Linux2)
<br>
@@ -25,7 +33,7 @@
<br>
## :pushpin: Run Application
## :pushpin: Setting
### 💽 로컬 DB 구성 (docker)
- local에 DB(MySQL)용 docker container run
@@ -34,31 +42,8 @@
$ docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=beaniejoy -d -p 3306:3306 mysql:8.0.21
```
### 💽 DB Migration (flyway)
[flyway doc](https://documentation.red-gate.com/fd/flyway-documentation-138346877.html)
- **Info**
Prints the details and status information about all the migrations
```bash
$ ./gradlew :db:flywayInfo
```
- **Validate**
Validates the applied migrations against the available ones
DB에 적용된 migration과 local에 적용된 migration 정보 일치 여부 체크
```bash
$ ./gradlew :db:flywayValidate
```
- **Migrate**
Migrates the schema to the latest version
migration 설정 내용들 반영
```bash
$ ./gradlew :db:flywayMigrate
```
- **Clean**
Drops all objects (tables, views, procedures, triggers, …) in the configured schemas
(prodution 단계에서는 절대 사용 X)
```bash
$ ./gradlew :db:flywayClean -i
```
### 💽 DB Migration (with flyway)
- [DB migration directory README](https://github.com/beaniejoy/dongne-cafe-api/blob/main/db/README.md)
### 💽 docker compose 실행(수정 작업 진행중)
- docker compose를 이용한 nginx, DB(MySQL), application 한꺼번에 실행하는 경우

View File

@@ -1,20 +1,26 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id(Plugins.Spring.boot).version(Version.Spring.boot)
id(Plugins.Spring.dependencyManagement).version(Version.Spring.dependencyManagement).apply(false)
kotlin(Plugins.Kotlin.jvm).version(Version.kotlin)
kotlin(Plugins.Kotlin.pluginSpring).version(Version.kotlin).apply(false)
kotlin(Plugins.Kotlin.pluginJpa).version(Version.kotlin).apply(false)
}
import plugin.BuildLifecyclePlugin
import task.test.TestContainer
import task.test.TestLoggingUtils
import task.test.TestSummary
val bootJar: BootJar by tasks
bootJar.enabled = false
plugins {
id(Plugins.Spring.BOOT).version(Version.Spring.BOOT)
id(Plugins.Spring.DEPENDENCY_MANAGEMENT).version(Version.Spring.DEPENDENCY_MANAGEMENT).apply(false)
kotlin(Plugins.Kotlin.JVM).version(Version.KOTLIN)
kotlin(Plugins.Kotlin.PLUGIN_SPRING).version(Version.KOTLIN).apply(false)
kotlin(Plugins.Kotlin.PLUGIN_JPA).version(Version.KOTLIN).apply(false)
}
allprojects {
group = "io.beaniejoy.dongecafe"
version = Version.projectVersion
version = Version.PROJECT_VERSION
repositories {
mavenCentral()
@@ -24,15 +30,18 @@ allprojects {
subprojects {
apply {
plugin(Plugins.java)
plugin(Plugins.Spring.dependencyManagement)
plugin(Plugins.Spring.boot)
plugin(Plugins.Spring.DEPENDENCY_MANAGEMENT)
plugin(Plugins.Spring.BOOT)
plugin(Plugins.Kotlin.KOTLIN)
plugin(Plugins.Kotlin.kotlinSpring)
plugin(Plugins.Kotlin.kotlinJpa)
plugin(Plugins.Kotlin.KOTLIN_SPRING)
plugin(Plugins.Kotlin.KOTLIN_JPA)
}
java.sourceCompatibility = JavaVersion.VERSION_17
java.apply {
sourceCompatibility = Version.JAVA
targetCompatibility = Version.JAVA
}
dependencies {
// Spring Boot Project
@@ -52,9 +61,9 @@ subprojects {
runtimeOnly("com.h2database:h2") // H2
// JWT
implementation("io.jsonwebtoken:jjwt-api:${Version.Deps.Jwt}")
runtimeOnly("io.jsonwebtoken:jjwt-impl:${Version.Deps.Jwt}")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:${Version.Deps.Jwt}")
implementation("io.jsonwebtoken:jjwt-api:${Version.Deps.JWT}")
runtimeOnly("io.jsonwebtoken:jjwt-impl:${Version.Deps.JWT}")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:${Version.Deps.JWT}")
// Logging
implementation("io.github.microutils:kotlin-logging:${Version.Deps.KOTLIN_LOGGING}")
@@ -65,14 +74,54 @@ subprojects {
}
tasks.withType<KotlinCompile> {
println("### Configuring $name in project ${project.name} ###")
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_17.toString()
jvmTarget = Version.JAVA.toString()
}
}
// for logging when build finished
apply<BuildLifecyclePlugin>()
// gradle test logging
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
events = setOf(
TestLogEvent.FAILED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_ERROR
)
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
// ignoreFailures = true
addTestListener(object : TestListener {
override fun beforeSuite(desc: TestDescriptor) {}
// handling after all test finished
override fun afterSuite(desc: TestDescriptor, result: TestResult) {
if (desc.parent != null) return
val summary = TestSummary(
projectName = project.name,
taskName = name,
result = result
)
TestContainer.testResults = summary
}
override fun beforeTest(desc: TestDescriptor) {}
// handling after each test finished
override fun afterTest(desc: TestDescriptor, result: TestResult) {
TestLoggingUtils.printEachResult(desc, result)
}
})
}
}

View File

@@ -4,4 +4,4 @@ plugins {
repositories {
mavenCentral()
}
}

View File

@@ -2,21 +2,17 @@ object Plugins {
const val java = "java"
object Spring {
const val boot = "org.springframework.boot"
const val dependencyManagement = "io.spring.dependency-management"
const val BOOT = "org.springframework.boot"
const val DEPENDENCY_MANAGEMENT = "io.spring.dependency-management"
}
object Kotlin {
const val KOTLIN = "kotlin"
const val kotlinSpring = "kotlin-spring"
const val kotlinJpa = "kotlin-jpa"
const val KOTLIN_SPRING = "kotlin-spring"
const val KOTLIN_JPA = "kotlin-jpa"
const val jvm = "jvm"
const val pluginSpring = "plugin.spring"
const val pluginJpa = "plugin.jpa"
}
object FlywayDB {
const val FLYWAY = "org.flywaydb.flyway"
const val JVM = "jvm"
const val PLUGIN_SPRING = "plugin.spring"
const val PLUGIN_JPA = "plugin.jpa"
}
}

View File

@@ -1,18 +1,18 @@
import org.gradle.api.JavaVersion
object Version {
const val projectVersion = "0.0.1-SNAPSHOT"
const val kotlin = "1.6.21"
const val PROJECT_VERSION = "0.0.1-SNAPSHOT"
const val KOTLIN = "1.6.21"
val JAVA = JavaVersion.VERSION_17
object Spring {
const val boot = "2.7.0"
const val dependencyManagement = "1.0.11.RELEASE"
const val BOOT = "2.7.0"
const val DEPENDENCY_MANAGEMENT = "1.0.11.RELEASE"
}
object Deps {
const val KOTLIN_LOGGING = "3.0.4"
const val Jwt = "0.11.5"
}
object FlywayDB {
const val FLYWAY_CORE = "9.8.1"
const val JWT = "0.11.5"
}
}

View File

@@ -0,0 +1,23 @@
package plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.invocation.DefaultGradle
class BuildLifecyclePlugin : Plugin<Project> {
override fun apply(project: Project) {
val gradle = project.gradle
val services = (gradle as DefaultGradle).services
val registry = services[BuildEventListenerRegistryInternal::class.java]
val operationService = gradle.sharedServices.registerIfAbsent("operationService", BuildOperationService::class.java) {
gradle.taskGraph.whenReady {
parameters.lastTaskPath = this.allTasks.last().path
}
}
registry.subscribe(operationService)
}
}

View File

@@ -0,0 +1,26 @@
package plugin
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import org.gradle.tooling.events.task.TaskFinishEvent
import task.test.TestContainer
import task.test.TestLoggingUtils
abstract class BuildOperationService : BuildService<BuildOperationService.Params>, OperationCompletionListener {
interface Params : BuildServiceParameters {
var lastTaskPath: String
}
override fun onFinish(event: FinishEvent?) {
if (event == null || event !is TaskFinishEvent) {
return
}
if (event.descriptor.taskPath == parameters.lastTaskPath) {
TestLoggingUtils.printTotalResult(TestContainer.testResults)
TestContainer.testResults = null
}
}
}

View File

@@ -0,0 +1,8 @@
package task.test
class TestContainer {
companion object {
var testResults: TestSummary? = null
const val colorMode: Boolean = false
}
}

View File

@@ -0,0 +1,55 @@
package task.test
import org.gradle.api.tasks.testing.TestDescriptor
import org.gradle.api.tasks.testing.TestResult
class TestLoggingUtils {
companion object {
const val ANSI_RESET = "\u001B[0m"
const val ANSI_GREEN = "\u001B[32m"
const val ANSI_RED = "\u001B[31m"
fun printTotalResult(summary: TestSummary?) {
if (summary == null) return
val maxLength = summary.maxWidth()
println(
"""
|┌${"─".repeat(maxLength)}
|${
summary.toLogList().joinToString("│\n│", "│", "│") {
val coloredResult = colorResultType(summary.result.resultType)
"$it${" ".repeat(maxLength - it.length)}"
.replace(
oldValue = coloredResult.first.toString(),
newValue = coloredResult.second
)
}
}
|└${"─".repeat(maxLength)}
""".trimMargin()
)
}
fun printEachResult(desc: TestDescriptor, result: TestResult) {
println("[${desc.className}] ${desc.displayName} >> result: ${colorResultType(result.resultType).second}")
}
fun colorResultType(resultType: TestResult.ResultType): Pair<TestResult.ResultType, String> {
if (TestContainer.colorMode.not()) {
return resultType to "${resultType}"
}
val color = when (resultType) {
TestResult.ResultType.SUCCESS -> ANSI_GREEN
TestResult.ResultType.FAILURE -> ANSI_RED
else -> ""
}
return resultType to if (color.isNotEmpty()) {
"${color}${resultType}$ANSI_RESET"
} else "${resultType}"
}
}
}

View File

@@ -0,0 +1,20 @@
package task.test
import org.gradle.api.tasks.testing.TestResult
data class TestSummary(
val projectName: String? = null,
val taskName: String? = null,
val result: TestResult
) {
fun maxWidth(): Int {
return toLogList().maxByOrNull { it.length }?.length ?: 0
}
fun toLogList(): List<String> {
return listOf(
"${projectName}:${taskName} Test result: ${result.resultType}",
"Test summary: ${result.testCount} tests, ${result.successfulTestCount} succeeded, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped"
)
}
}

42
db/README.md Normal file
View File

@@ -0,0 +1,42 @@
# DB Migration
- flyway version: `9.15.4`
- [flyway doc](https://documentation.red-gate.com/fd/flyway-documentation-138346877.html)
## :pushpin: Installation
```shell
$ brew install flyway
```
- macOS 전용
<br>
## :pushpin: Flyway Command
- **Clean**
Drops all objects (tables, views, procedures, triggers, …) in the configured schemas
(prodution 단계에서는 절대 사용 X)
```bash
$ flyway clean -configFiles=db/flyway.conf
```
- **Info**
Prints the details and status information about all the migrations
```bash
$ flyway info -configFiles=db/flyway.conf
```
- **Migrate**
Migrates the schema to the latest version
migration 설정 내용들 반영
```bash
$ flyway migrate -configFiles=db/flyway.conf
```
- **Validate**
Validates the applied migrations against the available ones
DB에 적용된 migration과 local에 적용된 migration 정보 일치 여부 체크
```bash
$ flyway validate -configFiles=db/flyway.conf
```

View File

@@ -1,26 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.flywaydb:flyway-mysql:${Version.FlywayDB.FLYWAY_CORE}")
}
}
plugins {
id(Plugins.FlywayDB.FLYWAY).version(Version.FlywayDB.FLYWAY_CORE)
}
dependencies {
implementation("org.flywaydb:flyway-core:${Version.FlywayDB.FLYWAY_CORE}") // flyway
}
flyway {
baselineDescription = "Start Flyway Migration!"
baselineOnMigrate = true
baselineVersion = "000"
locations = arrayOf("filesystem:./migration", "filesystem:./seed")
configFiles = arrayOf("conf/flyway.conf")
cleanDisabled = false // activate flywayClean
ignoreMigrationPatterns = arrayOf("*:pending") // ignore validating pending(대기) state
}

View File

@@ -1,4 +0,0 @@
flyway.url=jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
flyway.user=root
flyway.password=beaniejoy
flyway.driver=com.mysql.cj.jdbc.Driver

10
db/flyway.conf Normal file
View File

@@ -0,0 +1,10 @@
flyway.url=jdbc:mysql://localhost:3306/dongne?autoreconnect=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
flyway.user=root
flyway.password=beaniejoy
flyway.driver=com.mysql.cj.jdbc.Driver
flyway.locations=filesystem:db/migration,db/seed
flyway.baselineOnMigrate=true
flyway.baselineVersion=000
# flyway.ignoreMigrationPatterns=*:pending
flyway.cleanDisabled=false

View File

@@ -6,8 +6,8 @@ CREATE TABLE `cafe` (
`total_rate` float NOT NULL COMMENT '카페 종합 평가 점수',
`description` varchar(255) COMMENT '카페 상세설명',
`created_at` datetime NOT NULL COMMENT '카페 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '카페 등록자',
`created_by` varchar(320) NOT NULL COMMENT '카페 등록자',
`updated_at` datetime NULL COMMENT '카페 변경날짜',
`updated_by` varchar(20) NULL COMMENT '카페 변경자',
`updated_by` varchar(320) NULL COMMENT '카페 변경자',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -3,9 +3,9 @@ CREATE TABLE `cafe_menu` (
`name` varchar(50) NOT NULL COMMENT '카페 메뉴명',
`price` decimal(10, 2) NOT NULL COMMENT '메뉴 가격',
`created_at` datetime NOT NULL COMMENT '메뉴 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '메뉴 등록자',
`created_by` varchar(320) NOT NULL COMMENT '메뉴 등록자',
`updated_at` datetime COMMENT '메뉴 변경날짜',
`updated_by` varchar(20) NULL COMMENT '메뉴 변경자',
`updated_by` varchar(320) NULL COMMENT '메뉴 변경자',
`cafe_id` bigint unsigned NOT NULL COMMENT '연관된 카페 ID',
PRIMARY KEY (`id`),
KEY `cafe_id` (`cafe_id`),

View File

@@ -2,9 +2,9 @@ CREATE TABLE `cafe_image` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '카페 이미지 ID',
`img_url` varchar(255) NOT NULL COMMENT '이미지 경로',
`created_at` datetime NOT NULL COMMENT '이미지 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '이미지 등록자',
`created_by` varchar(320) NOT NULL COMMENT '이미지 등록자',
`updated_at` datetime COMMENT '이미지 변경날짜',
`updated_by` varchar(20) NULL COMMENT '이미지 변경자',
`updated_by` varchar(320) NULL COMMENT '이미지 변경자',
`cafe_id` bigint unsigned NOT NULL COMMENT '연관된 카페 ID',
PRIMARY KEY (`id`),
KEY `cafe_id` (`cafe_id`),

View File

@@ -2,9 +2,9 @@ CREATE TABLE `menu_option`(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '옵션 ID',
`title` varchar(50) NOT NULL COMMENT '메뉴 옵션 이름',
`created_at` datetime NOT NULL COMMENT '옵션 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '옵션 등록자',
`created_by` varchar(320) NOT NULL COMMENT '옵션 등록자',
`updated_at` datetime COMMENT '옵션 변경날짜',
`updated_by` varchar(20) NULL COMMENT '옵션 변경자',
`updated_by` varchar(320) NULL COMMENT '옵션 변경자',
`menu_id` bigint unsigned NOT NULL COMMENT '연관된 카페 메뉴 ID',
PRIMARY KEY (`id`),
KEY `menu_id` (`menu_id`),

View File

@@ -3,9 +3,9 @@ CREATE TABLE `option_detail` (
`name` varchar(50) NOT NULL COMMENT '옵션 상세명',
`extra_price` decimal(10, 2) NOT NULL COMMENT '옵션 추가 요금',
`created_at` datetime NOT NULL COMMENT '옵션 상세 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '옵션 상세 등록자',
`created_by` varchar(320) NOT NULL COMMENT '옵션 상세 등록자',
`updated_at` datetime COMMENT '옵션 상세 변경날짜',
`updated_by` varchar(20) NULL COMMENT '옵션 상세 변경자',
`updated_by` varchar(320) NULL COMMENT '옵션 상세 변경자',
`option_id` bigint unsigned NOT NULL COMMENT '연관된 옵션 ID',
PRIMARY KEY (`id`),
KEY `option_id` (`option_id`),

View File

@@ -7,8 +7,8 @@ CREATE TABLE `member` (
`role_type` varchar(20) COMMENT '회원 권한',
`activated` tinyint NOT NULL COMMENT '계정 활성화 여부',
`created_at` datetime NOT NULL COMMENT '회원 등록날짜',
`created_by` varchar(20) NOT NULL COMMENT '회원 등록자',
`created_by` varchar(320) NOT NULL COMMENT '회원 등록자',
`updated_at` datetime NULL COMMENT '회원 변경날짜',
`updated_by` varchar(20) NULL COMMENT '회원 변경자',
`updated_by` varchar(320) NULL COMMENT '회원 변경자',
PRIMARY KEY (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -1,8 +0,0 @@
package io.beaniejoy.dongnecafe.common.config
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
@Configuration
@EnableJpaAuditing
class AuditingConfig

View File

@@ -1,12 +0,0 @@
package io.beaniejoy.dongnecafe.common.entity
import org.springframework.data.domain.AuditorAware
import org.springframework.stereotype.Component
import java.util.*
@Component
class BaseEntityAuditorAware: AuditorAware<String> {
override fun getCurrentAuditor(): Optional<String> {
return Optional.of("system")
}
}

View File

@@ -5,7 +5,7 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: none # use [service-api] flyway migration
ddl-auto: none # use flyway migration
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect

View File

@@ -0,0 +1,25 @@
package io.beaniejoy.dongnecafe.common.config
import io.beaniejoy.dongnecafe.security.getAuthPrincipal
import mu.KLogging
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.domain.AuditorAware
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
import org.springframework.security.core.context.SecurityContextHolder
import java.util.*
@Configuration
@EnableJpaAuditing
class AuditingConfig {
companion object: KLogging() {
const val SYSTEM = "system"
}
@Bean
fun auditorProvider(): AuditorAware<String> {
return AuditorAware<String> {
Optional.of(SecurityContextHolder.getContext().authentication?.getAuthPrincipal() ?: SYSTEM)
}
}
}

View File

@@ -0,0 +1,21 @@
package io.beaniejoy.dongnecafe.common.entity
import org.springframework.data.annotation.CreatedBy
import org.springframework.data.annotation.LastModifiedBy
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import javax.persistence.Column
import javax.persistence.EntityListeners
import javax.persistence.MappedSuperclass
@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity protected constructor() : BaseTimeEntity() {
@CreatedBy
@Column(updatable = false)
lateinit var createdBy: String
protected set
@LastModifiedBy
lateinit var updatedBy: String
protected set
}

View File

@@ -1,11 +1,10 @@
package io.beaniejoy.dongnecafe.common
package io.beaniejoy.dongnecafe.common.entity
import org.springframework.data.annotation.CreatedBy
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedBy
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime
import javax.persistence.Column
import javax.persistence.EntityListeners
import javax.persistence.MappedSuperclass
@@ -13,18 +12,11 @@ import javax.persistence.MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseTimeEntity protected constructor() {
@CreatedDate
var createdAt: LocalDateTime = LocalDateTime.now()
protected set
@CreatedBy
var createdBy: String = ""
@Column(updatable = false)
lateinit var createdAt: LocalDateTime
protected set
@LastModifiedDate
var updatedAt: LocalDateTime? = null
protected set
@LastModifiedBy
var updatedBy: String? = null
lateinit var updatedAt: LocalDateTime
protected set
}

View File

@@ -1,6 +1,6 @@
package io.beaniejoy.dongnecafe.domain.cafe.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuRegisterRequest
import javax.persistence.*
@@ -11,7 +11,7 @@ class Cafe protected constructor(
address: String,
phoneNumber: String,
description: String,
) : BaseTimeEntity() {
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cafe_id", nullable = false)

View File

@@ -1,11 +1,11 @@
package io.beaniejoy.dongnecafe.domain.cafe.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import javax.persistence.*
@Entity
@Table(name = "cafe_image")
class CafeImage(
class CafeImage protected constructor(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cafe_image_id", nullable = false)
@@ -17,4 +17,4 @@ class CafeImage(
@ManyToOne
@JoinColumn(name = "cafe_id", nullable = false)
val cafe: Cafe
) : BaseTimeEntity()
) : BaseEntity()

View File

@@ -1,6 +1,6 @@
package io.beaniejoy.dongnecafe.domain.cafe.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import io.beaniejoy.dongnecafe.domain.cafe.model.request.MenuOptionRegisterRequest
import java.math.BigDecimal
import javax.persistence.*
@@ -10,7 +10,7 @@ import javax.persistence.*
class CafeMenu protected constructor(
name: String,
price: BigDecimal,
) : BaseTimeEntity() {
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cafe_menu_id", nullable = false)
@@ -33,7 +33,11 @@ class CafeMenu protected constructor(
val menuOptionList: MutableList<MenuOption> = arrayListOf()
companion object {
fun createCafeMenu(name: String, price: BigDecimal, menuOptionRequestList: List<MenuOptionRegisterRequest>): CafeMenu {
fun createCafeMenu(
name: String,
price: BigDecimal,
menuOptionRequestList: List<MenuOptionRegisterRequest>
): CafeMenu {
val menuOptionEntityList = menuOptionRequestList.map { menuOptionRequestDto ->
MenuOption.createMenuOption(
title = menuOptionRequestDto.title,

View File

@@ -1,6 +1,6 @@
package io.beaniejoy.dongnecafe.domain.cafe.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import io.beaniejoy.dongnecafe.domain.cafe.model.request.OptionDetailRegisterRequest
import javax.persistence.*
@@ -8,7 +8,7 @@ import javax.persistence.*
@Table(name = "menu_option")
class MenuOption protected constructor(
title: String
) : BaseTimeEntity() {
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "menu_option_id", nullable = false)

View File

@@ -1,6 +1,6 @@
package io.beaniejoy.dongnecafe.domain.cafe.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import java.math.BigDecimal
import javax.persistence.*
@@ -9,7 +9,7 @@ import javax.persistence.*
class OptionDetail protected constructor(
name: String,
extraPrice: BigDecimal
) : BaseTimeEntity() {
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "option_detail_id", nullable = false)

View File

@@ -1,6 +1,6 @@
package io.beaniejoy.dongnecafe.domain.member.entity
import io.beaniejoy.dongnecafe.common.BaseTimeEntity
import io.beaniejoy.dongnecafe.common.entity.BaseEntity
import io.beaniejoy.dongnecafe.domain.member.constant.RoleType
import javax.persistence.*
@@ -11,7 +11,7 @@ class Member(
password: String,
address: String,
phoneNumber: String
): BaseTimeEntity() {
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id", nullable = false)

View File

@@ -0,0 +1,15 @@
package io.beaniejoy.dongnecafe.security
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.ANONYMOUS_USER
import io.beaniejoy.dongnecafe.security.constant.SecurityConstant.ROLE_ANONYMOUS
import org.springframework.security.core.Authentication
fun Authentication.getAuthPrincipal() : String? {
if (this.isAnonymous()) return null
return this.principal.toString()
}
fun Authentication.isAnonymous(): Boolean {
return this.principal == ANONYMOUS_USER || this.authorities.any { it.authority == ROLE_ANONYMOUS }
}

View File

@@ -3,4 +3,7 @@ package io.beaniejoy.dongnecafe.security.constant
object SecurityConstant {
const val BEARER = "Bearer"
const val WHITESPACE = " "
const val ANONYMOUS_USER = "anonymousUser"
const val ROLE_ANONYMOUS = "ROLE_ANONYMOUS"
}

View File

@@ -1,8 +0,0 @@
package io.beaniejoy.dongnecafe.common.config
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
@Configuration
@EnableJpaAuditing
class AuditingConfig

View File

@@ -21,10 +21,10 @@ class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
return http
// only api 방식 인증 & 인가 적용 위해 csrf & formLogin 비활성화
.csrf().disable()
.formLogin().disable()
// FIXME 임시 permitAll 설정
.authorizeRequests()
.anyRequest().authenticated()
@@ -43,6 +43,7 @@ class SecurityConfig {
.jwtTokenUtils(jwtTokenUtils)
}
// Security Filter 미적용 자원 설정
@Bean
fun webSecurityCustomizer(): WebSecurityCustomizer {
return WebSecurityCustomizer { web ->

View File

@@ -1,13 +0,0 @@
package io.beaniejoy.dongnecafe.common.entity
import org.springframework.data.domain.AuditorAware
import org.springframework.stereotype.Component
import java.util.*
@Component
class BaseEntityAuditorAware: AuditorAware<String> {
override fun getCurrentAuditor(): Optional<String> {
// TODO 추후 사용자 로그인 기능 추가되면 실제 등록한 사용자를 DB에 저장하는 방향으로 수정
return Optional.of("system")
}
}

View File

@@ -24,9 +24,11 @@ class JwtAuthenticationFilter(
val httpRequest = request as HttpServletRequest
log.info { "[JwtAuthenticationFilter][${request.dispatcherType}] uri: ${request.requestURI}" }
// 인증 헤더에 토큰값 없는 경우 pass
getAccessToken(httpRequest)?.let {
jwtTokenUtils.getAuthentication(it)
}?.also {
// 유효한 인증 토큰 존재하는 경우 SecurityContext 토큰값 저장
SecurityContextHolder.getContext().authentication = it
log.info { "Valid Access Token [${it.name}]" }
}
@@ -34,6 +36,10 @@ class JwtAuthenticationFilter(
chain.doFilter(request, response)
}
/**
* 인증 토큰 획득
* Authorization : Bearer [AUTH_TOKEN]
*/
private fun getAccessToken(request: HttpServletRequest): String? {
val bearer = request.getHeader(HttpHeaders.AUTHORIZATION)
?: return null

View File

@@ -1,7 +1,6 @@
package io.beaniejoy.dongnecafe.domain.cafe.repository
import io.beaniejoy.dongnecafe.common.config.AuditingConfig
import io.beaniejoy.dongnecafe.common.entity.BaseEntityAuditorAware
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils
import mu.KLogging
@@ -17,7 +16,6 @@ import org.springframework.data.repository.findByIdOrNull
@DataJpaTest(
includeFilters = [
ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [AuditingConfig::class]),
ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = [BaseEntityAuditorAware::class])
]
)
internal class CafeRepositoryTest {

View File

@@ -87,13 +87,13 @@ internal class CafeServiceTest {
fun update_cafe_test() {
// given
val (name, address, phoneNumber, description, cafeMenuList) = CafeTestUtils.createCafeRegisterRequest()
val cafe = Cafe.createCafe(
name = name!!,
address = address!!,
phoneNumber = phoneNumber!!,
description = description!!,
cafeMenuRequestList = cafeMenuList
)
// val cafe = Cafe.createCafe(
// name = name!!,
// address = address!!,
// phoneNumber = phoneNumber!!,
// description = description!!,
// cafeMenuRequestList = cafeMenuList
// )
val cafeId = 50L
// TODO 'findByIdOrNull'은 kotlin test 라이브러리 필요한 듯

View File

@@ -1,6 +1,4 @@
spring:
flyway:
enabled: false
jpa:
properties:
hibernate:
@@ -9,7 +7,7 @@ spring:
logging:
level:
org.hibernate.SQL: debug
org.hibernate.SQL: info # ionShutdownHook logging issue (in 'DEBUG' level)
org.hibernate.type: trace # 실제 sql 쿼리의 parameter 값을 확인하고자 함
jwt:

View File

@@ -2,4 +2,3 @@ rootProject.name = "dongne-cafe-api"
include("dongne-common")
include("dongne-service-api")
include("dongne-account-api")
include("db")