Repository: with-one-voice/ticketing-service
Pull request #5: Feature/#4 add common module
Author: login: kopite97
Labels: name: feat
description: ✨ 새로운 기능 추가
Created: 2025-04-05T20:17:36Z | Merged: 2025-04-05T20:19:30Z

## #️⃣ Issue Number


> IssueNumber : #4 

<!---해결할 관련된 이슈 번호를 작성해주세요 (예: #123). -->

## 📄 설명

> PR에 대한 간략한 설명을 작성해주세요.
> 어떤 문제를 해결했는지, 새로운 기능을 추가했는지 등 내용을 자세히 기입해 주세요.

- Common 모듈 추가
- BaseEntity
- CommonResponse
- ResponseCode
- ErrorResponse
- GlobaleExceptionHandler

<!--- 변경 사항 및 관련 이슈에 대해 간단하게 작성해주세요. 어떻게보다 무엇을 왜 수정했는지 설명해주세요. -->

## 🛠️ PR 유형

어떤 변경 사항이 있나요?

- [x] 새로운 기능 추가
- [ ] 버그 수정
- [ ] CSS 등 사용자 UI 디자인 변경
- [ ] 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
- [ ] 코드 리팩토링
- [ ] 주석 추가 및 수정
- [ ] 문서 수정
- [ ] 테스트 추가, 테스트 리팩토링
- [ ] 빌드 부분 혹은 패키지 매니저 수정
- [ ] 파일 혹은 폴더명 수정
- [ ] 파일 혹은 폴더 삭제

## 📸스크린샷 (선택)




## 💬 공유사항 to 리뷰어

<!--- 리뷰어가 중점적으로 봐줬으면 좋겠는 부분이 있으면 적어주세요. -->
<!--- 논의해야할 부분이 있다면 적어주세요.-->
<!--- ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - 공통 모듈(common) 추가 및 Spring Boot 기반의 표준 응답, 예외 처리, JPA 엔티티 베이스 클래스 제공
  - 표준 API 응답 구조 및 에러 코드(enum) 도입
  - 글로벌 예외 처리 기능 및 커스텀 예외 클래스 제공

- **Chores**
  - 공통 모듈용 Gradle 설정, 빌드 스크립트, Gradle Wrapper, .gitignore, .gitattributes 파일 추가
  - 공통 모듈에 대한 테스트 케이스 추가
  - 프로젝트 설정에 공통 모듈 포함 및 의존성 추가

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/build.gradle b/build.gradle
index aa6870e4..0fa9677e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -55,6 +55,7 @@ configure(sharedModules) {
 		implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 		implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
 		implementation 'org.apache.commons:commons-lang3'
+		implementation project(':common')
 
 		compileOnly 'org.projectlombok:lombok'
 		annotationProcessor 'org.projectlombok:lombok'
diff --git a/common/.gitattributes b/common/.gitattributes
new file mode 100644
index 00000000..8af972cd
--- /dev/null
+++ b/common/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 00000000..c2065bc2
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,37 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
diff --git a/common/build.gradle b/common/build.gradle
new file mode 100644
index 00000000..ebbfa5a5
--- /dev/null
+++ b/common/build.gradle
@@ -0,0 +1,34 @@
+plugins {
+	id 'java'
+	id 'org.springframework.boot'
+	id 'io.spring.dependency-management'
+}
+
+group = 'com.onevoice'
+version = '0.0.1-SNAPSHOT'
+
+bootJar{
+	enabled =false
+}
+
+jar{
+	enabled =true
+}
+
+java {
+	toolchain {
+		languageVersion = JavaLanguageVersion.of(17)
+	}
+}
+
+repositories {
+	mavenCentral()
+}
+
+dependencies {
+	compileOnly 'org.projectlombok:lombok'
+	annotationProcessor 'org.projectlombok:lombok'
+	implementation 'org.springframework.boot:spring-boot-starter-validation'
+	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+	implementation 'org.springframework:spring-web'
+}
diff --git a/common/gradle/wrapper/gradle-wrapper.jar b/common/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..9bbc975c
Binary files /dev/null and b/common/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/common/gradle/wrapper/gradle-wrapper.properties b/common/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..37f853b1
--- /dev/null
+++ b/common/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/common/gradlew b/common/gradlew
new file mode 100644
index 00000000..faf93008
--- /dev/null
+++ b/common/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC2039,SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/common/gradlew.bat b/common/gradlew.bat
new file mode 100644
index 00000000..9d21a218
--- /dev/null
+++ b/common/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/common/settings.gradle b/common/settings.gradle
new file mode 100644
index 00000000..48fe92d7
--- /dev/null
+++ b/common/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'common'
diff --git a/common/src/main/java/com/onevoice/common/CommonApplication.java b/common/src/main/java/com/onevoice/common/CommonApplication.java
new file mode 100644
index 00000000..525e0840
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/CommonApplication.java
@@ -0,0 +1,13 @@
+package com.onevoice.common;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CommonApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(CommonApplication.class, args);
+	}
+
+}
diff --git a/common/src/main/java/com/onevoice/common/dto/CommonResponse.java b/common/src/main/java/com/onevoice/common/dto/CommonResponse.java
new file mode 100644
index 00000000..9e235de7
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/dto/CommonResponse.java
@@ -0,0 +1,29 @@
+package com.onevoice.common.dto;
+
+import lombok.Getter;
+import org.springframework.http.ResponseEntity;
+
+@Getter
+public class CommonResponse<T> {
+    private final int code;
+    private final String message;
+    private final T result;
+
+    public static <T> ResponseEntity<CommonResponse<T>> success(T result){
+        return ResponseEntity.ok(createCommonResponse(ResponseCode.SUCCESS, result));
+    }
+
+    public static <T> ResponseEntity<CommonResponse<T>> of(ResponseCode code,T result){
+        return new ResponseEntity<>(createCommonResponse(code, result), code.getStatus());
+    }
+
+    public static <T> CommonResponse<T> createCommonResponse(ResponseCode code, T result) {
+        return new CommonResponse<>(code, result);
+    }
+
+    public CommonResponse(ResponseCode code, T result) {
+        this.code = code.getCode();
+        this.message = code.getMessage();
+        this.result = result;
+    }
+}
diff --git a/common/src/main/java/com/onevoice/common/dto/ErrorResponse.java b/common/src/main/java/com/onevoice/common/dto/ErrorResponse.java
new file mode 100644
index 00000000..09440ae7
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/dto/ErrorResponse.java
@@ -0,0 +1,5 @@
+package com.onevoice.common.dto;
+
+public record ErrorResponse(String code, String message) {
+
+}
diff --git a/common/src/main/java/com/onevoice/common/dto/ResponseCode.java b/common/src/main/java/com/onevoice/common/dto/ResponseCode.java
new file mode 100644
index 00000000..40603c62
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/dto/ResponseCode.java
@@ -0,0 +1,58 @@
+package com.onevoice.common.dto;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public enum ResponseCode {
+
+    /**
+     * ────────────────────────────────────────────
+     * @Response Code Convention
+     * ───────────────────────────────────────
+     * @Format: @ABCD
+     *  @- A : HTTP 상태 범주 (4 = 4xx Client Error, 5 = 5xx Server Error 등)
+     *  @- B : 도메인 구분 (1 = User, 5 = Ticket, ...)
+     *  @- CD: 세부 에러 코드 (40 = Not Found, 90 = Conflict 등)
+     * ───────────────────────────────────────
+     * @User Domain (B = 1)
+     * ───────────────────────────────────────
+     * @4140 USER_NOT_FOUND        → 404 Not Found
+     * @4190 DUPLICATE_USER        → 409 Conflict
+     * ───────────────────────────────────────
+     * @Ticket Domain (B = 5)
+     * ───────────────────────────────────────
+     * @4590 TICKET_ALREADY_BOOKED → 409 Conflict
+     * @※ 참고: 에러 메시지와 매핑된 HTTP 상태 코드도 함께 기재하면 더 명확합니다.
+     */
+
+
+
+    // 2xx: 성공
+    SUCCESS(2000, HttpStatus.OK, "요청 성공"),
+    CREATED(2010, HttpStatus.CREATED, "생성 성공"),
+
+    // 4xx: 클라이언트 오류
+    BAD_REQUEST(4000, HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
+    UNAUTHORIZED(4010, HttpStatus.UNAUTHORIZED, "인증이 필요합니다."),
+    FORBIDDEN(4030, HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),
+    NOT_FOUND(4040, HttpStatus.NOT_FOUND, "대상을 찾을 수 없습니다."),
+
+    // 5xx: 서버 오류
+    INTERNAL_ERROR(5000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."),
+
+    // User
+    USER_NOT_FOUND(4140, HttpStatus.NOT_FOUND, "존재하지 않는 사용자 입니다."),
+    DUPLICATE_USER(4190, HttpStatus.CONFLICT, "이미 존재하는 사용자 입니다.");
+
+
+    private final int code; // 커스텀 코드 (우리 마음대로 정하는 거)
+    private final HttpStatus status;
+    private final String message;
+
+    ResponseCode(int code, HttpStatus status, String message) {
+        this.code = code;
+        this.status = status;
+        this.message = message;
+    }
+}
diff --git a/common/src/main/java/com/onevoice/common/entity/BaseEntity.java b/common/src/main/java/com/onevoice/common/entity/BaseEntity.java
new file mode 100644
index 00000000..8a127210
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/entity/BaseEntity.java
@@ -0,0 +1,57 @@
+package com.onevoice.common.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+import lombok.Getter;
+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;
+
+@Getter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseEntity {
+
+    @CreatedDate
+    @Column(name = "created_at", updatable = false)
+    protected LocalDateTime createdAt;
+
+    @CreatedBy
+    @Column(name = "created_by", updatable = false)
+    protected String createdBy;
+
+    @LastModifiedDate
+    @Column(name = "updated_at")
+    protected LocalDateTime updatedAt;
+
+    @LastModifiedBy
+    @Column(name = "updated_by")
+    protected String updatedBy;
+
+    @Column(name ="deleted_at")
+    protected LocalDateTime deletedAt;
+
+    @Column(name ="deleted_by")
+    protected String deletedBy;
+
+    /**
+     * soft delete
+     */
+    public void delete(String username){
+        this.deletedAt = LocalDateTime.now();
+        this.deletedBy = username;
+    }
+
+    public void restore(){
+        this.deletedAt = null;
+        this.deletedBy = null;
+    }
+
+    public boolean isDeleted(){
+        return deletedAt != null;
+    }
+}
diff --git a/common/src/main/java/com/onevoice/common/exception/BaseException.java b/common/src/main/java/com/onevoice/common/exception/BaseException.java
new file mode 100644
index 00000000..1cfa57ba
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/exception/BaseException.java
@@ -0,0 +1,16 @@
+package com.onevoice.common.exception;
+
+import com.onevoice.common.dto.ResponseCode;
+
+public class BaseException extends RuntimeException {
+    private final ResponseCode responseCode;
+
+    public BaseException(ResponseCode responseCode) {
+        super(responseCode.getMessage());
+        this.responseCode = responseCode;
+    }
+
+    public ResponseCode getResponseCode() {
+        return responseCode;
+    }
+}
diff --git a/common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java b/common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java
new file mode 100644
index 00000000..773e1e1e
--- /dev/null
+++ b/common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java
@@ -0,0 +1,71 @@
+package com.onevoice.common.exception;
+
+import com.onevoice.common.dto.CommonResponse;
+import com.onevoice.common.dto.ResponseCode;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 커스텀 예외 처리 (BaseException 상속 구조)
+     */
+    @ExceptionHandler(BaseException.class)
+    public ResponseEntity<CommonResponse<Void>> handleBaseException(BaseException ex) {
+        ResponseCode code = ex.getResponseCode();
+        log.warn("[BaseException] {} - {}", code.getCode(), code.getMessage(), ex);
+        return ResponseEntity
+            .status(code.getStatus())
+            .body(CommonResponse.createCommonResponse(code, null));
+    }
+
+    /**
+     * @Valid or @Validated 바인딩 실패
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<CommonResponse<String>> handleValidationException(MethodArgumentNotValidException ex) {
+        String errorMessage = ex.getBindingResult()
+            .getFieldErrors()
+            .stream()
+            .map(error -> error.getField() + ": " + error.getDefaultMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[Validation Error] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
+
+    /**
+     * 단일 파라미터 @Validated 실패
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<CommonResponse<String>> handleConstraintViolationException(ConstraintViolationException ex) {
+        String errorMessage = ex.getConstraintViolations()
+            .stream()
+            .map(v -> v.getPropertyPath() + ": " + v.getMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[ConstraintViolation] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
+
+    /**
+     * 그 외 모든 예외
+     */
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<CommonResponse<Void>> handleUnexpectedException(Exception ex) {
+        log.error("[Unhandled Exception]", ex);
+        return ResponseEntity
+            .status(ResponseCode.INTERNAL_ERROR.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.INTERNAL_ERROR, null));
+    }
+}
diff --git a/common/src/main/resources/application.yml b/common/src/main/resources/application.yml
new file mode 100644
index 00000000..e69de29b
diff --git a/common/src/test/java/com/onevoice/common/CommonApplicationTests.java b/common/src/test/java/com/onevoice/common/CommonApplicationTests.java
new file mode 100644
index 00000000..10675fee
--- /dev/null
+++ b/common/src/test/java/com/onevoice/common/CommonApplicationTests.java
@@ -0,0 +1,13 @@
+package com.onevoice.common;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class CommonApplicationTests {
+
+	@Test
+	void contextLoads() {
+	}
+
+}
diff --git a/settings.gradle b/settings.gradle
index eb5677eb..301dc495 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -12,6 +12,7 @@ include 'seat'
 include 'payment'
 include 'notification'
 include 'admin'
+include 'common'
 
 
 project(':config').projectDir = file('config')
@@ -26,3 +27,4 @@ project(':seat').projectDir = file('seat')
 project(':payment').projectDir = file('payment')
 project(':notification').projectDir = file('notification')
 project(':admin').projectDir = file('admin')
+project(':common').projectDir = file('common')


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 9**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 65e68b3dcbd651035109dcd4557d9ee7a30e52d9 and b03b546c2d1c598376b98c6c54a8e09a135e5fe8.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `common/gradle/wrapper/gradle-wrapper.jar` is excluded by `!**/*.jar`

</details>

<details>
<summary>📒 Files selected for processing (17)</summary>

* `build.gradle` (1 hunks)
* `common/.gitattributes` (1 hunks)
* `common/.gitignore` (1 hunks)
* `common/build.gradle` (1 hunks)
* `common/gradle/wrapper/gradle-wrapper.properties` (1 hunks)
* `common/gradlew` (1 hunks)
* `common/gradlew.bat` (1 hunks)
* `common/settings.gradle` (1 hunks)
* `common/src/main/java/com/onevoice/common/CommonApplication.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/dto/CommonResponse.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/dto/ErrorResponse.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/dto/ResponseCode.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/entity/BaseEntity.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/exception/BaseException.java` (1 hunks)
* `common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java` (1 hunks)
* `common/src/test/java/com/onevoice/common/CommonApplicationTests.java` (1 hunks)
* `settings.gradle` (2 hunks)

</details>

<details>
<summary>🔇 Additional comments (25)</summary><blockquote>

<details>
<summary>common/.gitattributes (1)</summary>

`1-3`: **EOL 및 바이너리 속성 규칙이 적절하게 구성되었습니다.**

common 모듈의 `gradlew` 스크립트에 LF, Windows 배치 파일에 CRLF, JAR 파일에 바이너리 설정을 적용해 플랫폼별 줄바꿈 문제와 텍스트 변환을 방지하고 있습니다.

</details>
<details>
<summary>common/settings.gradle (1)</summary>

`1-1`: **모듈 이름 설정이 올바릅니다.**

`rootProject.name = 'common'`으로 common 모듈의 루트 프로젝트명이 명확하게 지정되어 있으며, 멀티 모듈 빌드와 일관됩니다.

</details>
<details>
<summary>build.gradle (1)</summary>

`58-58`: **공통 모듈 의존성이 정상적으로 추가되었습니다.**

`sharedModules` 블록에 `implementation project(':common')`을 추가해 공통 기능을 다른 모듈에서 재사용할 수 있도록 통합한 변경사항이 의도대로 잘 반영되었습니다.

</details>
<details>
<summary>settings.gradle (1)</summary>

`15-15`: **root 설정에 common 모듈이 포함되었습니다.**

`include 'common'` 및 `project(':common').projectDir = file('common')`를 추가하여 멀티 모듈 빌드에 common 모듈이 올바르게 통합되어 있습니다.



Also applies to: 30-30

</details>
<details>
<summary>common/src/main/java/com/onevoice/common/CommonApplication.java (1)</summary>

`1-5`: **패키지 및 의존성 선언 확인**  
`package` 및 `import` 구문이 올바르게 선언되어 있습니다. 불필요한 import가 없고, Spring Boot 애노테이션이 적용된 메인 클래스 진입점을 위한 준비가 잘 되어 있습니다.

</details>
<details>
<summary>common/gradle/wrapper/gradle-wrapper.properties (1)</summary>

`1-7`: **Gradle Wrapper 설정 확인**  
Gradle Wrapper 설정이 일관되게 구성되어 있습니다. Gradle 8.13 바이너리 다운로드 URL이 적절하며, 네트워크 타임아웃 및 검증 옵션이 설정되어 있습니다.

</details>
<details>
<summary>common/src/test/java/com/onevoice/common/CommonApplicationTests.java (2)</summary>

`1-5`: **테스트 클래스 기본 구성 적절**  
`@SpringBootTest` 및 JUnit 5 `@Test` 애노테이션이 정상적으로 적용되어 있으며, 스프링 컨텍스트 로딩 검증용 기본 테스트가 구현되어 있습니다.

---

`6-13`: **기본 컨텍스트 로드 테스트**  
`contextLoads` 메서드는 간결하며 Spring Boot 애플리케이션 컨텍스트 시작 검증에 적합합니다.

</details>
<details>
<summary>common/build.gradle (5)</summary>

`1-5`: **플러그인 선언 검토**  
`java`, `org.springframework.boot`, `io.spring.dependency-management` 플러그인 선언이 올바르게 되어 있습니다. 다만 플러그인 버전 관리는 상위 `pluginManagement`를 통해 제공되는지 확인이 필요합니다.  
  
상위 `build.gradle` 또는 `settings.gradle`에 해당 플러그인 버전이 관리되고 있는지 확인해주세요.

---

`7-8`: **그룹 및 버전 설정**  
`group`과 `version`이 일관성 있게 지정되어 있습니다. 퍼블리시나 버전 관리 시 적절히 사용될 것으로 보입니다.

---

`10-16`: **Jar 생성 설정 검토**  
`bootJar`를 비활성화하고 일반 `jar`를 활성화하여 라이브러리 형태로 배포하도록 설정했습니다. `common` 모듈이 독립 실행형이 아니라 공통 라이브러리로 사용된다는 의도에 부합합니다.

---

`18-22`: **Java Toolchain 설정**  
Java 17 툴체인이 명시되어 있어 빌드 환경의 일관성을 보장합니다.

---

`24-26`: **저장소 설정**  
`mavenCentral()`만 사용하고 있습니다. 내부 Nexus나 Artifactory가 필요한 경우 추가 고려가 필요할 수 있습니다.

</details>
<details>
<summary>common/.gitignore (5)</summary>

`1-7`: **기본 무시 패턴 확인**  
Gradle 빌드 및 일부 예외 패턴이 잘 설정되어 있어 불필요한 파일 커밋을 방지합니다.

---

`8-19`: **STS 관련 무시 패턴**  
Spring Tool Suite 전용 메타데이터와 출력 디렉터리에 대한 패턴이 적절히 포함되어 있습니다.

---

`20-28`: **IntelliJ IDEA 관련 무시 패턴**  
`.idea`, `.iml`, `out/` 등에 대한 무시 규칙이 잘 구성되어 있습니다.

---

`29-35`: **NetBeans 관련 무시 패턴**  
NetBeans 프로젝트 설정 및 빌드 산출물이 무시되도록 되어 있습니다.

---

`36-38`: **VS Code 관련 무시 패턴**  
`.vscode/`을 무시하도록 설정되어 있습니다.

</details>
<details>
<summary>common/src/main/java/com/onevoice/common/exception/BaseException.java (1)</summary>

`3-15`: **코드가 잘 구현되어 있습니다.**

Exception 클래스가 깔끔하게 구현되어 있습니다. ResponseCode를 활용하여 에러 코드와 메시지를 캡슐화하는 패턴이 적절합니다. 이 구현은 예외 처리 시 응답 코드를 함께 전달하여 GlobalExceptionHandler에서 일관된 에러 응답을 생성할 수 있게 해줍니다.

</details>
<details>
<summary>common/src/main/java/com/onevoice/common/entity/BaseEntity.java (1)</summary>

