diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java index 31e6747db..4609d5f96 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java @@ -115,7 +115,7 @@ public interface AuthApi { description = "ok" ) }) - @GetMapping( + @PostMapping( value = "/sign-up", produces = {"application/json"} ) diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/SignUpRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/SignUpRequest.java index 2add4b8f5..b8d1fd620 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/SignUpRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/SignUpRequest.java @@ -14,16 +14,16 @@ public record SignUpRequest( String authId, @Schema(description = "사용자 이메일") - @NotBlank(message = "사용자 이메일은 필수입니다.") + @NotNull(message = "사용자 이메일은 필수입니다.") @Email String email, @Schema(description = "사용자 프로필 url") - @NotBlank(message = "사용자 프로필 url은 필수입니다.") + @NotNull(message = "사용자 프로필 url은 필수입니다.") String profileUrl, @Schema(description = "소셜 프로바이더 타입") - @NotNull(message = "소셜 프로바이더 타입은 필수입니다.") + @NotBlank(message = "소셜 프로바이더 타입은 필수입니다.") SocialType socialType ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/TokenReIssueRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/TokenReIssueRequest.java index e1f2133ac..4e13a35fb 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/TokenReIssueRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/TokenReIssueRequest.java @@ -2,8 +2,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import org.springframework.validation.annotation.Validated; @Schema(description = "토큰 재발급 요청") public record TokenReIssueRequest( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/VerificationMessageSendRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/VerificationMessageSendRequest.java index 4785e5605..08c22041f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/VerificationMessageSendRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/VerificationMessageSendRequest.java @@ -1,13 +1,13 @@ package site.timecapsulearchive.core.domain.auth.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.validation.annotation.Validated; +import jakarta.validation.constraints.Pattern; @Schema(description = "인증 문자 요청") -@Validated public record VerificationMessageSendRequest( @Schema(description = "핸드폰 번호") + @Pattern(regexp = "^01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$", message = "10 ~ 11 자리의 숫자만 입력 가능합니다.") String phone ) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TemporaryTokenResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TemporaryTokenResponse.java index 83f595ac0..91c26d70c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TemporaryTokenResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TemporaryTokenResponse.java @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.domain.auth.dto.response; import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.validation.annotation.Validated; @Schema(description = "임시 인증 토큰") public record TemporaryTokenResponse( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TokenResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TokenResponse.java index 44624298e..65dc5104b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TokenResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/response/TokenResponse.java @@ -1,7 +1,6 @@ package site.timecapsulearchive.core.domain.auth.dto.response; import io.swagger.v3.oas.annotations.media.Schema; -import org.springframework.validation.annotation.Validated; @Schema(description = "완전한 인증 토큰") public record TokenResponse( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/entity/SocialType.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/entity/SocialType.java index b82fb64c7..5f3de55a9 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/entity/SocialType.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/entity/SocialType.java @@ -1,8 +1,15 @@ package site.timecapsulearchive.core.domain.auth.entity; +import com.fasterxml.jackson.annotation.JsonCreator; + public enum SocialType { KAKAO, GOOGLE; + @JsonCreator + public static SocialType from(String s) { + return SocialType.valueOf(s.toUpperCase()); + } + public static SocialType getSocialType(String registrationId) { if (isKakaoLogin(registrationId)) { return SocialType.KAKAO; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/AlreadyReIssuedTokenException.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/AlreadyReIssuedTokenException.java index 4ce6088dd..7e3617c42 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/AlreadyReIssuedTokenException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/AlreadyReIssuedTokenException.java @@ -9,6 +9,6 @@ public class AlreadyReIssuedTokenException extends BusinessException { public AlreadyReIssuedTokenException() { - super(ErrorCode.ALREADY_RE_ISSUED_TOKEN_EXCEPTION); + super(ErrorCode.ALREADY_RE_ISSUED_TOKEN_ERROR); } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginFailureHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginFailureHandler.java index 8f179b140..0f6cf4578 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginFailureHandler.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginFailureHandler.java @@ -6,7 +6,6 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -30,8 +29,7 @@ public void onAuthenticationFailure( log.info("oauth2 인증 실패", exception); ErrorResponse errorResponse = ErrorResponse.create( - ErrorCode.OAUTH2_NOT_AUTHENTICATED_EXCEPTION.getCode(), - exception.getMessage() + ErrorCode.OAUTH2_NOT_AUTHENTICATED_ERROR ); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginSuccessHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginSuccessHandler.java index b9022bafc..943eee281 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginSuccessHandler.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/exception/OAuth2LoginSuccessHandler.java @@ -51,8 +51,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("oauth2 인증 실패", exception); ErrorResponse errorResponse = ErrorResponse.create( - ErrorCode.INTERNAL_SERVER_ERROR.getCode(), - exception.getMessage() + ErrorCode.INTERNAL_SERVER_ERROR ); response.getWriter() diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java index 812272339..74480f75f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApi.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; -import site.timecapsulearchive.core.domain.auth.dto.request.CheckStatusRequest; +import site.timecapsulearchive.core.domain.member.dto.reqeust.CheckStatusRequest; import site.timecapsulearchive.core.domain.member.dto.reqeust.MemberDetailUpdateRequest; import site.timecapsulearchive.core.domain.member.dto.response.MemberDetailResponse; import site.timecapsulearchive.core.domain.member.dto.response.MemberStatusResponse; @@ -35,7 +35,7 @@ public interface MemberApi { ) }) @GetMapping( - value = "/me", + value = "/", produces = {"application/json"} ) ResponseEntity findMemberById(); @@ -53,7 +53,7 @@ public interface MemberApi { ) }) @PatchMapping( - value = "/me", + value = "/", consumes = {"multipart/form-data"} ) ResponseEntity updateMemberById(@ModelAttribute MemberDetailUpdateRequest request); @@ -70,7 +70,7 @@ public interface MemberApi { ) }) @GetMapping( - value = "/me/status", + value = "/status", produces = {"application/json"} ) ResponseEntity> checkStatus( diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java index da0ec2ece..ad6a71231 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/api/MemberApiController.java @@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import site.timecapsulearchive.core.domain.auth.dto.request.CheckStatusRequest; +import site.timecapsulearchive.core.domain.member.dto.reqeust.CheckStatusRequest; import site.timecapsulearchive.core.domain.member.dto.reqeust.MemberDetailUpdateRequest; import site.timecapsulearchive.core.domain.member.dto.response.MemberDetailResponse; import site.timecapsulearchive.core.domain.member.dto.response.MemberStatusResponse; @@ -16,7 +16,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/") +@RequestMapping("/me") public class MemberApiController implements MemberApi { private final MemberService memberService; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/CheckStatusRequest.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/dto/reqeust/CheckStatusRequest.java similarity index 91% rename from backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/CheckStatusRequest.java rename to backend/core/src/main/java/site/timecapsulearchive/core/domain/member/dto/reqeust/CheckStatusRequest.java index c0a68114b..b24dcd3df 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/dto/request/CheckStatusRequest.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/dto/reqeust/CheckStatusRequest.java @@ -1,4 +1,4 @@ -package site.timecapsulearchive.core.domain.auth.dto.request; +package site.timecapsulearchive.core.domain.member.dto.reqeust; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index d7681fa42..e323f744f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -35,6 +35,7 @@ public MemberStatusResponse checkStatus( String authId, SocialType socialType ) { + Boolean isVerified = memberQueryRepository.findIsVerifiedByAuthIdAndSocialType( authId, socialType diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java index af74b0654..c5d08cf1c 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; @@ -73,7 +72,7 @@ private RequestMatcher notRequireAuthenticationMatcher() { antMatcher("/v3/api-docs/**"), antMatcher("/swagger-ui/**"), antMatcher(HttpMethod.POST, "/auth/token/re-issue"), - antMatcher(HttpMethod.POST, "/me/status")); + antMatcher(HttpMethod.GET, "/me/status")); } @Bean diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java index 59092745d..63d6b339f 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorCode.java @@ -9,16 +9,18 @@ public enum ErrorCode { //global INTERNAL_SERVER_ERROR(500, "G001", "서버에 오류가 발생하였습니다."), + INPUT_INVALID_VALUE_ERROR(400, "G002", "잘못된 입력 값입니다."), + INPUT_INVALID_TYPE_ERROR(400, "G003", "잘못된 입력 타입입니다."), //jwt - INVALID_TOKEN_EXCEPTION(400, "J001", "jwt 토큰이 유효하지 않습니다."), - ALREADY_RE_ISSUED_TOKEN_EXCEPTION(400, "J002", "이미 액세스 토큰 재발급에 사용된 리프레시 토큰입니다."), + INVALID_TOKEN_ERROR(400, "J001", "jwt 토큰이 유효하지 않습니다."), + ALREADY_RE_ISSUED_TOKEN_ERROR(400, "J002", "이미 액세스 토큰 재발급에 사용된 리프레시 토큰입니다."), //auth - AUTHENTICATION_EXCEPTION(401, "A001", "인증에 실패했습니다. 인증 수단이 유효한지 확인하세요."), + AUTHENTICATION_ERROR(401, "A001", "인증에 실패했습니다. 인증 수단이 유효한지 확인하세요."), //ouath - OAUTH2_NOT_AUTHENTICATED_EXCEPTION(401, "O001", "OAuth2 인증에 실패하였습니다."); + OAUTH2_NOT_AUTHENTICATED_ERROR(401, "O001", "OAuth2 인증에 실패하였습니다."); private final int status; private final String code; diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorResponse.java index 20e3a3055..2bb0c0887 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/ErrorResponse.java @@ -3,10 +3,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.Collections; import java.util.List; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; @Schema(description = "에러 발생 시 응답") public record ErrorResponse( - @Schema(description = "에러 코드") String code, @@ -14,18 +15,45 @@ public record ErrorResponse( String message, @Schema(description = "에러 리스트 ex) 필드 에러들") - List result + List errors ) { - public static ErrorResponse create(String code, String message) { - return new ErrorResponse(code, message, Collections.emptyList()); + public static ErrorResponse create(final ErrorCode errorCode) { + return new ErrorResponse( + errorCode.getCode(), + errorCode.getMessage(), + Collections.emptyList() + ); + } + + public static ErrorResponse create(final ErrorCode errorCode, + final BindingResult bindingResult) { + return new ErrorResponse( + errorCode.getCode(), + errorCode.getMessage(), + Error.from(bindingResult) + ); } - private record Error( + public record Error( String field, String value, String reason ) { + public static List from(final BindingResult bindingResult) { + return bindingResult.getFieldErrors().stream() + .map(Error::from) + .toList(); + } + + private static Error from(final FieldError fieldError) { + return new Error( + fieldError.getField(), + String.valueOf(fieldError.getRejectedValue()), + fieldError.getDefaultMessage() + ); + } + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java index 420ab64e7..bcfc8f136 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/GlobalExceptionHandler.java @@ -1,7 +1,13 @@ package site.timecapsulearchive.core.global.error; +import static site.timecapsulearchive.core.global.error.ErrorCode.INPUT_INVALID_TYPE_ERROR; +import static site.timecapsulearchive.core.global.error.ErrorCode.INPUT_INVALID_VALUE_ERROR; + import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import site.timecapsulearchive.core.global.error.exception.BusinessException; @@ -10,14 +16,38 @@ @Slf4j public class GlobalExceptionHandler { + @ExceptionHandler(Exception.class) + protected ResponseEntity handleGlobalException(final Exception e) { + log.error(e.getMessage(), e); + ErrorResponse errorResponse = ErrorResponse.create(ErrorCode.INTERNAL_SERVER_ERROR); + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } + @ExceptionHandler(BusinessException.class) protected ResponseEntity handleBusinessException(final BusinessException e) { - log.error("handleBusinessException", e); - final ErrorCode errorCode = e.getErrorCode(); - final ErrorResponse errorResponse = ErrorResponse.create(errorCode.getCode(), - errorCode.getMessage()); + log.error(e.getMessage(), e); + ErrorCode errorCode = e.getErrorCode(); + ErrorResponse errorResponse = ErrorResponse.create(errorCode); return ResponseEntity.status(errorCode.getStatus()) .body(errorResponse); } + + @ExceptionHandler + protected ResponseEntity handleRequestArgumentNotValidException( + MethodArgumentNotValidException e) { + log.warn(e.getMessage()); + ErrorResponse response = ErrorResponse.create(INPUT_INVALID_VALUE_ERROR, e.getBindingResult()); + return ResponseEntity.status(INPUT_INVALID_VALUE_ERROR.getStatus()) + .body(response); + } + + @ExceptionHandler + protected ResponseEntity handleRequestTypeNotValidException( + HttpMessageNotReadableException e) { + log.warn(e.getMessage()); + ErrorResponse response = ErrorResponse.create(INPUT_INVALID_TYPE_ERROR); + return ResponseEntity.status(INPUT_INVALID_VALUE_ERROR.getStatus()) + .body(response); + } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InvalidTokenException.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InvalidTokenException.java index 48070226e..bceeea249 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InvalidTokenException.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/error/exception/InvalidTokenException.java @@ -8,10 +8,10 @@ public class InvalidTokenException extends BusinessException { public InvalidTokenException() { - super(ErrorCode.INVALID_TOKEN_EXCEPTION); + super(ErrorCode.INVALID_TOKEN_ERROR); } public InvalidTokenException(Throwable throwable) { - super(ErrorCode.INVALID_TOKEN_EXCEPTION, throwable); + super(ErrorCode.INVALID_TOKEN_ERROR, throwable); } } \ No newline at end of file diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java index 1e4e223e0..eb7e7923b 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/jwt/JwtAuthenticationFilter.java @@ -99,8 +99,7 @@ private void unsuccessfulAuthentication( SecurityContextHolder.clearContext(); ErrorResponse errorResponse = ErrorResponse.create( - ErrorCode.AUTHENTICATION_EXCEPTION.getCode(), - exception.getMessage() + ErrorCode.AUTHENTICATION_ERROR ); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);