diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6c94357..1a0e6de 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,16 +1,19 @@
## PULL REQUEST
-### π μμ
μ€μΈ λΈλμΉ
+### π Issue Ticket
-
### π μ£Όμ μμ
μ¬ν
-
### π (Optional) μ°Έκ³ μλ£
-- μ€ν¬λ¦°μ·μ 첨λΆν΄μ£ΌμΈμ.
-
-### κ΄λ ¨ μ΄μ
-
+### (μ€μ) μλΈλͺ¨λμ΄ μμ λμλμ?
+- κΈ°μ‘΄ μ»€λ° :
+- λ³κ²½ μ»€λ° :
+- λ³κ²½ μ¬ν :
+- [] ν΄λΉ μλΈλͺ¨λ λ³κ²½μ¬νμ΄ PRμ μ λ°μλμλμ?
+
### κΌ νμΈν΄ μ£ΌμΈμ!!
--
+-
diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml
index 1269951..87a4b52 100644
--- a/.github/workflows/CD.yml
+++ b/.github/workflows/CD.yml
@@ -5,6 +5,11 @@ on:
branches:
- master
+permissions:
+ contents: read
+ actions: read
+ id-token: write
+
jobs:
deploy:
runs-on: ubuntu-latest
@@ -15,59 +20,48 @@ jobs:
with:
token: ${{ secrets.GIT_TOKEN }}
submodules: true
-
- name: Setup Java 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
-
- name: Update Git submodules
run: git submodule update --remote --recursive
-
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
-
- - name: Build with Gradle
- run: ./gradlew clean build --debug
-
- - name: Get current time
- uses: 1466587594/get-current-time@v2
- id: current-time
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v4
with:
- format: YYYY-MM-DDTHH-mm-ss
- utcOffset: "+09:00"
-
- - name: Show Current Time
- run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v1
- with:
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::125183404358:role/VacgomGithubActionAssumeRole
aws-region: ap-northeast-2
-
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
-
+ - name: Invoke Gradle
+ uses: gradle/gradle-build-action@v2
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
- name: Build, tag, and push image to Amazon ECR
+ env:
+ PROFILE: dev
+ IMAGE_REPO_URL: ${{ steps.login-ecr.outputs.registry }}/vacgom
+ IMAGE_TAG: ${{ github.sha }}
+ run: ./gradlew jib --parallel
+ - name: Download Task Definition
run: |
- docker build -t vacgom:${{steps.current-time.outputs.formattedTime}} .
- docker tag vacgom:${{steps.current-time.outputs.formattedTime}} ${{ secrets.ECR_URI }}:${{steps.current-time.outputs.formattedTime}}
- docker push ${{ secrets.ECR_URI }}:${{steps.current-time.outputs.formattedTime}}
-
- - name: SSH into EC2 instance
- uses: appleboy/ssh-action@master
+ aws ecs describe-task-definition \
+ --task-definition vacgom-taskdef \
+ --query taskDefinition \
+ > task-definition.json
+ - name: Update Task Definition
+ id: task-def
+ uses: aws-actions/amazon-ecs-render-task-definition@v1
+ with:
+ task-definition: task-definition.json
+ container-name: backend
+ image: ${{ steps.login-ecr.outputs.registry }}/vacgom:${{ github.sha }}
+ - name: Deploy Amazon ECS task definition
+ uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
- host: ${{ secrets.EC2_HOST }}
- username: ${{ secrets.EC2_USERNAME }}
- key: ${{ secrets.EC2_PRIVATE_KEY }}
- port: ${{ secrets.EC2_SSH_PORT }}
- script: |
- aws ecr get-login-password | docker login --username AWS --password-stdin ${{ secrets.ECR_URI }}
- docker pull ${{ secrets.ECR_URI }}:${{ steps.current-time.outputs.formattedTime }}
- docker ps -f name=vacgom-api -q | xargs --no-run-if-empty docker container stop
- docker ps -a -f name=vacgom-api -q | xargs --no-run-if-empty docker container rm
- docker run -d --name vacgom-api -p 80:8080 ${{ secrets.ECR_URI }}:${{ steps.current-time.outputs.formattedTime }}
+ task-definition: ${{ steps.task-def.outputs.task-definition }}
+ service: vacgom-best-service
+ cluster: vacgom-cluster
+ wait-for-service-stability: true
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index a96161b..230894e 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -1,42 +1,38 @@
-name: λΉλ ν
μ€νΈ / λ¦¬λ·°μ΄ ν λΉ
+name: ν
μ€νΈ μ½λ λ° λΉλ
on:
pull_request:
- types: [opened, synchronize, closed]
+ types: [ opened, synchronize, closed ]
branches:
- 'develop'
- 'master'
jobs:
- build_and_review_assign:
- name: "[CI] Check Build/Testcases and Assign Reviewer"
+ test:
+ name: "[CI] Check Tests"
runs-on: ubuntu-latest
steps:
- - name: (Set Up) checkout
+ - name: Checkout code
uses: actions/checkout@v3
with:
token: ${{ secrets.GIT_TOKEN }}
submodules: true
- - name: (Set Up) Set up JDK 17
+ - name: Setup Java 17
uses: actions/setup-java@v3
with:
java-version: '17'
- distribution: 'temurin'
+ distribution: 'adopt'
+
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
- name: (Set Up) Grant Execute permission for gradlew
run: chmod 777 gradlew
- - name: (Build) Build with Gradle
- id: build
- run: ./gradlew test -i
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
- - name: (Assign Reviewer)
- if: steps.build.outcome == 'success'
- uses: hkusu/review-assign-action@v1
- with:
- assignees: ${{ github.actor }}
- reviewers: HyungJu, h-beeen
- ready-comment: 'μ½λ 리뷰 μμ²ν©λλ€ π '
- merged-comment: 'μ±κ³΅μ μΌλ‘ Merge λμμ΅λλ€. Shout out to :wink:'
+ - name: Test with Gradle
+ run: ./gradlew test
diff --git a/build.gradle.kts b/build.gradle.kts
index ca64428..94a4a9f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,8 +1,13 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+val activeProfile = System.getenv("PROFILE") ?: "dev"
+val imageTag = System.getenv("IMAGE_TAG") ?: "latest"
+val repoURL: String? = System.getenv("IMAGE_REPO_URL")
+
plugins {
id("org.springframework.boot") version "3.2.3"
id("io.spring.dependency-management") version "1.1.4"
+ id("com.google.cloud.tools.jib") version "3.4.1"
kotlin("jvm") version "1.9.22"
kotlin("plugin.spring") version "1.9.22"
kotlin("plugin.jpa") version "1.9.22"
@@ -56,3 +61,21 @@ tasks.register("initConfig") {
include("*.yml")
into("./src/main/resources")
}
+
+jib {
+ from {
+ image = "amazoncorretto:17-alpine3.18"
+ }
+ to {
+ image = repoURL
+ tags = setOf(imageTag)
+ }
+ container {
+ jvmFlags = listOf(
+ "-Dspring.profiles.active=${activeProfile}",
+ "-Dserver.port=8080",
+ "-XX:+UseContainerSupport",
+ )
+ ports = listOf("8080")
+ }
+}
diff --git a/src/main/kotlin/com/vacgom/backend/domain/member/Member.kt b/src/main/kotlin/com/vacgom/backend/domain/member/Member.kt
new file mode 100644
index 0000000..de0538c
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/domain/member/Member.kt
@@ -0,0 +1,21 @@
+package com.vacgom.backend.domain.member
+
+import com.vacgom.backend.global.auditing.BaseEntity
+import jakarta.persistence.*
+
+@Entity
+@Table(name = "t_member")
+class Member(nickname: String) : BaseEntity() {
+
+ @Id
+ @Column(name = "member_id")
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ var id: Long? = null
+
+ @Column(name = "nickname")
+ val nickname: String
+
+ init {
+ this.nickname = nickname
+ }
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandler.kt b/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandler.kt
new file mode 100644
index 0000000..36f6c8a
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandler.kt
@@ -0,0 +1,55 @@
+package com.vacgom.backend.global.exception
+
+import com.vacgom.backend.global.exception.error.BusinessException
+import com.vacgom.backend.global.exception.error.ErrorCode
+import com.vacgom.backend.global.exception.error.ErrorResponse
+import com.vacgom.backend.global.exception.error.GlobalError
+import com.vacgom.backend.global.logger.CommonLogger
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.MethodArgumentNotValidException
+import org.springframework.web.bind.MissingServletRequestParameterException
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseStatus
+import org.springframework.web.bind.annotation.RestControllerAdvice
+
+@RestControllerAdvice
+class ApiExceptionHandler {
+ companion object : CommonLogger();
+
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ExceptionHandler(MethodArgumentNotValidException::class)
+ fun handleMethodArgumentNotValidException(ex: MethodArgumentNotValidException): ErrorResponse? {
+ return ErrorResponse(ex.fieldErrors)
+ }
+
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ExceptionHandler(MissingServletRequestParameterException::class)
+ fun missingServletRequestParameterException(): ErrorResponse? {
+ return ErrorResponse(GlobalError.INVALID_REQUEST_PARAM)
+ }
+
+ @ExceptionHandler(BusinessException::class)
+ fun handleBusinessException(
+ exception: BusinessException
+ ): ResponseEntity {
+ logBusinessException(exception)
+ return convert(exception.errorCode)
+ }
+
+ private fun convert(
+ errorCode: ErrorCode
+ ): ResponseEntity {
+ return ResponseEntity
+ .status(errorCode.status)
+ .body(ErrorResponse(errorCode))
+ }
+
+ private fun logBusinessException(exception: BusinessException) {
+ if (exception.errorCode.status.is5xxServerError) {
+ log.error("", exception)
+ } else {
+ log.error("Error Message = {}", exception.message)
+ }
+ }
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandlingFilter.kt b/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandlingFilter.kt
new file mode 100644
index 0000000..37cb8e1
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/ApiExceptionHandlingFilter.kt
@@ -0,0 +1,45 @@
+package com.vacgom.backend.global.exception
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.vacgom.backend.global.exception.error.BusinessException
+import com.vacgom.backend.global.exception.error.ErrorResponse
+import jakarta.servlet.FilterChain
+import jakarta.servlet.ServletException
+import jakarta.servlet.http.HttpServletRequest
+import jakarta.servlet.http.HttpServletResponse
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.stereotype.Component
+import org.springframework.web.filter.OncePerRequestFilter
+import java.io.IOException
+
+
+@Component
+class ApiExceptionHandlingFilter(
+ private val om: ObjectMapper
+) : OncePerRequestFilter() {
+
+ @Throws(ServletException::class, IOException::class)
+ override fun doFilterInternal(
+ request: HttpServletRequest,
+ response: HttpServletResponse,
+ chain: FilterChain
+ ) {
+ try {
+ chain.doFilter(request, response)
+ } catch (exception: BusinessException) {
+ setErrorResponse(response, exception)
+ }
+ }
+
+ private fun setErrorResponse(
+ response: HttpServletResponse,
+ exception: BusinessException
+ ) = try {
+ response.contentType = MediaType.APPLICATION_JSON_VALUE
+ response.status = HttpStatus.UNAUTHORIZED.value()
+ om.writeValue(response.outputStream, ErrorResponse(exception.errorCode))
+ } catch (exception: IOException) {
+ throw RuntimeException(exception)
+ }
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/error/BusinessException.kt b/src/main/kotlin/com/vacgom/backend/global/exception/error/BusinessException.kt
new file mode 100644
index 0000000..638ee2b
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/error/BusinessException.kt
@@ -0,0 +1,6 @@
+package com.vacgom.backend.global.exception.error
+
+
+class BusinessException(
+ val errorCode: ErrorCode
+) : RuntimeException(errorCode.message)
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorCode.kt b/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorCode.kt
new file mode 100644
index 0000000..550d1dc
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorCode.kt
@@ -0,0 +1,9 @@
+package com.vacgom.backend.global.exception.error
+
+import org.springframework.http.HttpStatus
+
+interface ErrorCode {
+ val message: String
+ val status: HttpStatus
+ val code: String
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorResponse.kt b/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorResponse.kt
new file mode 100644
index 0000000..4b7b4a0
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/error/ErrorResponse.kt
@@ -0,0 +1,41 @@
+package com.vacgom.backend.global.exception.error
+
+import org.springframework.validation.FieldError
+import java.time.LocalDateTime
+
+data class ErrorResponse(
+ val timeStamp: String = LocalDateTime.now().toString(),
+ val errorCode: String,
+ val errorMessage: String,
+ val details: Any? = null
+) {
+ constructor(
+ errorCode: ErrorCode
+ ) : this(
+ errorCode = errorCode.code,
+ errorMessage = errorCode.message
+ )
+
+ constructor(
+ errorCode: ErrorCode,
+ details: Any?
+ ) : this(
+ errorCode = errorCode.code,
+ errorMessage = errorCode.message,
+ details = details
+ )
+
+ constructor(
+ fieldError: FieldError?
+ ) : this(
+ errorCode = fieldError?.code ?: "",
+ errorMessage = fieldError?.defaultMessage ?: ""
+ )
+
+ constructor(fieldErrors: List) : this(
+ GlobalError.INVALID_REQUEST_PARAM,
+ fieldErrors.associate {
+ it.field to (it.defaultMessage ?: "null")
+ }
+ )
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/exception/error/GlobalError.kt b/src/main/kotlin/com/vacgom/backend/global/exception/error/GlobalError.kt
new file mode 100644
index 0000000..432f9c5
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/exception/error/GlobalError.kt
@@ -0,0 +1,12 @@
+package com.vacgom.backend.global.exception.error
+
+import org.springframework.http.HttpStatus
+
+enum class GlobalError(
+ override val message: String,
+ override val status: HttpStatus,
+ override val code: String
+) : ErrorCode {
+ GLOBAL_NOT_FOUND("리μμ€κ° μ‘΄μ¬νμ§ μμ΅λλ€.", HttpStatus.NOT_FOUND, "G_001"),
+ INVALID_REQUEST_PARAM("μμ² νλΌλ―Έν°κ° μ ν¨νμ§ μμ΅λλ€.", HttpStatus.BAD_REQUEST, "G_002");
+}
diff --git a/src/main/kotlin/com/vacgom/backend/global/logger/CommonLogger.kt b/src/main/kotlin/com/vacgom/backend/global/logger/CommonLogger.kt
new file mode 100644
index 0000000..0d8e1c9
--- /dev/null
+++ b/src/main/kotlin/com/vacgom/backend/global/logger/CommonLogger.kt
@@ -0,0 +1,7 @@
+package com.vacgom.backend.global.logger
+
+import org.slf4j.LoggerFactory
+
+abstract class CommonLogger {
+ val log = LoggerFactory.getLogger(this.javaClass)!!
+}
diff --git a/src/main/kotlin/com/vacgom/backend/member/domain/Member.kt b/src/main/kotlin/com/vacgom/backend/member/domain/Member.kt
deleted file mode 100644
index f0f339e..0000000
--- a/src/main/kotlin/com/vacgom/backend/member/domain/Member.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.vacgom.backend.member.domain
-
-import com.vacgom.backend.global.auditing.BaseEntity
-import jakarta.persistence.*
-
-@Entity
-@Table(name = "t_user")
-class User(
- val nickname: String
-) : BaseEntity() {
-
- @Id
- @Column(name = "user_id")
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- var id: Long? = null
-}