`14-39`: **엔티티 기본 클래스 구현이 잘 되어 있습니다.**

Spring Data JPA의 Auditing 기능을 활용한 생성 및 수정 정보 추적이 잘 구현되어 있습니다. `@MappedSuperclass`와 `@EntityListeners`를 적절히 사용했고, 각 필드마다 명확한 JPA 애노테이션이 적용되어 있습니다.

</details>
<details>
<summary>common/gradlew.bat (1)</summary>

`1-94`: **표준 Gradle 래퍼 스크립트가 적절히 추가되었습니다.**

표준 Gradle 래퍼 배치 스크립트가 추가되었습니다. 이 파일은 Gradle 빌드 시스템에서 자동 생성한 것으로, 팀원들이 Gradle을 별도로 설치하지 않고도 프로젝트를 빌드할 수 있게 해줍니다.

</details>
<details>
<summary>common/src/main/java/com/onevoice/common/dto/CommonResponse.java (2)</summary>

`6-10`: **응답 DTO 설계가 명확합니다.**

응답 클래스의 필드 구성이 잘 되어 있습니다. 클라이언트에게 필요한 코드, 메시지, 결과 데이터를 포함하고 있어 API 응답 형식을 표준화하는 데 효과적입니다.

---

`12-28`: **팩토리 메소드 패턴이 효과적으로 적용되었습니다.**

팩토리 메소드 패턴을 활용하여 응답 객체 생성을 간소화한 것이 좋습니다. 성공 응답, 커스텀 응답을 위한 메소드가 잘 구현되어 있습니다.

특히 `success()`와 `of()` 메소드가 `ResponseEntity`를 리턴하도록 하여 컨트롤러에서 쉽게 사용할 수 있게 한 점이 좋습니다.

</details>
<details>
<summary>common/src/main/java/com/onevoice/common/dto/ResponseCode.java (1)</summary>

`31-47`: **도메인 코드(`B`)가 0으로 고정된 상태**  
설명에 따르면 B 자리는 도메인(1=User, 5=Ticket …)이지만 성공 코드 `2000`, `2010` 의 B 자리는 `0`입니다.  
“공통/비도메인” 의미라면 주석에 명시해 주세요. 도메인이 추가될 때 중복 충돌을 예방할 수 있습니다.

</details>
<details>
<summary>common/gradlew (1)</summary>

`1-252`: **Gradle Wrapper 스크립트는 자동 생성 파일로 리뷰를 생략합니다**  
별도 수정 사항이 없으므로 검토를 건너뜹니다.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-17T07:15:06Z

Review comments:
diff_hunk: @@ -0,0 +1,5 @@
+package com.onevoice.common.dto;
+
+public record ErrorResponse(String code, String message) {
+
+}
path: common/src/main/java/com/onevoice/common/dto/ErrorResponse.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**ErrorResponse 레코드 정의가 적절합니다.**

불변성을 활용한 Java 레코드로 간결하게 작성되었으며, Spring Boot 3.x 및 Jackson 2.14+ 환경에서 직렬화/역직렬화에 문제 없을 것으로 보입니다.  
[제안] API 문서화 및 유지보수를 위해 JavaDoc 주석 또는 필요한 경우 `@JsonProperty` 애노테이션 추가를 고려해 보세요.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,13 @@
+package com.onevoice.common;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CommonApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(CommonApplication.class, args);
+	}
+
+}
path: common/src/main/java/com/onevoice/common/CommonApplication.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**메인 애플리케이션 클래스 구현 적절**  
`@SpringBootApplication`이 정상적으로 선언되었으며 `main` 메서드에서 `SpringApplication.run`이 올바르게 호출되고 있습니다.  
  
클래스에 JavaDoc을 추가하면 모듈의 역할과 목적을 명확히 설명할 수 있습니다. 예를 들어:

