From f9920530fe19229853f5f2748eb5a69f9017f815 Mon Sep 17 00:00:00 2001
From: belljun3395 <195850@jnu.ac.kr>
Date: Fri, 28 Jun 2024 01:58:57 +0900
Subject: [PATCH 1/2] =?UTF-8?q?[Refactor/#117]=20=EB=B0=B0=EC=B9=98=20Writ?=
=?UTF-8?q?er=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20(#118)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: progress와 day가 달라 생기는 문제 해결
* feat: 마지막 아티클을 전송후 구독 해지하는 로직 추가
* !chore: 임시 article.html 추가
* feat: 메일 전송 성공한 멤버만 필터링
* refactor: 1b8402419dd2faa39699057b7344edfb1cde5531 미반영 테스트 수정
---
.../writer/WorkBookSubscriberWriter.kt | 37 ++++-
.../WorkBookSubscriberWriterTestSetHelper.kt | 2 +-
.../src/main/resources/templates/article.html | 126 ++++++++++++++++++
3 files changed, 162 insertions(+), 3 deletions(-)
create mode 100644 email/src/main/resources/templates/article.html
diff --git a/batch/src/main/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriter.kt b/batch/src/main/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriter.kt
index 540924bbd..fd5836213 100644
--- a/batch/src/main/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriter.kt
+++ b/batch/src/main/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriter.kt
@@ -11,9 +11,11 @@ import jooq.jooq_dsl.tables.MappingWorkbookArticle
import jooq.jooq_dsl.tables.Member
import jooq.jooq_dsl.tables.Subscription
import org.jooq.DSLContext
+import org.jooq.impl.DSL
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
+import java.time.LocalDateTime
data class MemberReceiveArticle(
val workbookId: Long,
@@ -70,6 +72,7 @@ class WorkBookSubscriberWriter(
/** 구독자들이 구독한 학습지 ID와 구독자의 학습지 구독 진행률을 기준으로 구독자가 받을 학습지 정보를 조회한다.*/
val memberReceiveArticles = targetWorkBookProgress.keys.stream().map { workbookId ->
+ val dayCols = targetWorkBookProgress[workbookId]!!.stream().map { it + 1L }.toList()
// todo check refactoring
dslContext.select(
mappingWorkbookArticleT.WORKBOOK_ID.`as`(MemberReceiveArticle::workbookId.name),
@@ -80,7 +83,7 @@ class WorkBookSubscriberWriter(
.where(
mappingWorkbookArticleT.WORKBOOK_ID.eq(workbookId)
)
- .and(mappingWorkbookArticleT.DAY_COL.`in`(targetWorkBookProgress[workbookId]!!))
+ .and(mappingWorkbookArticleT.DAY_COL.`in`(dayCols))
.and(mappingWorkbookArticleT.DELETED_AT.isNull)
.fetchInto(MemberReceiveArticle::class.java)
}.flatMap { it.stream() }.toList().let {
@@ -103,7 +106,7 @@ class WorkBookSubscriberWriter(
// todo check !! target is not null
val emailServiceArgs = items.map {
val toEmail = memberEmailRecords[it.memberId]!!
- val memberArticle = memberReceiveArticles.getByWorkBookIdAndDayCol(it.targetWorkBookId, it.progress)
+ val memberArticle = memberReceiveArticles.getByWorkBookIdAndDayCol(it.targetWorkBookId, it.progress + 1)
val articleContent = articleContentsMap[memberArticle.articleId]!!
return@map it.memberId to SendArticleEmailArgs(toEmail, ARTICLE_SUBJECT, ARTICLE_TEMPLATE, articleContent, ARTICLE_STYLE)
}
@@ -120,6 +123,29 @@ class WorkBookSubscriberWriter(
}
}
+ /** 워크북 마지막 학습지 DAY_COL을 조회한다 */
+ val lastDayCol = dslContext.select(
+ mappingWorkbookArticleT.WORKBOOK_ID,
+ DSL.max(mappingWorkbookArticleT.DAY_COL)
+ )
+ .from(mappingWorkbookArticleT)
+ .where(mappingWorkbookArticleT.WORKBOOK_ID.`in`(targetWorkBookIds))
+ .and(mappingWorkbookArticleT.DELETED_AT.isNull)
+ .groupBy(mappingWorkbookArticleT.WORKBOOK_ID)
+ .fetch()
+ .intoMap(mappingWorkbookArticleT.WORKBOOK_ID, DSL.max(mappingWorkbookArticleT.DAY_COL))
+
+ /** 마지막 학습지를 받은 구독자들의 ID를 필터링한다.*/
+ val receiveLastDayMembers = items.filter {
+ it.targetWorkBookId in lastDayCol.keys
+ }.filter {
+ (it.progress.toInt() + 1) == lastDayCol[it.targetWorkBookId]
+ }.map {
+ it.memberId
+ }.filter {
+ memberSuccessRecords[it] == true
+ }
+
val successMemberIds = memberSuccessRecords.filter { it.value }.keys
/** 이메일 전송에 성공한 구독자들의 진행률을 업데이트한다.*/
dslContext.update(subscriptionT)
@@ -128,6 +154,13 @@ class WorkBookSubscriberWriter(
.and(subscriptionT.TARGET_WORKBOOK_ID.`in`(targetWorkBookIds))
.execute()
+ /** 마지막 학습지를 받은 구독자들은 구독을 해지한다.*/
+ dslContext.update(subscriptionT)
+ .set(subscriptionT.DELETED_AT, LocalDateTime.now())
+ .where(subscriptionT.MEMBER_ID.`in`(receiveLastDayMembers))
+ .and(subscriptionT.TARGET_WORKBOOK_ID.`in`(targetWorkBookIds))
+ .execute()
+
return if (failRecords.isNotEmpty()) {
mapOf("success" to memberSuccessRecords, "fail" to failRecords)
} else {
diff --git a/batch/src/test/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriterTestSetHelper.kt b/batch/src/test/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriterTestSetHelper.kt
index 5db4d1d9f..cf9c48d8a 100644
--- a/batch/src/test/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriterTestSetHelper.kt
+++ b/batch/src/test/kotlin/com/few/batch/service/article/writer/WorkBookSubscriberWriterTestSetHelper.kt
@@ -88,7 +88,7 @@ class WorkBookSubscriberWriterTestSetHelper(
.where(
MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.WORKBOOK_ID.eq(workbookId)
)
- .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.eq(it[Subscription.SUBSCRIPTION.PROGRESS].toInt()))
+ .and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DAY_COL.eq(it[Subscription.SUBSCRIPTION.PROGRESS].toInt() + 1))
.and(MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE.DELETED_AT.isNull)
.fetchOneInto(String::class.java)
diff --git a/email/src/main/resources/templates/article.html b/email/src/main/resources/templates/article.html
new file mode 100644
index 000000000..7d8c63623
--- /dev/null
+++ b/email/src/main/resources/templates/article.html
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
From 854f3ef2fd7bc829633138b57ff72fbd3b63816c Mon Sep 17 00:00:00 2001
From: belljun3395 <195850@jnu.ac.kr>
Date: Mon, 1 Jul 2024 13:11:11 +0900
Subject: [PATCH 2/2] =?UTF-8?q?Fix/#115=20:=20=EB=B0=B0=ED=8F=AC=20?=
=?UTF-8?q?=EA=B3=BC=EC=A0=95=EC=97=90=EC=84=9C=20=EB=B0=9C=EA=B2=AC?=
=?UTF-8?q?=ED=95=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#116)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: 사용하지 않는 Mapper 삭제
* fix: ContentsJsonMapper의 키값 id -> number 수정
* refactor: @EnableWebMvc 제거
* refactor: contentsJsonMapper로 변환 구현하도록 수정
* refactor: SPRINGDOC 버전 1.6.3 -> 2.0.2
* feat: api 모듈에 jooq 의존성 추가
* refactor: openapi 의존성 webflux 버전으로 수정
* refactor: openapi3, postman의 url을 변수를 통해 받을 수 있도록 수정
* refactor: buildDockerImage 태스크 수행시 필요 변수 추가
* fix: 스키마 명 대문자로 수정
* fix: web 의존성 webflux -> web으로 변경
* fix: Swagger 생성을 보장할 수 있도록 수정
* feat: CORS 관련 설정 추가
* refactor: Jooq 관련 공통 의존성 최상의 gradle로 통일
* feat: jooqCodegenAll 테스크 추가
* refactor: 단일 조회시 예외 발생이 아니라 XXX? 값을 반환 하도록 수정
* fix: 변경된 jooqCodegen 반영
* fix: 76c7e3a159a81bd673e276b679c880762c74a696 미반영 테스트 수정
* feat: 임시 batch-cron 구현
* refactor: build시 test가 수행되어 순서 변경 및 중복 삭제
* feat: root debug 로깅 레벨 설정
* refactor: 매 오전 8시에 batch-cron 실행 되도록 수정
* chore: 주석 추가 및 테스크 이름 수정
* feat: WebConfig에 @EnableWebMvc 추가 및 resourceHandlers 추가
* refactor: SPRINGDOC 버전 수정 2.0.2 -> 1.6.3
* chore: EMAIL_PASSWORD 통일 및 local에 기본값 추가
* refactor: openapi-ui 의존성 수정 webflux -> openapi
* fix: EMAIL_PASSWORD 통일 액션에 반영
* fix: openapi 의존성 오타 수정
* fix: generateSwaggerUIApi가 수행되고 generateStaticSwaggerUIApi가 수행되도록 수정
* refactor: 도커 이미지 빌드 과정 수정
* refactor: 시간 기록 추가 및 성공 여부 status 수정
---
.github/workflows/batch-cron.yml | 18 +++
.github/workflows/code-ci.yml | 20 ++-
.github/workflows/integration-test.yml | 8 +-
api-repo/build.gradle.kts | 118 -----------------
.../few/api/repo/dao/article/ArticleDao.kt | 6 +-
.../com/few/api/repo/dao/member/MemberDao.kt | 9 +-
.../{MemberRecord.kt => MemberIdRecord.kt} | 2 +-
.../member/support/WriterDescriptionMapper.kt | 22 ----
.../few/api/repo/dao/problem/ProblemDao.kt | 8 +-
.../dao/problem/support/ContentsJsonMapper.kt | 2 +-
.../dao/problem/support/ContentsMapper.kt | 23 ----
.../few/api/repo/dao/workbook/WorkbookDao.kt | 3 +-
.../api/repo/dao/article/ArticleDaoTest.kt | 4 +-
.../support/WriterDescriptionMapperTest.kt | 60 ---------
.../api/repo/dao/problem/ProblemDaoTest.kt | 2 +-
.../problem/support/ContentsJsonMapperTest.kt | 4 +-
.../dao/problem/support/ContentsMapperTest.kt | 72 ----------
.../api/repo/dao/workbook/WorkbookDaoTest.kt | 2 +-
api/build.gradle.kts | 50 +++++--
.../kotlin/com/few/api/config/ApiConfig.kt | 2 -
.../service/BrowseArticleProblemsService.kt | 2 +-
.../article/usecase/ReadArticleUseCase.kt | 2 +-
.../problem/usecase/CheckProblemUseCase.kt | 4 +-
.../problem/usecase/ReadProblemUseCase.kt | 13 +-
.../subscription/service/MemberService.kt | 5 +-
.../usecase/SubscribeWorkbookUseCase.kt | 5 +-
.../usecase/UnsubscribeAllUseCase.kt | 2 +-
.../usecase/UnsubscribeWorkbookUseCase.kt | 2 +-
.../usecase/ReadWorkBookArticleUseCase.kt | 2 +-
.../workbook/usecase/ReadWorkbookUseCase.kt | 2 +-
.../com/few/api/web/config/WebConfig.kt | 26 ++++
api/src/main/resources/application.yml | 3 +
batch/build.gradle.kts | 111 ----------------
.../article/BatchSendArticleEmailService.kt | 16 ++-
build.gradle.kts | 124 ++++++++++++++++++
.../entity/V1.00.0.0__draft_table_design.sql | 22 ++--
.../entity/V1.00.0.1__add_column.sql | 2 +-
.../V1.00.0.2__add_subscription_progress.sql | 2 +-
.../V1.00.0.3__batch_call_execution_table.sql | 2 +-
.../resources/application-email-local.yml | 2 +-
email/src/test/resources/application-test.yml | 2 +-
41 files changed, 291 insertions(+), 495 deletions(-)
create mode 100644 .github/workflows/batch-cron.yml
rename api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/{MemberRecord.kt => MemberIdRecord.kt} (71%)
delete mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapper.kt
delete mode 100644 api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsMapper.kt
delete mode 100644 api-repo/src/test/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapperTest.kt
delete mode 100644 api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsMapperTest.kt
create mode 100644 api/src/main/kotlin/com/few/api/web/config/WebConfig.kt
diff --git a/.github/workflows/batch-cron.yml b/.github/workflows/batch-cron.yml
new file mode 100644
index 000000000..78a5aceac
--- /dev/null
+++ b/.github/workflows/batch-cron.yml
@@ -0,0 +1,18 @@
+name: Batch Cron
+
+on:
+ schedule:
+ # 매 오전 8시에 실행
+ - cron: '0 8 * * *'
+ workflow_dispatch:
+
+jobs:
+ code-review:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ run: |
+ curl -X 'POST' \
+ 'https://api.fewletter.site/batch/article' \
+ -H 'accept: application/json' \
+ -H 'Content-Type: application/json'
diff --git a/.github/workflows/code-ci.yml b/.github/workflows/code-ci.yml
index 14e09dc2d..e9e382e73 100644
--- a/.github/workflows/code-ci.yml
+++ b/.github/workflows/code-ci.yml
@@ -10,7 +10,7 @@ permissions:
env:
RELEASE_VERSION: ${{ github.sha }}
- MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
+ EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
jobs:
build:
@@ -25,19 +25,27 @@ jobs:
- name: Jooq Code Generation
run: |
- ./gradlew --info jooqCodegen
+ ./gradlew --info jooqCodegenAll
+
+ - name: Test with Gradle
+ run: |
+ ./gradlew --info test
- name: Build with Gradle
run: |
- ./gradlew --info api:build
+ ./gradlew --info api:build -x test
- - name: Test with Gradle
+ - name: Generate OpenAPI3
run: |
- ./gradlew --info test
+ ./gradlew --info api:openapi3 -PserverUrl=https://api.fewletter.site
+
+ - name: Generate Swagger
+ run: |
+ ./gradlew --info api:generateStaticSwaggerUIApi
- name : Docker Login
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker Image
run: |
- ./gradlew --info api:buildDockerImage
+ ./gradlew --info api:buildDockerImage -PimageName=fewletter/api -PreleaseVersion=${{ env.RELEASE_VERSION }}
diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml
index feb063486..ac1462b88 100644
--- a/.github/workflows/integration-test.yml
+++ b/.github/workflows/integration-test.yml
@@ -9,7 +9,7 @@ permissions:
contents: read
env:
- MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}
+ EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
jobs:
build:
@@ -24,11 +24,7 @@ jobs:
- name: Jooq Code Generation
run: |
- ./gradlew --info jooqCodegen
-
- - name: Build with Gradle
- run: |
- ./gradlew --info api:build
+ ./gradlew --info jooqCodegenAll
- name: Test with Gradle
run: |
diff --git a/api-repo/build.gradle.kts b/api-repo/build.gradle.kts
index 1a49821d5..a29bcaf5c 100644
--- a/api-repo/build.gradle.kts
+++ b/api-repo/build.gradle.kts
@@ -6,21 +6,6 @@ tasks.getByName("jar") {
enabled = true
}
-plugins {
- /** jooq */
- id("org.jooq.jooq-codegen-gradle") version DependencyVersion.JOOQ
-}
-
-sourceSets {
- main {
- java {
- val mainDir = "src/main/kotlin"
- val jooqDir = "src/generated"
- srcDirs(mainDir, jooqDir)
- }
- }
-}
-
dependencies {
/** module */
api(project(":data"))
@@ -33,110 +18,7 @@ dependencies {
implementation("org.flywaydb:flyway-core:${DependencyVersion.FLYWAY}")
implementation("org.flywaydb:flyway-mysql")
- /** jooq */
- implementation("org.springframework.boot:spring-boot-starter-jooq")
- implementation("org.jooq:jooq:${DependencyVersion.JOOQ}")
- implementation("org.jooq:jooq-meta:${DependencyVersion.JOOQ}")
- implementation("org.jooq:jooq-codegen:${DependencyVersion.JOOQ}")
- jooqCodegen("org.jooq:jooq-meta-extensions:${DependencyVersion.JOOQ}")
-
/** test container */
implementation(platform("org.testcontainers:testcontainers-bom:${DependencyVersion.TEST_CONTAINER}"))
testImplementation("org.testcontainers:mysql")
-}
-
-/** copy data migration */
-tasks.create("copyDataMigration") {
- doLast {
- val root = rootDir
- val flyWayResourceDir = "/db/migration/entity"
- val dataMigrationDir = "$root/data/$flyWayResourceDir"
- File(dataMigrationDir).walkTopDown().forEach {
- if (it.isFile) {
- it.copyTo(
- File("${project.projectDir}/src/main/resources$flyWayResourceDir/${it.name}"),
- true
- )
- }
- }
- }
-}
-
-/** copy data migration before compile kotlin */
-tasks.getByName("compileKotlin") {
- dependsOn("copyDataMigration")
-}
-
-/** jooq codegen after copy data migration */
-tasks.getByName("jooqCodegen") {
- dependsOn("copyDataMigration")
-}
-
-jooq {
- configuration {
- generator {
- database {
- name = "org.jooq.meta.extensions.ddl.DDLDatabase"
- properties {
- // Specify the location of your SQL script.
- // You may use ant-style file matching, e.g. /path/**/to/*.sql
- //
- // Where:
- // - ** matches any directory subtree
- // - * matches any number of characters in a directory / file name
- // - ? matches a single character in a directory / file name
- property {
- key = "scripts"
- value = "src/main/resources/db/migration/**/*.sql"
- }
-
- // The sort order of the scripts within a directory, where:
- //
- // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default)
- // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0
- // - flyway: sorts files the same way as flyway does
- // - none: doesn't sort directory contents after fetching them from the directory
- property {
- key = "sort"
- value = "flyway"
- }
-
- // The default schema for unqualified objects:
- //
- // - public: all unqualified objects are located in the PUBLIC (upper case) schema
- // - none: all unqualified objects are located in the default schema (default)
- //
- // This configuration can be overridden with the schema mapping feature
- property {
- key = "unqualifiedSchema"
- value = "none"
- }
-
- // The default name case for unquoted objects:
- //
- // - as_is: unquoted object names are kept unquoted
- // - upper: unquoted object names are turned into upper case (most databases)
- // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL)
- property {
- key = "defaultNameCase"
- value = "as_is"
- }
- }
- }
-
- generate {
- isDeprecated = false
- isRecords = true
- isImmutablePojos = true
- isFluentSetters = true
- isJavaTimeTypes = true
- }
-
- target {
- packageName = "jooq.jooq_dsl"
- directory = "src/generated"
- encoding = "UTF-8"
- }
- }
- }
}
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt
index 8852a8049..1c103cd78 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/article/ArticleDao.kt
@@ -16,7 +16,7 @@ import org.springframework.stereotype.Repository
class ArticleDao(
private val dslContext: DSLContext
) {
- fun selectArticleRecord(query: SelectArticleRecordQuery): SelectArticleRecord {
+ fun selectArticleRecord(query: SelectArticleRecordQuery): SelectArticleRecord? {
val articleId = query.articleId
return dslContext.select(
@@ -33,10 +33,9 @@ class ArticleDao(
.where(ArticleMst.ARTICLE_MST.ID.eq(articleId))
.and(ArticleMst.ARTICLE_MST.DELETED_AT.isNull)
.fetchOneInto(SelectArticleRecord::class.java)
- ?: throw IllegalArgumentException("cannot find article record by articleId: $articleId")
}
- fun selectWorkBookArticleRecord(query: SelectWorkBookArticleRecordQuery): SelectWorkBookArticleRecord {
+ fun selectWorkBookArticleRecord(query: SelectWorkBookArticleRecordQuery): SelectWorkBookArticleRecord? {
val articleMst = ArticleMst.ARTICLE_MST
val articleIfo = ArticleIfo.ARTICLE_IFO
val mappingWorkbookArticle = MappingWorkbookArticle.MAPPING_WORKBOOK_ARTICLE
@@ -62,7 +61,6 @@ class ArticleDao(
.where(articleMst.ID.eq(articleId))
.and(articleMst.DELETED_AT.isNull)
.fetchOneInto(SelectWorkBookArticleRecord::class.java)
- ?: throw IllegalArgumentException("cannot find $workbookId article record by articleId: $articleId")
}
fun selectWorkbookMappedArticleRecords(query: SelectWorkbookMappedArticleRecordsQuery): List {
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt
index 7d23ae622..c5585d002 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/MemberDao.kt
@@ -4,7 +4,7 @@ import com.few.api.repo.dao.member.command.InsertMemberCommand
import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery
import com.few.api.repo.dao.member.query.SelectWriterQuery
import com.few.api.repo.dao.member.query.SelectWritersQuery
-import com.few.api.repo.dao.member.record.MemberRecord
+import com.few.api.repo.dao.member.record.MemberIdRecord
import com.few.api.repo.dao.member.record.WriterRecord
import com.few.data.common.code.MemberType
import jooq.jooq_dsl.tables.Member
@@ -47,17 +47,16 @@ class MemberDao(
.fetchInto(WriterRecord::class.java)
}
- fun selectMemberByEmail(query: SelectMemberByEmailQuery): MemberRecord {
+ fun selectMemberByEmail(query: SelectMemberByEmailQuery): MemberIdRecord? {
val email = query.email
return dslContext.select(
- Member.MEMBER.ID.`as`(MemberRecord::memberId.name)
+ Member.MEMBER.ID.`as`(MemberIdRecord::memberId.name)
)
.from(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(email))
.and(Member.MEMBER.DELETED_AT.isNull)
- .fetchOneInto(MemberRecord::class.java)
- ?: throw IllegalArgumentException("cannot find member record by email: $email")
+ .fetchOneInto(MemberIdRecord::class.java)
}
fun insertMember(command: InsertMemberCommand): Long {
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberRecord.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberIdRecord.kt
similarity index 71%
rename from api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberRecord.kt
rename to api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberIdRecord.kt
index b98d9c86f..0786d75d8 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberRecord.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/record/MemberIdRecord.kt
@@ -1,5 +1,5 @@
package com.few.api.repo.dao.member.record
-data class MemberRecord(
+data class MemberIdRecord(
val memberId: Long
)
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapper.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapper.kt
deleted file mode 100644
index dfebab7f3..000000000
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapper.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.few.api.repo.dao.member.support
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.registerKotlinModule
-import org.springframework.stereotype.Component
-
-@Component
-class WriterDescriptionMapper(
- private val objectMapper: ObjectMapper
-) {
- init {
- objectMapper.registerKotlinModule()
- }
-
- fun toJson(writerDescription: WriterDescription): String {
- return objectMapper.writeValueAsString(writerDescription)
- }
-
- fun toObject(value: String): WriterDescription {
- return objectMapper.readValue(value, WriterDescription::class.java)
- }
-}
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
index b65ed833d..236e78a25 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/ProblemDao.kt
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Component
class ProblemDao(
private val dslContext: DSLContext
) {
- fun selectProblemContents(query: SelectProblemQuery): SelectProblemRecord {
+ fun selectProblemContents(query: SelectProblemQuery): SelectProblemRecord? {
return dslContext.select(
Problem.PROBLEM.ID.`as`(SelectProblemRecord::id.name),
Problem.PROBLEM.TITLE.`as`(SelectProblemRecord::title.name),
@@ -26,10 +26,9 @@ class ProblemDao(
.where(Problem.PROBLEM.ID.eq(query.problemId))
.and(Problem.PROBLEM.DELETED_AT.isNull)
.fetchOneInto(SelectProblemRecord::class.java)
- ?: throw RuntimeException("Problem with ID ${query.problemId} not found") // TODO: 에러 표준화
}
- fun selectProblemAnswer(query: SelectProblemAnswerQuery): SelectProblemAnswerRecord {
+ fun selectProblemAnswer(query: SelectProblemAnswerQuery): SelectProblemAnswerRecord? {
return dslContext.select(
Problem.PROBLEM.ID.`as`(SelectProblemAnswerRecord::id.name),
Problem.PROBLEM.ANSWER.`as`(SelectProblemAnswerRecord::answer.name),
@@ -39,10 +38,9 @@ class ProblemDao(
.where(Problem.PROBLEM.ID.eq(query.problemId))
.and(Problem.PROBLEM.DELETED_AT.isNull)
.fetchOneInto(SelectProblemAnswerRecord::class.java)
- ?: throw RuntimeException("Problem Answer with ID ${query.problemId} not found") // TODO: 에러 표준화
}
- fun selectProblemsByArticleId(query: SelectProblemsByArticleIdQuery): ProblemIdsRecord {
+ fun selectProblemsByArticleId(query: SelectProblemsByArticleIdQuery): ProblemIdsRecord? {
val articleId = query.articleId
return dslContext.select()
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapper.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapper.kt
index dba9e8b07..1cb22ae52 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapper.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapper.kt
@@ -16,7 +16,7 @@ class ContentsJsonMapper(
val contents = objectMapper.readTree(value).get("contents")
val contentList = mutableListOf()
contents.forEach {
- contentList.add(Content(it.get("id").asLong(), it.get("content").asText()))
+ contentList.add(Content(it.get("number").asLong(), it.get("content").asText()))
}
return Contents(contentList)
}
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsMapper.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsMapper.kt
deleted file mode 100644
index f86b4579f..000000000
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/problem/support/ContentsMapper.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.few.api.repo.dao.problem.support
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.springframework.stereotype.Component
-
-@Component
-class ContentsMapper(
- private val objectMapper: ObjectMapper
-) {
-
- fun toJson(contents: Contents): String {
- return objectMapper.writeValueAsString(contents)
- }
-
- fun toObject(value: String): Contents {
- val contents = objectMapper.readTree(value).get("contents")
- val contentList = mutableListOf()
- contents.forEach {
- contentList.add(Content(it.get("id").asLong(), it.get("content").asText()))
- }
- return Contents(contentList)
- }
-}
\ No newline at end of file
diff --git a/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt
index 4e0d5fd2a..bd81c9c5a 100644
--- a/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt
+++ b/api-repo/src/main/kotlin/com/few/api/repo/dao/workbook/WorkbookDao.kt
@@ -10,7 +10,7 @@ import org.springframework.stereotype.Repository
class WorkbookDao(
private val dslContext: DSLContext
) {
- fun selectWorkBook(query: SelectWorkBookRecordQuery): SelectWorkBookRecord {
+ fun selectWorkBook(query: SelectWorkBookRecordQuery): SelectWorkBookRecord? {
return dslContext.select(
Workbook.WORKBOOK.ID.`as`(SelectWorkBookRecord::id.name),
Workbook.WORKBOOK.TITLE.`as`(SelectWorkBookRecord::title.name),
@@ -23,6 +23,5 @@ class WorkbookDao(
.where(Workbook.WORKBOOK.ID.eq(query.id))
.and(Workbook.WORKBOOK.DELETED_AT.isNull)
.fetchOneInto(SelectWorkBookRecord::class.java)
- ?: throw RuntimeException("WorkBook with ID ${query.id} not found")
}
}
\ No newline at end of file
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/article/ArticleDaoTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/article/ArticleDaoTest.kt
index 3a34032ca..72b78e483 100644
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/article/ArticleDaoTest.kt
+++ b/api-repo/src/test/kotlin/com/few/api/repo/dao/article/ArticleDaoTest.kt
@@ -57,7 +57,7 @@ class ArticleDaoTest : JooqTestSpec() {
}
// then
- assertNotNull(result)
+ assertNotNull(result!!)
assertEquals(1L, result.articleId)
assertEquals(1L, result.writerId)
assertEquals(URL("http://localhost:8080/image1.jpg"), result.mainImageURL)
@@ -79,7 +79,7 @@ class ArticleDaoTest : JooqTestSpec() {
}
// then
- assertNotNull(result)
+ assertNotNull(result!!)
assertEquals(1L, result.articleId)
assertEquals(1L, result.writerId)
assertEquals(URL("http://localhost:8080/image1.jpg"), result.mainImageURL)
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapperTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapperTest.kt
deleted file mode 100644
index 49bc60a4f..000000000
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/member/support/WriterDescriptionMapperTest.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.few.api.repo.dao.member.support
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.junit.jupiter.api.Assertions.*
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-
-import java.net.URL
-
-@SpringBootTest(classes = [ObjectMapper::class])
-class WriterDescriptionMapperTest {
-
- @Autowired
- private lateinit var objectMapper: ObjectMapper
- private lateinit var writerDescriptionMapper: WriterDescriptionMapper
-
- @BeforeEach
- fun setUp() {
- writerDescriptionMapper = WriterDescriptionMapper(objectMapper)
- }
-
- @Test
- fun `WriterDescription을 Json 형식으로 변환합니다`() {
- // Given
- val writerDescription = WriterDescription(
- name = "writer",
- url = URL("http://localhost:8080/writers/url")
- )
-
- // When
- val json = writerDescriptionMapper.toJson(writerDescription)
-
- // Then
- assertNotNull(json)
- assertTrue(json.isNotBlank())
- assertTrue(json.contains("writer"))
- assertTrue(json.contains("http://localhost:8080/writers/url"))
- }
-
- @Test
- fun `Json 형식의 WriterDescription을 WriterDescription으로 변환합니다`() {
- // Given
- val json = """
- {
- "name": "writer",
- "url": "http://localhost:8080/writers/url"
- }
- """.trimIndent()
-
- // When
- val writerDescription = writerDescriptionMapper.toObject(json)
-
- // Then
- assertNotNull(writerDescription)
- assertEquals("writer", writerDescription.name)
- assertEquals(URL("http://localhost:8080/writers/url"), writerDescription.url)
- }
-}
\ No newline at end of file
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/ProblemDaoTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/ProblemDaoTest.kt
index aca57429f..b0ecb0315 100644
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/ProblemDaoTest.kt
+++ b/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/ProblemDaoTest.kt
@@ -59,7 +59,7 @@ class ProblemDaoTest : JooqTestSpec() {
val result = problemDao.selectProblemsByArticleId(query)
// then
- assertNotNull(result)
+ assertNotNull(result!!)
assertEquals(1, result.problemIds.size)
assertEquals(1, result.problemIds[0])
}
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapperTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapperTest.kt
index fd3fa011b..8e6cbc754 100644
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapperTest.kt
+++ b/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsJsonMapperTest.kt
@@ -35,11 +35,11 @@ class ContentsJsonMapperTest {
{
"contents": [
{
- "id": 1,
+ "number": 1,
"content": "this is number one"
},
{
- "id": 2,
+ "number": 2,
"content": "this is number two"
}
]
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsMapperTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsMapperTest.kt
deleted file mode 100644
index 3f9866dba..000000000
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/problem/support/ContentsMapperTest.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.few.api.repo.dao.problem.support
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.junit.jupiter.api.Assertions.*
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-
-@SpringBootTest(classes = [ObjectMapper::class])
-class ContentsMapperTest {
-
- @Autowired
- private lateinit var objectMapper: ObjectMapper
-
- private lateinit var contentsMapper: ContentsMapper
-
- @BeforeEach
- fun setUp() {
- contentsMapper = ContentsMapper(objectMapper)
- }
-
- @Test
- fun `Contents를 Json 형식으로 변환합니다`() {
- // Given
- val contents = Contents(
- contents = listOf(
- Content(1L, "this is number one"),
- Content(2L, "this is number two")
- )
- )
-
- // When
- val json = contentsMapper.toJson(contents)
-
- // Then
- assertNotNull(json)
- assertTrue(json.isNotBlank())
- assertTrue(json.contains("this is number one"))
- assertTrue(json.contains("this is number two"))
- }
-
- @Test
- fun `Json 형식의 Contents를 Contents으로 변환합니다`() {
- // Given
- val json = """
- {
- "contents": [
- {
- "id": 1,
- "content": "this is number one"
- },
- {
- "id": 2,
- "content": "this is number two"
- }
- ]
- }
- """.trimIndent()
-
- // When
- val contents = contentsMapper.toObject(json)
-
- // Then
- assertNotNull(contents)
- assertEquals(2, contents.contents.size)
- assertEquals(1L, contents.contents[0].number)
- assertEquals("this is number one", contents.contents[0].content)
- assertEquals(2L, contents.contents[1].number)
- assertEquals("this is number two", contents.contents[1].content)
- }
-}
\ No newline at end of file
diff --git a/api-repo/src/test/kotlin/com/few/api/repo/dao/workbook/WorkbookDaoTest.kt b/api-repo/src/test/kotlin/com/few/api/repo/dao/workbook/WorkbookDaoTest.kt
index 2f5554622..a1ac6c8fa 100644
--- a/api-repo/src/test/kotlin/com/few/api/repo/dao/workbook/WorkbookDaoTest.kt
+++ b/api-repo/src/test/kotlin/com/few/api/repo/dao/workbook/WorkbookDaoTest.kt
@@ -41,7 +41,7 @@ class WorkbookDaoTest : JooqTestSpec() {
}
// then
- assertNotNull(result)
+ assertNotNull(result!!)
assertEquals(1L, result.id)
assertEquals("title1", result.title)
assertEquals(URL("http://localhost:8080/image1.jpg"), result.mainImageUrl)
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index 3ee3f221c..d2a00faa0 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -1,4 +1,5 @@
import org.hidetake.gradle.swagger.generator.GenerateSwaggerUI
+import java.util.Random
dependencies {
/** module */
@@ -6,7 +7,7 @@ dependencies {
implementation(project(":batch"))
/** spring starter */
- implementation("org.springframework.boot:spring-boot-starter-webflux")
+ implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
/** swagger & restdocs */
@@ -25,9 +26,17 @@ plugins {
id("org.hidetake.swagger.generator") version DependencyVersion.SWAGGER_GENERATOR
}
+val serverUrl = project.hasProperty("serverUrl").let {
+ if (it) {
+ project.property("serverUrl") as String
+ } else {
+ "http://localhost:8080"
+ }
+}
+
/** convert snippet to swagger */
openapi3 {
- this.setServer("http://localhost:8080") // todo refactor to use setServers
+ this.setServer(serverUrl)
title = project.name
version = project.version.toString()
format = "yaml"
@@ -40,24 +49,29 @@ openapi3 {
postman {
title = project.name
version = project.version.toString()
- baseUrl = "http://localhost:8080"
+ baseUrl = serverUrl
outputDirectory = "src/main/resources/static/"
outputFileNamePrefix = "postman"
}
+/** generate swagger ui */
swaggerSources {
+ /** generateSwaggerUIApi */
register("api") {
setInputFile(file("$projectDir/src/main/resources/static/openapi3.yaml"))
}
}
-tasks.withType(GenerateSwaggerUI::class) {
- dependsOn("openapi3")
-}
-
-tasks.register("generateApiSwaggerUI", Copy::class) {
- dependsOn("generateSwaggerUI")
+/**
+ * generate static swagger ui
+ * need snippet to generate swagger ui
+ * */
+tasks.register("generateStaticSwaggerUIApi", Copy::class) {
+ /** generateSwaggerUIApi */
+ dependsOn("generateSwaggerUIApi")
val generateSwaggerUISampleTask = tasks.named("generateSwaggerUIApi", GenerateSwaggerUI::class).get()
+
+ /** copy */
from(generateSwaggerUISampleTask.outputDir)
into("$projectDir/src/main/resources/static/docs/swagger-ui")
}
@@ -66,20 +80,19 @@ val imageName = project.hasProperty("imageName").let {
if (it) {
project.property("imageName") as String
} else {
- "api"
+ "fewletter/api"
}
}
val releaseVersion = project.hasProperty("releaseVersion").let {
if (it) {
project.property("releaseVersion") as String
} else {
- "latest"
+ Random().nextInt(90000) + 10000
}
}
tasks.register("buildDockerImage") {
dependsOn("bootJar")
- dependsOn("generateApiSwaggerUI")
doLast {
exec {
@@ -94,7 +107,18 @@ tasks.register("buildDockerImage") {
exec {
workingDir(".")
- commandLine("docker", "buildx", "build", "--platform=linux/amd64,linux/arm64", "-t", "fewletter/$imageName", "--build-arg", "RELEASE_VERSION=$releaseVersion", ".", "--push")
+ commandLine(
+ "docker", "buildx", "build", "--platform=linux/amd64,linux/arm64", "-t",
+ "$imageName:latest", "--build-arg", "RELEASE_VERSION=$releaseVersion", ".", "--push"
+ )
+ }
+
+ exec {
+ workingDir(".")
+ commandLine(
+ "docker", "buildx", "build", "--platform=linux/amd64,linux/arm64", "-t",
+ "$imageName:$releaseVersion", "--build-arg", "RELEASE_VERSION=$releaseVersion", ".", "--push"
+ )
}
}
}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt
index b23625075..01ede359b 100644
--- a/api/src/main/kotlin/com/few/api/config/ApiConfig.kt
+++ b/api/src/main/kotlin/com/few/api/config/ApiConfig.kt
@@ -5,12 +5,10 @@ import com.few.batch.config.BatchConfig
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
-import org.springframework.web.servlet.config.annotation.EnableWebMvc
@Configuration
@ComponentScan(basePackages = [ApiConfig.BASE_PACKAGE])
@Import(ApiRepoConfig::class, BatchConfig::class)
-@EnableWebMvc
class ApiConfig {
companion object {
const val BASE_PACKAGE = "com.few.api"
diff --git a/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt b/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt
index 83d3b745c..744f50121 100644
--- a/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt
+++ b/api/src/main/kotlin/com/few/api/domain/article/service/BrowseArticleProblemsService.kt
@@ -13,7 +13,7 @@ class BrowseArticleProblemsService(
fun execute(query: BrowseArticleProblemIdsQuery): ProblemIdsRecord {
SelectProblemsByArticleIdQuery(query.articleId).let { query: SelectProblemsByArticleIdQuery ->
- return problemDao.selectProblemsByArticleId(query)
+ return problemDao.selectProblemsByArticleId(query) ?: throw IllegalArgumentException("cannot find problems by articleId: ${query.articleId}") // todo 에러 표준화
}
}
}
\ No newline at end of file
diff --git a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt
index cdcfca55b..18dcab29f 100644
--- a/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/article/usecase/ReadArticleUseCase.kt
@@ -23,7 +23,7 @@ class ReadArticleUseCase(
fun execute(useCaseIn: ReadArticleUseCaseIn): ReadArticleUseCaseOut {
val articleRecord = SelectArticleRecordQuery(useCaseIn.articleId).let { query: SelectArticleRecordQuery ->
articleDao.selectArticleRecord(query)
- }
+ } ?: throw IllegalArgumentException("cannot find article record by articleId: $useCaseIn.articleId")
val writerRecord = ReadWriterRecordQuery(articleRecord.writerId).let { query: ReadWriterRecordQuery ->
readArticleWriterRecordService.execute(query)
diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt
index 16c65a7e8..96f66e7fb 100644
--- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/CheckProblemUseCase.kt
@@ -20,8 +20,8 @@ class CheckProblemUseCase(
val problemId = useCaseIn.problemId
val submitAns = useCaseIn.sub
- val record = problemDao.selectProblemAnswer(SelectProblemAnswerQuery(problemId))
- val isSolved = submitAns.equals(record.answer)
+ val record = problemDao.selectProblemAnswer(SelectProblemAnswerQuery(problemId)) ?: throw RuntimeException("Problem Answer with ID $problemId not found") // TODO: 에러 표준화
+ val isSolved = record.answer == submitAns
val submitHistoryId = submitHistoryDao.insertSubmitHistory(
InsertSubmitHistoryCommand(problemId, 1L, submitAns, isSolved)
diff --git a/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt b/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt
index 56a8ce042..a66019754 100644
--- a/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/problem/usecase/ReadProblemUseCase.kt
@@ -1,19 +1,18 @@
package com.few.api.domain.problem.usecase
-import com.fasterxml.jackson.databind.ObjectMapper
-import com.fasterxml.jackson.module.kotlin.readValue
import com.few.api.repo.dao.problem.ProblemDao
import com.few.api.repo.dao.problem.query.SelectProblemQuery
import com.few.api.domain.problem.usecase.`in`.ReadProblemUseCaseIn
import com.few.api.domain.problem.usecase.out.ReadProblemContentsUseCaseOutDetail
import com.few.api.domain.problem.usecase.out.ReadProblemUseCaseOut
+import com.few.api.repo.dao.problem.support.ContentsJsonMapper
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@Component
class ReadProblemUseCase(
private val problemDao: ProblemDao,
- private val objectMapper: ObjectMapper
+ private val contentsJsonMapper: ContentsJsonMapper
) {
@Transactional(readOnly = true)
@@ -21,8 +20,14 @@ class ReadProblemUseCase(
val problemId = useCaseIn.problemId
val record = problemDao.selectProblemContents(SelectProblemQuery(problemId))
+ ?: throw RuntimeException("Problem with ID $problemId not found") // TODO: 에러 표준화
- val contents: List = objectMapper.readValue(record.contents)
+ val contents: List = contentsJsonMapper.toObject(record.contents).contents.map {
+ ReadProblemContentsUseCaseOutDetail(
+ number = it.number,
+ content = it.content
+ )
+ }
return ReadProblemUseCaseOut(
id = record.id,
diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt b/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt
index aa65fc19a..90e95c58b 100644
--- a/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt
+++ b/api/src/main/kotlin/com/few/api/domain/subscription/service/MemberService.kt
@@ -5,6 +5,7 @@ import com.few.api.domain.subscription.service.dto.ReadMemberIdDto
import com.few.api.repo.dao.member.MemberDao
import com.few.api.repo.dao.member.command.InsertMemberCommand
import com.few.api.repo.dao.member.query.SelectMemberByEmailQuery
+import com.few.api.repo.dao.member.record.MemberIdRecord
import org.springframework.stereotype.Service
@Service
@@ -12,8 +13,8 @@ class MemberService(
private val memberDao: MemberDao
) {
- fun readMemberId(dto: ReadMemberIdDto): Long? {
- return memberDao.selectMemberByEmail(SelectMemberByEmailQuery(dto.email)).memberId
+ fun readMemberId(dto: ReadMemberIdDto): MemberIdRecord? {
+ return memberDao.selectMemberByEmail(SelectMemberByEmailQuery(dto.email))
}
fun insertMember(dto: InsertMemberDto): Long {
diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt
index ac0249372..716e67cbe 100644
--- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/SubscribeWorkbookUseCase.kt
@@ -17,12 +17,13 @@ class SubscribeWorkbookUseCase(
private val memberService: MemberService
) {
+ // todo 이미 가입된 경우
@Transactional
fun execute(useCaseIn: SubscribeWorkbookUseCaseIn) {
// TODO: request sending email
- val memberId = memberService.readMemberId(ReadMemberIdDto(useCaseIn.email)) ?: memberService.insertMember(
- InsertMemberDto(useCaseIn.email, MemberType.NORMAL)
+ val memberId = memberService.readMemberId(ReadMemberIdDto(useCaseIn.email))?.memberId ?: memberService.insertMember(
+ InsertMemberDto(email = useCaseIn.email, memberType = MemberType.NORMAL)
)
// 이미 구독중인지 확인
diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt
index b198f94b7..b96ef291c 100644
--- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeAllUseCase.kt
@@ -19,7 +19,7 @@ class UnsubscribeAllUseCase(
// TODO: request sending email
val memberId =
- memberService.readMemberId(ReadMemberIdDto(useCaseIn.email)) ?: throw RuntimeException("Member Not Found")
+ memberService.readMemberId(ReadMemberIdDto(useCaseIn.email))?.memberId ?: throw RuntimeException("Not found member")
subscriptionDao.updateDeletedAtInAllSubscription(
UpdateDeletedAtInAllSubscriptionCommand(memberId = memberId, opinion = useCaseIn.opinion)
diff --git a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt
index d59444fbe..1e2fc8be0 100644
--- a/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/subscription/usecase/UnsubscribeWorkbookUseCase.kt
@@ -19,7 +19,7 @@ class UnsubscribeWorkbookUseCase(
// TODO: request sending email
val memberId =
- memberService.readMemberId(ReadMemberIdDto(useCaseIn.email)) ?: throw RuntimeException("Member Not Found")
+ memberService.readMemberId(ReadMemberIdDto(useCaseIn.email))?.memberId ?: throw RuntimeException("Not found member")
subscriptionDao.updateDeletedAtInWorkbookSubscription(
UpdateDeletedAtInWorkbookSubscriptionCommand(
diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt b/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt
index 1b882d750..079c91357 100644
--- a/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/workbook/article/usecase/ReadWorkBookArticleUseCase.kt
@@ -25,7 +25,7 @@ class ReadWorkBookArticleUseCase(
useCaseIn.articleId
).let { query: SelectWorkBookArticleRecordQuery ->
articleDao.selectWorkBookArticleRecord(query)
- }
+ } ?: throw IllegalArgumentException("cannot find $useCaseIn.workbookId article record by articleId: $useCaseIn.articleId")
val writerRecord =
ReadWriterRecordQuery(articleRecord.writerId).let { query: ReadWriterRecordQuery ->
diff --git a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt
index 29d4fafa0..83c8f8613 100644
--- a/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt
+++ b/api/src/main/kotlin/com/few/api/domain/workbook/usecase/ReadWorkbookUseCase.kt
@@ -20,7 +20,7 @@ class ReadWorkbookUseCase(
val workbookId = useCaseIn.workbookId
val workbookRecord = SelectWorkBookRecordQuery(workbookId).let { query ->
- workbookDao.selectWorkBook(query)
+ workbookDao.selectWorkBook(query) ?: throw RuntimeException("WorkBook with ID ${query.id} not found")
}
val workbookMappedArticles = BrowseWorkbookArticlesQuery(workbookId).let { query ->
diff --git a/api/src/main/kotlin/com/few/api/web/config/WebConfig.kt b/api/src/main/kotlin/com/few/api/web/config/WebConfig.kt
new file mode 100644
index 000000000..cb308f31c
--- /dev/null
+++ b/api/src/main/kotlin/com/few/api/web/config/WebConfig.kt
@@ -0,0 +1,26 @@
+package com.few.api.web.config
+
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.cors.CorsConfiguration
+import org.springframework.web.servlet.config.annotation.CorsRegistry
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
+
+@Configuration
+@EnableWebMvc
+class WebConfig : WebMvcConfigurer {
+ override fun addCorsMappings(registry: CorsRegistry) {
+ registry.addMapping("/**")
+ .allowedOriginPatterns(CorsConfiguration.ALL)
+ .allowedMethods(CorsConfiguration.ALL)
+ .allowedHeaders(CorsConfiguration.ALL)
+ .allowCredentials(true)
+ .maxAge(3600)
+ }
+
+ override fun addResourceHandlers(registry: ResourceHandlerRegistry) {
+ registry.addResourceHandler("/**")
+ .addResourceLocations("classpath:/static/")
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/resources/application.yml b/api/src/main/resources/application.yml
index 63a505873..0ced4f5db 100644
--- a/api/src/main/resources/application.yml
+++ b/api/src/main/resources/application.yml
@@ -10,3 +10,6 @@ spring:
prd:
- api-repo-prd
- email-prd
+logging:
+ level:
+ root: debug
diff --git a/batch/build.gradle.kts b/batch/build.gradle.kts
index ebd32850d..4fb79680d 100644
--- a/batch/build.gradle.kts
+++ b/batch/build.gradle.kts
@@ -6,21 +6,6 @@ tasks.getByName("jar") {
enabled = true
}
-plugins {
- /** jooq */
- id("org.jooq.jooq-codegen-gradle") version DependencyVersion.JOOQ
-}
-
-sourceSets {
- main {
- java {
- val mainDir = "src/main/kotlin"
- val jooqDir = "src/generated"
- srcDirs(mainDir, jooqDir)
- }
- }
-}
-
dependencies {
/** module */
implementation(project(":email"))
@@ -46,100 +31,4 @@ dependencies {
/** test flyway */
testImplementation("org.flywaydb:flyway-core:${DependencyVersion.FLYWAY}")
testImplementation("org.flywaydb:flyway-mysql")
-}
-
-/** copy data migration */
-tasks.create("copyDataMigration") {
- doLast {
- val root = rootDir
- val flyWayResourceDir = "/db/migration/entity"
- val dataMigrationDir = "$root/data/$flyWayResourceDir"
- File(dataMigrationDir).walkTopDown().forEach {
- if (it.isFile) {
- it.copyTo(
- File("${project.projectDir}/src/main/resources$flyWayResourceDir/${it.name}"),
- true
- )
- }
- }
- }
-}
-
-/** copy data migration before compile kotlin */
-tasks.getByName("compileKotlin") {
- dependsOn("copyDataMigration")
-}
-
-/** jooq codegen after copy data migration */
-tasks.getByName("jooqCodegen") {
- dependsOn("copyDataMigration")
-}
-
-jooq {
- configuration {
- generator {
- database {
- name = "org.jooq.meta.extensions.ddl.DDLDatabase"
- properties {
- // Specify the location of your SQL script.
- // You may use ant-style file matching, e.g. /path/**/to/*.sql
- //
- // Where:
- // - ** matches any directory subtree
- // - * matches any number of characters in a directory / file name
- // - ? matches a single character in a directory / file name
- property {
- key = "scripts"
- value = "src/main/resources/db/migration/**/*.sql"
- }
-
- // The sort order of the scripts within a directory, where:
- //
- // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default)
- // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0
- // - flyway: sorts files the same way as flyway does
- // - none: doesn't sort directory contents after fetching them from the directory
- property {
- key = "sort"
- value = "flyway"
- }
-
- // The default schema for unqualified objects:
- //
- // - public: all unqualified objects are located in the PUBLIC (upper case) schema
- // - none: all unqualified objects are located in the default schema (default)
- //
- // This configuration can be overridden with the schema mapping feature
- property {
- key = "unqualifiedSchema"
- value = "none"
- }
-
- // The default name case for unquoted objects:
- //
- // - as_is: unquoted object names are kept unquoted
- // - upper: unquoted object names are turned into upper case (most databases)
- // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL)
- property {
- key = "defaultNameCase"
- value = "as_is"
- }
- }
- }
-
- generate {
- isDeprecated = false
- isRecords = true
- isImmutablePojos = true
- isFluentSetters = true
- isJavaTimeTypes = true
- }
-
- target {
- packageName = "jooq.jooq_dsl"
- directory = "src/generated"
- encoding = "UTF-8"
- }
- }
- }
}
\ No newline at end of file
diff --git a/batch/src/main/kotlin/com/few/batch/service/article/BatchSendArticleEmailService.kt b/batch/src/main/kotlin/com/few/batch/service/article/BatchSendArticleEmailService.kt
index f96daa50e..bffca33e0 100644
--- a/batch/src/main/kotlin/com/few/batch/service/article/BatchSendArticleEmailService.kt
+++ b/batch/src/main/kotlin/com/few/batch/service/article/BatchSendArticleEmailService.kt
@@ -16,13 +16,17 @@ class BatchSendArticleEmailService(
) {
@Transactional
fun execute() {
+ val startTime = System.currentTimeMillis()
workBookSubscriberReader.execute().let { item ->
- workBookSubscriberWriter.execute(item).let { execution ->
- objectMapper.writeValueAsString(execution).let { json ->
- if (!json.contains("fail")) {
- batchCallExecutionService.execute(false, json)
- } else {
- batchCallExecutionService.execute(true, json)
+ workBookSubscriberWriter.execute(item).let { resultExecution ->
+ val elapsedTime = System.currentTimeMillis() - startTime
+ resultExecution.plus("elapsedTime" to elapsedTime).let { execution ->
+ objectMapper.writeValueAsString(execution).let { json ->
+ if (!json.contains("fail")) {
+ batchCallExecutionService.execute(true, json)
+ } else {
+ batchCallExecutionService.execute(false, json)
+ }
}
}
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 24bab4203..70be90d5d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,6 +10,9 @@ plugins {
id("org.springframework.boot") version DependencyVersion.SPRING_BOOT
id("io.spring.dependency-management") version DependencyVersion.SPRING_DEPENDENCY_MANAGEMENT
+ /** jooq */
+ id("org.jooq.jooq-codegen-gradle") version DependencyVersion.JOOQ
+
/** ktlint */
id("org.jlleitschuh.gradle.ktlint") version DependencyVersion.KTLINT
@@ -44,6 +47,16 @@ allprojects {
tasks.withType {
useJUnitPlatform()
}
+
+ sourceSets {
+ main {
+ java {
+ val mainDir = "src/main/kotlin"
+ val jooqDir = "src/generated"
+ srcDirs(mainDir, jooqDir)
+ }
+ }
+ }
}
tasks.getByName("bootJar") {
@@ -57,6 +70,7 @@ subprojects {
apply(plugin = "org.jetbrains.kotlin.kapt")
apply(plugin = "org.jlleitschuh.gradle.ktlint")
apply(plugin = "org.hidetake.swagger.generator")
+ apply(plugin = "org.jooq.jooq-codegen-gradle")
/**
* https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
@@ -80,6 +94,13 @@ subprojects {
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
+ /** jooq */
+ implementation("org.springframework.boot:spring-boot-starter-jooq")
+ implementation("org.jooq:jooq:${DependencyVersion.JOOQ}")
+ implementation("org.jooq:jooq-meta:${DependencyVersion.JOOQ}")
+ implementation("org.jooq:jooq-codegen:${DependencyVersion.JOOQ}")
+ jooqCodegen("org.jooq:jooq-meta-extensions:${DependencyVersion.JOOQ}")
+
/** test **/
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.mockk:mockk:${DependencyVersion.MOCKK}")
@@ -94,9 +115,112 @@ subprojects {
swaggerUI("org.webjars:swagger-ui:${DependencyVersion.SWAGGER_UI}")
}
+ /** copy data migration */
+ tasks.create("copyDataMigration") {
+ doLast {
+ val root = rootDir
+ val flyWayResourceDir = "/db/migration/entity"
+ val dataMigrationDir = "$root/data/$flyWayResourceDir"
+ File(dataMigrationDir).walkTopDown().forEach {
+ if (it.isFile) {
+ it.copyTo(
+ File("${project.projectDir}/src/main/resources$flyWayResourceDir/${it.name}"),
+ true
+ )
+ }
+ }
+ }
+ }
+
+ /** copy data migration before compile kotlin */
+ tasks.getByName("compileKotlin") {
+ dependsOn("copyDataMigration")
+ }
+
+ /** jooq codegen after copy data migration */
+ tasks.getByName("jooqCodegen") {
+ dependsOn("copyDataMigration")
+ }
+
+ jooq {
+ configuration {
+ generator {
+ database {
+ name = "org.jooq.meta.extensions.ddl.DDLDatabase"
+ properties {
+ // Specify the location of your SQL script.
+ // You may use ant-style file matching, e.g. /path/**/to/*.sql
+ //
+ // Where:
+ // - ** matches any directory subtree
+ // - * matches any number of characters in a directory / file name
+ // - ? matches a single character in a directory / file name
+ property {
+ key = "scripts"
+ value = "src/main/resources/db/migration/**/*.sql"
+ }
+
+ // The sort order of the scripts within a directory, where:
+ //
+ // - semantic: sorts versions, e.g. v-3.10.0 is after v-3.9.0 (default)
+ // - alphanumeric: sorts strings, e.g. v-3.10.0 is before v-3.9.0
+ // - flyway: sorts files the same way as flyway does
+ // - none: doesn't sort directory contents after fetching them from the directory
+ property {
+ key = "sort"
+ value = "flyway"
+ }
+
+ // The default schema for unqualified objects:
+ //
+ // - public: all unqualified objects are located in the PUBLIC (upper case) schema
+ // - none: all unqualified objects are located in the default schema (default)
+ //
+ // This configuration can be overridden with the schema mapping feature
+ property {
+ key = "unqualifiedSchema"
+ value = "none"
+ }
+
+ // The default name case for unquoted objects:
+ //
+ // - as_is: unquoted object names are kept unquoted
+ // - upper: unquoted object names are turned into upper case (most databases)
+ // - lower: unquoted object names are turned into lower case (e.g. PostgreSQL)
+ property {
+ key = "defaultNameCase"
+ value = "as_is"
+ }
+ }
+ }
+
+ generate {
+ isDeprecated = false
+ isRecords = true
+ isImmutablePojos = true
+ isFluentSetters = true
+ isJavaTimeTypes = true
+ }
+
+ target {
+ packageName = "jooq.jooq_dsl"
+ directory = "src/generated"
+ encoding = "UTF-8"
+ }
+ }
+ }
+ }
+
defaultTasks("bootRun")
}
+/** do all jooq codegen */
+tasks.register("jooqCodegenAll") {
+ dependsOn(":api:jooqCodegen")
+ dependsOn(":api-repo:jooqCodegen")
+ dependsOn(":batch:jooqCodegen")
+}
+
/** git hooks */
tasks.register("gitExecutableHooks") {
doLast {
diff --git a/data/db/migration/entity/V1.00.0.0__draft_table_design.sql b/data/db/migration/entity/V1.00.0.0__draft_table_design.sql
index f4fe58634..6256ffc37 100644
--- a/data/db/migration/entity/V1.00.0.0__draft_table_design.sql
+++ b/data/db/migration/entity/V1.00.0.0__draft_table_design.sql
@@ -1,5 +1,5 @@
-- 작가 및 유저
-CREATE TABLE member
+CREATE TABLE MEMBER
(
id BIGINT NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
@@ -13,7 +13,7 @@ CREATE TABLE member
);
-- 아티클 마스터
-CREATE TABLE article_mst
+CREATE TABLE ARTICLE_MST
(
id BIGINT NOT NULL AUTO_INCREMENT,
member_id BIGINT NOT NULL,
@@ -26,7 +26,7 @@ CREATE TABLE article_mst
);
-- 아티클 인포
-CREATE TABLE article_ifo
+CREATE TABLE ARTICLE_IFO
(
article_mst_id BIGINT NOT NULL,
content TEXT NOT NULL,
@@ -35,7 +35,7 @@ CREATE TABLE article_ifo
);
-- 학습지
-CREATE TABLE workbook
+CREATE TABLE WORKBOOK
(
id BIGINT NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
@@ -48,7 +48,7 @@ CREATE TABLE workbook
);
-- 작가-학습지 매핑테이블(다대다)
-CREATE TABLE mapping_member_workbook
+CREATE TABLE MAPPING_MEMBER_WORKBOOK
(
member_id BIGINT NOT NULL,
workbook_id BIGINT NOT NULL,
@@ -57,7 +57,7 @@ CREATE TABLE mapping_member_workbook
);
-- 학습지-아티클 매핑테이블(다대다)
-CREATE TABLE mapping_workbook_article
+CREATE TABLE MAPPING_WORKBOOK_ARTICLE
(
workbook_id BIGINT NOT NULL,
article_id BIGINT NOT NULL,
@@ -67,7 +67,7 @@ CREATE TABLE mapping_workbook_article
);
-- 문제, 정답, 해설(메타데이터)
-CREATE TABLE problem
+CREATE TABLE PROBLEM
(
id BIGINT NOT NULL AUTO_INCREMENT,
article_id BIGINT NOT NULL,
@@ -82,7 +82,7 @@ CREATE TABLE problem
);
-- 풀이 히스토리
-CREATE TABLE submit_history
+CREATE TABLE SUBMIT_HISTORY
(
id BIGINT NOT NULL AUTO_INCREMENT,
problem_id BIGINT NOT NULL,
@@ -95,7 +95,7 @@ CREATE TABLE submit_history
);
-- 구독
-CREATE TABLE subscription
+CREATE TABLE SUBSCRIPTION
(
id BIGINT NOT NULL AUTO_INCREMENT,
member_id BIGINT NOT NULL,
@@ -110,7 +110,7 @@ CREATE TABLE subscription
-- [인덱스 추가] --
-- problem_idx1: problem 테이블에서 article_id 기반으로 문제 조회시 사용
-CREATE INDEX problem_idx1 ON problem (article_id);
+CREATE INDEX problem_idx1 ON PROBLEM (article_id);
-- article_mst_idx1: 작가가 작성한 아티클 조회시 사용
-CREATE INDEX article_mst_idx1 ON article_mst (member_id);
+CREATE INDEX article_mst_idx1 ON ARTICLE_MST (member_id);
diff --git a/data/db/migration/entity/V1.00.0.1__add_column.sql b/data/db/migration/entity/V1.00.0.1__add_column.sql
index 6f775d87f..c9f1ce20e 100644
--- a/data/db/migration/entity/V1.00.0.1__add_column.sql
+++ b/data/db/migration/entity/V1.00.0.1__add_column.sql
@@ -1,3 +1,3 @@
-- subscription 테이블에 reason 컬럼 추가 (한국어로만 100자 가능)
-ALTER TABLE subscription
+ALTER TABLE SUBSCRIPTION
ADD COLUMN unsubs_opinion VARCHAR(300) NULL;
diff --git a/data/db/migration/entity/V1.00.0.2__add_subscription_progress.sql b/data/db/migration/entity/V1.00.0.2__add_subscription_progress.sql
index 60e206295..3f5b0941c 100644
--- a/data/db/migration/entity/V1.00.0.2__add_subscription_progress.sql
+++ b/data/db/migration/entity/V1.00.0.2__add_subscription_progress.sql
@@ -1,2 +1,2 @@
-- 구독 진행 사항 컬럼 추가
-ALTER TABLE subscription ADD COLUMN progress BIGINT NOT NULL DEFAULT 0;
+ALTER TABLE SUBSCRIPTION ADD COLUMN progress BIGINT NOT NULL DEFAULT 0;
diff --git a/data/db/migration/entity/V1.00.0.3__batch_call_execution_table.sql b/data/db/migration/entity/V1.00.0.3__batch_call_execution_table.sql
index 8bbb3a519..fbf522bd5 100644
--- a/data/db/migration/entity/V1.00.0.3__batch_call_execution_table.sql
+++ b/data/db/migration/entity/V1.00.0.3__batch_call_execution_table.sql
@@ -1,5 +1,5 @@
-- 배치 요청 기록 테이블
-CREATE TABLE batch_call_execution
+CREATE TABLE BATCH_CALL_EXECUTION
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
status bit(1) NOT NULL,
diff --git a/email/src/main/resources/application-email-local.yml b/email/src/main/resources/application-email-local.yml
index a6e7c4dbb..471d8d553 100644
--- a/email/src/main/resources/application-email-local.yml
+++ b/email/src/main/resources/application-email-local.yml
@@ -4,7 +4,7 @@ spring:
host: smtp.gmail.com
port: 587
username: DevFewFew@gmail.com
- password: ${EMAIL_PASSWORD}
+ password: ${EMAIL_PASSWORD:password}
properties:
mail:
smtp:
diff --git a/email/src/test/resources/application-test.yml b/email/src/test/resources/application-test.yml
index 83b9b042b..06f5a2520 100644
--- a/email/src/test/resources/application-test.yml
+++ b/email/src/test/resources/application-test.yml
@@ -4,7 +4,7 @@ spring:
protocol: smtp
port: 25
username: 36a1889f02050c
- password: ${MAIL_PASSWORD}
+ password: ${EMAIL_PASSWORD}
properties:
mail:
smtp: