diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md new file mode 100644 index 0000000..e4b4354 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_template.md @@ -0,0 +1,25 @@ +--- +name: ISSUE_TEMPLATE +about: "\b이슈 생성 템플릿" +title: '' +labels: '' +assignees: '' + +--- + +## 💡 Issue + +- + +## 📝 To-do + + +- [ ] diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..9706280 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,21 @@ +# 💡 Issue +- resolved: #이슈번호 + +# 📸 Screenshot + + + +# 📄 Description + +- + +# 💬 To Reviewers + + - + +# 🔗 Reference + +- diff --git a/.gitignore b/.gitignore index 44fcb81..f96e1b4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ out/ ### Config ### application-secret.properties -application.yaml \ No newline at end of file +application.yaml +application.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index 28002b2..af58c56 100644 --- a/build.gradle +++ b/build.gradle @@ -24,17 +24,20 @@ repositories { } dependencies { - //JPA + // JPA implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - //Spring WEB + // PostgreSql + implementation group: 'org.postgresql', name:'postgresql', version:'42.7.3' + + // Spring WEB implementation 'org.springframework.boot:spring-boot-starter-web' - //Lombok + // Lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - //Test + // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/gradlew.bat b/gradlew.bat index 7101f8e..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@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 - -@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 +@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 + +@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/src/main/java/org/sopt/seonyakServer/SeonyakServerApplication.java b/src/main/java/org/sopt/seonyakServer/SeonyakServerApplication.java index 7e1b0e9..cd4ad2d 100644 --- a/src/main/java/org/sopt/seonyakServer/SeonyakServerApplication.java +++ b/src/main/java/org/sopt/seonyakServer/SeonyakServerApplication.java @@ -4,10 +4,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class SeonYakApplication { +public class SeonyakServerApplication { public static void main(String[] args) { - SpringApplication.run(SeonYakApplication.class, args); + SpringApplication.run(SeonyakServerApplication.class, args); } - } diff --git a/src/main/java/org/sopt/seonyakServer/global/common/dto/.gitkeep b/src/main/java/org/sopt/seonyakServer/global/common/dto/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/org/sopt/seonyakServer/global/common/dto/ResponseDto.java b/src/main/java/org/sopt/seonyakServer/global/common/dto/ResponseDto.java new file mode 100644 index 0000000..8afcc30 --- /dev/null +++ b/src/main/java/org/sopt/seonyakServer/global/common/dto/ResponseDto.java @@ -0,0 +1,19 @@ +package org.sopt.seonyakServer.global.common.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.sopt.seonyakServer.global.exception.enums.ErrorType; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ResponseDto( + String code, + T data, + String message +) { + public static ResponseDto success(final T data) { + return new ResponseDto<>("success", data, null); + } + + public static ResponseDto fail(ErrorType errorType) { + return new ResponseDto<>(errorType.getCode(), null, errorType.getMessage()); + } +} diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/GlobalExceptionHandler.java b/src/main/java/org/sopt/seonyakServer/global/exception/GlobalExceptionHandler.java index 6c7f3eb..ee134c4 100644 --- a/src/main/java/org/sopt/seonyakServer/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/sopt/seonyakServer/global/exception/GlobalExceptionHandler.java @@ -1,4 +1,21 @@ package org.sopt.seonyakServer.global.exception; +import lombok.extern.slf4j.Slf4j; +import org.sopt.seonyakServer.global.exception.enums.ErrorType; +import org.sopt.seonyakServer.global.exception.model.CustomException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice public class GlobalExceptionHandler { + // 비즈니스 로직에서 발생한 예외 (언체크) + @ExceptionHandler(CustomException.class) + public ResponseEntity handleBusinessException(CustomException e) { + log.error("GlobalExceptionHandler catch CustomException : {}", e.getErrorType().getMessage()); + return ResponseEntity + .status(e.getErrorType().getHttpStatus()) + .body(e.getErrorType()); + } } diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/ResponseDtoAdvice.java b/src/main/java/org/sopt/seonyakServer/global/exception/ResponseDtoAdvice.java new file mode 100644 index 0000000..84c8c90 --- /dev/null +++ b/src/main/java/org/sopt/seonyakServer/global/exception/ResponseDtoAdvice.java @@ -0,0 +1,37 @@ +package org.sopt.seonyakServer.global.exception; + +import org.sopt.seonyakServer.global.common.dto.ResponseDto; +import org.sopt.seonyakServer.global.exception.enums.ErrorType; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@RestControllerAdvice( + basePackages = "org.sopt.seonyakServer" +) +public class ResponseDtoAdvice implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter returnType, Class converterType) { + return !(returnType.getParameterType() == ResponseDto.class) + && MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType); + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response + ) { + if (body instanceof ErrorType) + return ResponseDto.fail((ErrorType) body); + return ResponseDto.success(body); + } +} diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/enums/.gitkeep b/src/main/java/org/sopt/seonyakServer/global/exception/enums/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/enums/ErrorType.java b/src/main/java/org/sopt/seonyakServer/global/exception/enums/ErrorType.java new file mode 100644 index 0000000..c92dc56 --- /dev/null +++ b/src/main/java/org/sopt/seonyakServer/global/exception/enums/ErrorType.java @@ -0,0 +1,69 @@ +package org.sopt.seonyakServer.global.exception.enums; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public enum ErrorType { + /** + * 400 BAD REQUEST + */ + + // 표준 오류 + REQUEST_VALIDATION_ERROR(HttpStatus.BAD_REQUEST,"40001", "잘못된 요청입니다."), + INVALID_TYPE_ERROR(HttpStatus.BAD_REQUEST,"40002","잘못된 타입이 입력되었습니다."), + INVALID_MISSING_HEADER_ERROR(HttpStatus.BAD_REQUEST,"40003", "요청에 필요한 헤더값이 존재하지 않습니다."), + INVALID_HTTP_REQUEST_ERROR(HttpStatus.BAD_REQUEST, "40004", "요청 형식이 허용된 형식과 다릅니다."), + INVALID_HTTP_METHOD_ERROR(HttpStatus.BAD_REQUEST, "40005", "지원되지 않는 HTTP method 요청입니다."), + INVALID_TOKEN_HEADER_ERROR(HttpStatus.BAD_REQUEST, "40006", "토큰 헤더값의 형식이 잘못되었습니다."), + INVALID_CODE_HEADER_ERROR(HttpStatus.BAD_REQUEST, "40007", "code 헤더값의 형식이 잘못되었습니다."), + + // S3 관련 오류 + IMAGE_EXTENSION_ERROR(HttpStatus.BAD_REQUEST, "40008", "이미지 확장자는 jpg, png, webp만 가능합니다."), + IMAGE_SIZE_ERROR(HttpStatus.BAD_REQUEST, "40009", "이미지 사이즈는 5MB를 넘을 수 없습니다."), + + // 인증 관련 오류 + EMPTY_PRINCIPLE_ERROR(HttpStatus.BAD_REQUEST, "40010", "Principle 객체가 없습니다. (null)"), + + /** + * 401 UNAUTHORIZED + */ + INVALID_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "40101", "유효하지 않는 JWT 토큰입니다."), + EXPIRED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "40102", "만료된 JWT 토큰입니다."), + UNSUPPORTED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "40103", "지원하지 않는 JWT 토큰입니다."), + EMPTY_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "40104", "JWT 토큰이 존재하지 않습니다."), + INVALID_JWT_SIGNATURE(HttpStatus.UNAUTHORIZED, "40105", "잘못된 JWT 서명입니다."), + UNKNOWN_JWT_ERROR(HttpStatus.UNAUTHORIZED, "40106", "알 수 없는 JWT 토큰 오류가 발생했습니다."), + + INVALID_SOCIAL_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "40107", "유효하지 않은 소셜 엑세스 토큰입니다."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "40108", "유효하지 않은 리프레시 토큰입니다, 다시 로그인을 해주세요."), + + /** + * 404 NOT FOUND + */ + NOT_FOUND_MEMBER_ERROR(HttpStatus.NOT_FOUND, "40401", "존재하지 않는 회원입니다."), + NOT_FOUND_REFRESH_TOKEN_ERROR(HttpStatus.NOT_FOUND, "40402", "존재하지 않는 리프레시 토큰입니다."), + + /** + * 409 CONFLICT + */ + NICKNAME_DUP_ERROR(HttpStatus.CONFLICT, "40901", "중복된 회원 닉네임입니다."), + + + /** + * 500 INTERNAL SERVER ERROR + */ + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "50001", "알 수 없는 서버 에러가 발생했습니다."), + GET_UPLOAD_PRESIGNED_URL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "50002", "업로드를 위한 Presigned URL 획득에 실패했습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + public int getHttpStatusCode() { + return httpStatus.value(); + } +} diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/model/.gitkeep b/src/main/java/org/sopt/seonyakServer/global/exception/model/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/org/sopt/seonyakServer/global/exception/model/CustomException.java b/src/main/java/org/sopt/seonyakServer/global/exception/model/CustomException.java new file mode 100644 index 0000000..bb54bae --- /dev/null +++ b/src/main/java/org/sopt/seonyakServer/global/exception/model/CustomException.java @@ -0,0 +1,18 @@ +package org.sopt.seonyakServer.global.exception.model; + +import lombok.Getter; +import org.sopt.seonyakServer.global.exception.enums.ErrorType; + +@Getter +public class CustomException extends RuntimeException{ + private final ErrorType errorType; + + public CustomException(ErrorType errorType) { + super(errorType.getMessage()); + this.errorType = errorType; + } + + public int getHttpStatus() { + return errorType.getHttpStatusCode(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 35c8fd9..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=SeonYak