```java
/**
 * Common 모듈의 Spring Boot 애플리케이션 진입점 클래스입니다.
 */
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,34 @@
+plugins {
+	id 'java'
+	id 'org.springframework.boot'
+	id 'io.spring.dependency-management'
+}
+
+group = 'com.onevoice'
+version = '0.0.1-SNAPSHOT'
+
+bootJar{
+	enabled =false
+}
+
+jar{
+	enabled =true
+}
+
+java {
+	toolchain {
+		languageVersion = JavaLanguageVersion.of(17)
+	}
+}
+
+repositories {
+	mavenCentral()
+}
+
+dependencies {
+	compileOnly 'org.projectlombok:lombok'
+	annotationProcessor 'org.projectlombok:lombok'
+	implementation 'org.springframework.boot:spring-boot-starter-validation'
+	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
+	implementation 'org.springframework:spring-web'
+}
path: common/build.gradle
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**테스트 의존성 및 Web Starter 검토 필요**  
- 현재 `spring-web` 코어 라이브러리가 포함되어 있으나, 컨트롤러나 글로벌 예외 처리 기능 사용 시 `spring-boot-starter-web`을 사용하는 것이 권장됩니다.  
- `@SpringBootTest` 기반 통합 테스트를 위해 `testImplementation 'org.springframework.boot:spring-boot-starter-test'` 의존성을 추가해야 합니다.  


```diff
 dependencies {
-    implementation 'org.springframework:spring-web'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
dependencies {
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
-    implementation 'org.springframework:spring-web'
+    implementation 'org.springframework.boot:spring-boot-starter-web'
+    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,57 @@
+package com.onevoice.common.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.EntityListeners;
+import jakarta.persistence.MappedSuperclass;
+import java.time.LocalDateTime;
+import lombok.Getter;
+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;
+
+@Getter
+@MappedSuperclass
+@EntityListeners(AuditingEntityListener.class)
+public class BaseEntity {
+
+    @CreatedDate
+    @Column(name = "created_at", updatable = false)
+    protected LocalDateTime createdAt;
+
+    @CreatedBy
+    @Column(name = "created_by", updatable = false)
+    protected String createdBy;
+
+    @LastModifiedDate
+    @Column(name = "updated_at")
+    protected LocalDateTime updatedAt;
+
+    @LastModifiedBy
+    @Column(name = "updated_by")
+    protected String updatedBy;
+
+    @Column(name ="deleted_at")
+    protected LocalDateTime deletedAt;
+
+    @Column(name ="deleted_by")
+    protected String deletedBy;
+
+    /**
+     * soft delete
+     */
+    public void delete(String username){
+        this.deletedAt = LocalDateTime.now();
+        this.deletedBy = username;
+    }
+
+    public void restore(){
+        this.deletedAt = null;
+        this.deletedBy = null;
+    }
+
+    public boolean isDeleted(){
+        return deletedAt != null;
+    }
path: common/src/main/java/com/onevoice/common/entity/BaseEntity.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**소프트 삭제 기능이 효과적으로 구현되었습니다.**

소프트 삭제 관련 메소드가 잘 구현되어 있습니다. 삭제 시 사용자 정보를 함께 기록하는 것은 감사(audit) 측면에서 좋은 접근입니다. 

한 가지 고려할 점은 삭제된 엔티티에 대한 쿼리 처리입니다. 현재 구현에서는 개별 쿼리마다 `isDeleted()`를 확인해야 하는데, 나중에 필요하다면 `@Where` 또는 `@SQLDelete`, `@FilterDef` 애노테이션을 고려해볼 수 있습니다.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+package com.onevoice.common.dto;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public enum ResponseCode {
+
+    /**
+     * ────────────────────────────────────────────
+     * @Response Code Convention
+     * ───────────────────────────────────────
+     * @Format: @ABCD
+     *  @- A : HTTP 상태 범주 (4 = 4xx Client Error, 5 = 5xx Server Error 등)
+     *  @- B : 도메인 구분 (1 = User, 5 = Ticket, ...)
+     *  @- CD: 세부 에러 코드 (40 = Not Found, 90 = Conflict 등)
+     * ───────────────────────────────────────
+     * @User Domain (B = 1)
+     * ───────────────────────────────────────
+     * @4140 USER_NOT_FOUND        → 404 Not Found
+     * @4190 DUPLICATE_USER        → 409 Conflict
+     * ───────────────────────────────────────
+     * @Ticket Domain (B = 5)
+     * ───────────────────────────────────────
+     * @4590 TICKET_ALREADY_BOOKED → 409 Conflict
+     * @※ 참고: 에러 메시지와 매핑된 HTTP 상태 코드도 함께 기재하면 더 명확합니다.
+     */
+
+
+
+    // 2xx: 성공
+    SUCCESS(2000, HttpStatus.OK, "요청 성공"),
+    CREATED(2010, HttpStatus.CREATED, "생성 성공"),
+
+    // 4xx: 클라이언트 오류
+    BAD_REQUEST(4000, HttpStatus.BAD_REQUEST, "잘못된 요청입니다."),
+    UNAUTHORIZED(4010, HttpStatus.UNAUTHORIZED, "인증이 필요합니다."),
+    FORBIDDEN(4030, HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),
+    NOT_FOUND(4040, HttpStatus.NOT_FOUND, "대상을 찾을 수 없습니다."),
+
+    // 5xx: 서버 오류
+    INTERNAL_ERROR(5000, HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류입니다."),
+
+    // User
+    USER_NOT_FOUND(4140, HttpStatus.NOT_FOUND, "존재하지 않는 사용자 입니다."),
+    DUPLICATE_USER(4190, HttpStatus.CONFLICT, "이미 존재하는 사용자 입니다.");
+
+
+    private final int code; // 커스텀 코드 (우리 마음대로 정하는 거)
+    private final HttpStatus status;
+    private final String message;
+
path: common/src/main/java/com/onevoice/common/dto/ResponseCode.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**비공식적 코멘트는 배포 코드에서 제거 권장**  
`// 커스텀 코드 (우리 마음대로 정하는 거)` 는 내부 대화식 표현입니다. 운영 코드에는 공식 용어(예: “애플리케이션 정의 코드”)를 사용해 주세요.  

```diff
- private final int code; // 커스텀 코드 (우리 마음대로 정하는 거)
+ private final int code; // 애플리케이션 정의 고유 코드
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    private final int code; // 애플리케이션 정의 고유 코드
    private final HttpStatus status;
    private final String message;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,58 @@
+package com.onevoice.common.dto;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public enum ResponseCode {
+
+    /**
+     * ────────────────────────────────────────────
+     * @Response Code Convention
+     * ───────────────────────────────────────
+     * @Format: @ABCD
+     *  @- A : HTTP 상태 범주 (4 = 4xx Client Error, 5 = 5xx Server Error 등)
+     *  @- B : 도메인 구분 (1 = User, 5 = Ticket, ...)
+     *  @- CD: 세부 에러 코드 (40 = Not Found, 90 = Conflict 등)
+     * ───────────────────────────────────────
+     * @User Domain (B = 1)
+     * ───────────────────────────────────────
+     * @4140 USER_NOT_FOUND        → 404 Not Found
+     * @4190 DUPLICATE_USER        → 409 Conflict
+     * ───────────────────────────────────────
+     * @Ticket Domain (B = 5)
+     * ───────────────────────────────────────
+     * @4590 TICKET_ALREADY_BOOKED → 409 Conflict
+     * @※ 참고: 에러 메시지와 매핑된 HTTP 상태 코드도 함께 기재하면 더 명확합니다.
+     */
path: common/src/main/java/com/onevoice/common/dto/ResponseCode.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**주석 포맷 설명이 혼란을 줄 수 있습니다**  
`@ABCD` 식별자에 ‘@’ 문자가 포함되어 있어 실제 코드 값과 구별이 모호하며, 영어/한글 주석이 뒤섞여 가독성이 떨어집니다. 코드 컨벤션 설명을 Javadoc 블록으로 옮기고 ‘@’ 문자를 제거하면 IDE 툴팁에서도 일관되게 확인할 수 있습니다.  

```diff
- * @Format: @ABCD
+ * Format: ABCD
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,71 @@
+package com.onevoice.common.exception;
+
+import com.onevoice.common.dto.CommonResponse;
+import com.onevoice.common.dto.ResponseCode;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 커스텀 예외 처리 (BaseException 상속 구조)
+     */
+    @ExceptionHandler(BaseException.class)
+    public ResponseEntity<CommonResponse<Void>> handleBaseException(BaseException ex) {
+        ResponseCode code = ex.getResponseCode();
+        log.warn("[BaseException] {} - {}", code.getCode(), code.getMessage(), ex);
+        return ResponseEntity
+            .status(code.getStatus())
+            .body(CommonResponse.createCommonResponse(code, null));
+    }
+
+    /**
+     * @Valid or @Validated 바인딩 실패
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<CommonResponse<String>> handleValidationException(MethodArgumentNotValidException ex) {
+        String errorMessage = ex.getBindingResult()
+            .getFieldErrors()
+            .stream()
+            .map(error -> error.getField() + ": " + error.getDefaultMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[Validation Error] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
+
+    /**
+     * 단일 파라미터 @Validated 실패
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<CommonResponse<String>> handleConstraintViolationException(ConstraintViolationException ex) {
+        String errorMessage = ex.getConstraintViolations()
+            .stream()
+            .map(v -> v.getPropertyPath() + ": " + v.getMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[ConstraintViolation] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
path: common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**중복 코드 추출로 예외 처리기 간 일관성 향상**  
`handleValidationException` 과 `handleConstraintViolationException` 모두 BAD_REQUEST 응답을 생성하는 동일 로직이 반복됩니다. 공통 메서드로 추출해 유지보수성을 높여보세요.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,71 @@
+package com.onevoice.common.exception;
+
+import com.onevoice.common.dto.CommonResponse;
+import com.onevoice.common.dto.ResponseCode;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 커스텀 예외 처리 (BaseException 상속 구조)
+     */
+    @ExceptionHandler(BaseException.class)
+    public ResponseEntity<CommonResponse<Void>> handleBaseException(BaseException ex) {
+        ResponseCode code = ex.getResponseCode();
+        log.warn("[BaseException] {} - {}", code.getCode(), code.getMessage(), ex);
+        return ResponseEntity
+            .status(code.getStatus())
+            .body(CommonResponse.createCommonResponse(code, null));
+    }
+
+    /**
+     * @Valid or @Validated 바인딩 실패
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<CommonResponse<String>> handleValidationException(MethodArgumentNotValidException ex) {
+        String errorMessage = ex.getBindingResult()
+            .getFieldErrors()
+            .stream()
+            .map(error -> error.getField() + ": " + error.getDefaultMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[Validation Error] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
+
+    /**
+     * 단일 파라미터 @Validated 실패
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<CommonResponse<String>> handleConstraintViolationException(ConstraintViolationException ex) {
+        String errorMessage = ex.getConstraintViolations()
+            .stream()
+            .map(v -> v.getPropertyPath() + ": " + v.getMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[ConstraintViolation] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
+
+    /**
+     * 그 외 모든 예외
+     */
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<CommonResponse<Void>> handleUnexpectedException(Exception ex) {
+        log.error("[Unhandled Exception]", ex);
+        return ResponseEntity
+            .status(ResponseCode.INTERNAL_ERROR.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.INTERNAL_ERROR, null));
+    }
path: common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**미처리 예외 로그에 트레이싱 정보(예: 요청 ID) 포함 고려**  
운영 환경에서 원인 파악을 용이하게 하기 위해 Mapped Diagnostic Context(MDC)에 저장된 `traceId`/`requestId` 등을 로그에 함께 남기는 것을 권장합니다.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,71 @@
+package com.onevoice.common.exception;
+
+import com.onevoice.common.dto.CommonResponse;
+import com.onevoice.common.dto.ResponseCode;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 커스텀 예외 처리 (BaseException 상속 구조)
+     */
+    @ExceptionHandler(BaseException.class)
+    public ResponseEntity<CommonResponse<Void>> handleBaseException(BaseException ex) {
+        ResponseCode code = ex.getResponseCode();
+        log.warn("[BaseException] {} - {}", code.getCode(), code.getMessage(), ex);
+        return ResponseEntity
+            .status(code.getStatus())
+            .body(CommonResponse.createCommonResponse(code, null));
+    }
+
+    /**
+     * @Valid or @Validated 바인딩 실패
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<CommonResponse<String>> handleValidationException(MethodArgumentNotValidException ex) {
+        String errorMessage = ex.getBindingResult()
+            .getFieldErrors()
+            .stream()
+            .map(error -> error.getField() + ": " + error.getDefaultMessage())
+            .findFirst()
+            .orElse("잘못된 요청입니다.");
+        log.warn("[Validation Error] {}", errorMessage);
+        return ResponseEntity
+            .status(ResponseCode.BAD_REQUEST.getStatus())
+            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
+    }
path: common/src/main/java/com/onevoice/common/exception/GlobalExceptionHandler.java
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**첫 번째 오류 메시지만 반환되어 상세 정보가 누락됩니다**  
`MethodArgumentNotValidException` 처리 시 복수 필드가 실패해도 첫 번째 항목만 전달됩니다. 클라이언트 UX 개선을 위해 모든 필드 메시지를 리스트로 묶어 반환하거나, `errorCount`를 함께 내려주는 방안을 고려해 주세요.  

```diff
- String errorMessage = ex.getBindingResult()
-     .getFieldErrors()
-     .stream()
-     .map(error -> error.getField() + ": " + error.getDefaultMessage())
-     .findFirst()
-     .orElse("잘못된 요청입니다.");
+ String errorMessage = ex.getBindingResult()
+     .getFieldErrors()
+     .stream()
+     .map(error -> error.getField() + ": " + error.getDefaultMessage())
+     .collect(Collectors.joining(", "));
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    public ResponseEntity<CommonResponse<String>> handleValidationException(MethodArgumentNotValidException ex) {
        String errorMessage = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.joining(", "));
        log.warn("[Validation Error] {}", errorMessage);
        return ResponseEntity
            .status(ResponseCode.BAD_REQUEST.getStatus())
            .body(CommonResponse.createCommonResponse(ResponseCode.BAD_REQUEST, errorMessage));
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-17T07:15:05Z
author_association: NONE

PR comments:
user: login: lime1st
created_at: 2025-04-17T07:12:28Z
body: @coderabbitai review
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-04-17T07:12:35Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-17T07:12:38Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

이번 변경 사항에서는 `common`이라는 새로운 Gradle 모듈이 프로젝트에 추가되었습니다. `common` 모듈은 Spring Boot 기반의 공통 기능을 제공하며, 표준화된 응답 DTO(`CommonResponse`, `ErrorResponse`), 응답 코드 관리용 enum(`ResponseCode`), JPA 엔티티의 공통 기반 클래스(`BaseEntity`), 예외 처리용 베이스 예외(`BaseException`)와 글로벌 예외 처리기(`GlobalExceptionHandler`)를 포함합니다. 또한, Gradle 빌드 및 래퍼 관련 파일(`build.gradle`, `settings.gradle`, `gradlew`, `gradlew.bat`, `gradle-wrapper.properties`)과 Git 관리 파일(`.gitignore`, `.gitattributes`)이 추가되었습니다. 테스트를 위한 기본 Spring Boot 테스트 클래스도 포함되어 있습니다. 기존 루트 `settings.gradle`에는 `common` 모듈을 프로젝트에 포함시키는 설정이 추가되었으며, 상위 `build.gradle`에서는 `sharedModules`에 `:common` 모듈 의존성이 추가되었습니다.

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant GlobalExceptionHandler
    participant CommonResponse
    participant BaseException

    Client->>Controller: API 요청
    Controller->>Service: 비즈니스 로직 호출
    Service-->>Controller: 결과 또는 예외 발생
    alt 정상 처리
        Controller->>CommonResponse: CommonResponse.success(result)
        CommonResponse-->>Controller: ResponseEntity<CommonResponse>
        Controller-->>Client: 표준 응답 반환
    else BaseException 발생
        Service-->>BaseException: throw BaseException
        BaseException-->>GlobalExceptionHandler: 예외 전파
        GlobalExceptionHandler->>CommonResponse: createCommonResponse(code, null)
        GlobalExceptionHandler-->>Client: ResponseEntity<CommonResponse>
    else 기타 예외 발생
        Service-->>Exception: throw Exception
        Exception-->>GlobalExceptionHandler: 예외 전파
        GlobalExceptionHandler->>CommonResponse: createCommonResponse(INTERNAL_ERROR, null)
        GlobalExceptionHandler-->>Client: ResponseEntity<CommonResponse>
    end
```

```mermaid
sequenceDiagram
    participant Client
    participant Controller
    participant GlobalExceptionHandler
    participant CommonResponse

    Client->>Controller: @Valid/@Validated 요청
    Controller-->>GlobalExceptionHandler: MethodArgumentNotValidException 또는 ConstraintViolationException 발생
    GlobalExceptionHandler->>CommonResponse: createCommonResponse(BAD_REQUEST, errorMessage)
    GlobalExceptionHandler-->>Client: ResponseEntity<CommonResponse>
```

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡💬 Agentic Chat (Pro Plan, General Availability)</summary>
> 
> - We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=with-one-voice/ticketing-service&utm_content=5):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE