Skip to content

Commit

Permalink
[DEV-32] 커스텀 에러코드 적용 (#281)
Browse files Browse the repository at this point in the history
* refactor: custom errorCode 정의

* feat: ExceptionResponse 스펙 수정

* refactor: ErrorCode 추가 및 적용

* refactor: Auth - 로그인 요청 custom errorCode 적용

* refactor: 수강과목 등록 - pdf parsing text custom errorCode 적용

* feat: StudentCategory - 학생 이수구분 카테고리에 졸업 카테고리가 포함 여부 검증 로직 추가

* feat: 유저아이디 찾기 커스텀 에러코드 적용

* feat: 유저검증 커스텀 에러코드 적용

* feat: 비밀번호 재설정 커스텀 에러코드 적용

* feat: 아이디 중복 체크 커스텀 에러코드 적용

* feat: 학번 중복 체크정 커스텀 에러코드 적용

* feat: 회원 탈퇴 커스텀 에러코드 적용

* feat: 회원가입 요청 커스텀 에러코드 적용

* refactor: GlobalExceptionHandler response 스펙 수정

* refactor: 불필요 import 제거
  • Loading branch information
5uhwann authored Jul 19, 2024
1 parent 7debefa commit ed4acc4
Show file tree
Hide file tree
Showing 41 changed files with 335 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@NoArgsConstructor
public class SignInRequest {

@NotBlank(message = "아아디를 입력해주세요.")
@NotBlank(message = "아이디를 입력해주세요.")
@Schema(name = "authId", example = "plzgraduate")
private String authId;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.plzgraduate.myongjigraduatebe.auth.security;

import static com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode.*;

import java.io.IOException;

import javax.servlet.ServletException;
Expand All @@ -12,6 +14,7 @@
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode;
import com.plzgraduate.myongjigraduatebe.core.exception.ExceptionResponse;

import lombok.RequiredArgsConstructor;
Expand All @@ -20,13 +23,13 @@
@RequiredArgsConstructor
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

private static final ExceptionResponse E403 = ExceptionResponse.of(HttpStatus.FORBIDDEN, "Authentication error (cause: forbidden)");
private static final ExceptionResponse E403 = ExceptionResponse.from(AUTHENTICATION_FAIL_FORBIDDEN.toString());

private final ObjectMapper om;

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
AccessDeniedException accessDeniedException) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader("content-type", "application/json");
response.getWriter().write(om.writeValueAsString(E403));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.plzgraduate.myongjigraduatebe.auth.security;

import static com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode.*;

import java.io.IOException;

import javax.servlet.ServletException;
Expand All @@ -12,20 +14,21 @@
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode;
import com.plzgraduate.myongjigraduatebe.core.exception.ExceptionResponse;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint{
private static final ExceptionResponse E401 = ExceptionResponse.of(HttpStatus.UNAUTHORIZED, "Authentication error (cause: unauthorized)");
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final ExceptionResponse E401 = ExceptionResponse.from(AUTHENTICATION_FAIL_UNAUTHORIZED.toString());

private final ObjectMapper om;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
AuthenticationException authException) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("content-type", "application/json");
response.getWriter().write(om.writeValueAsString(E401));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.plzgraduate.myongjigraduatebe.auth.security;

import static com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode.*;
import static org.hibernate.validator.internal.util.TypeHelper.isAssignable;

import java.util.Collections;
Expand All @@ -10,6 +11,7 @@
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode;
import com.plzgraduate.myongjigraduatebe.core.exception.UnAuthorizedException;
import com.plzgraduate.myongjigraduatebe.user.application.usecase.find.FindUserUseCase;
import com.plzgraduate.myongjigraduatebe.user.domain.model.User;
Expand All @@ -35,14 +37,14 @@ public Authentication authenticate(Authentication authentication) throws Authent
}

private Authentication processAuthentication(JwtAuthenticationToken authenticationToken) {
User user = findUserUseCase.findUserByAuthId(String.valueOf(authenticationToken.getPrincipal()));
try {
User user = findUserUseCase.findUserByAuthId(String.valueOf(authenticationToken.getPrincipal()));
user.matchPassword(passwordEncoder, String.valueOf(authenticationToken.getCredentials()));
return new JwtAuthenticationToken(
user.getId(), null, Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
);
} catch (IllegalArgumentException e) {
throw new UnAuthorizedException("아이디 혹은 비밀번호가 일치하지 않습니다.");
throw new UnAuthorizedException(INCORRECT_PASSWORD.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.plzgraduate.myongjigraduatebe.core.exception;

public enum ErrorCode {

INTERNAL_SEVER_ERROR,
INVALIDATED_GRADUATION_CATEGORY,
UNFITTED_GRADUATION_CATEGORY,
UNREGISTERED_USER,
INVALIDATED_STUDENT_NUMBER_TYPE,
INVALIDATED_PASSWORD_TYPE,
MISMATCHED_PASSWORD,
NOT_FOUND_AUTHID,
INVALIDATED_AUTHID_TYPE,
INCORRECT_PASSWORD,
DUPLICATED_STUDENT_NUMBER,
DUPLICATED_AUTHID,
NOT_FOUND_STUDENT_NUMBER,
INVALIDATED_AUTH_TOKEN,
INCORRECT_STUDENT_NUMBER,
NON_EXISTED_LECTURE,
UNSUPPORTED_STUDENT_CATEGORY,
UNSUPPORTED_STUDENT_NUMBER,
AUTHENTICATION_FAIL_FORBIDDEN,
AUTHENTICATION_FAIL_UNAUTHORIZED
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
package com.plzgraduate.myongjigraduatebe.core.exception;

import org.springframework.http.HttpStatus;

import lombok.Getter;

@Getter
public class ExceptionResponse {

private final int status;
private final String message;
private final String errorCode;

private ExceptionResponse(
int status,
String message
) {
this.status = status;
this.message = message;
private ExceptionResponse(String errorCode) {
this.errorCode = errorCode;
}

public static ExceptionResponse of(
HttpStatus httpStatus,
String message
) {
return new ExceptionResponse(httpStatus.value(), message);
public static ExceptionResponse from(String errorCode) {
return new ExceptionResponse(errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,62 +31,56 @@ public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handleBadRequestException(Exception e) {
log.debug("Bad request exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.BAD_REQUEST, getMessage(e));
return ExceptionResponse.from(e.getMessage());
}

@ExceptionHandler(NoSuchElementException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ExceptionResponse handleNotFoundException(Exception e) {
log.debug("Not Found exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.NOT_FOUND, getMessage(e));
return ExceptionResponse.from(e.getMessage());
}

@ExceptionHandler(UnAuthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ExceptionResponse handleUnAuthorizedException(Exception e) {
log.debug("unauthorized exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.UNAUTHORIZED, getMessage(e));
return ExceptionResponse.from(e.getMessage());
}

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handleValidationException(ConstraintViolationException e) {
log.info("validated exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.BAD_REQUEST, getViolationErrorMessage(e));
return ExceptionResponse.from(getViolationErrorMessage(e));
}



@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.debug("validation exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.BAD_REQUEST, getBindingErrorMessage(e));
return ExceptionResponse.from(getBindingErrorMessage(e));
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.debug("graduation category mismatch exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.BAD_REQUEST, getMethodArgumentTypeMismatchErrorMessage(e));
log.debug("graduation category mismatch exception occurred: {}", getMethodArgumentTypeMismatchErrorMessage(e));
return ExceptionResponse.from(ErrorCode.INVALIDATED_GRADUATION_CATEGORY.toString());
}

@ExceptionHandler({PdfParsingException.class, InvalidPdfException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ExceptionResponse handlePdfException(Exception e) {
log.warn("pdf exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.BAD_REQUEST, getMessage(e));
return ExceptionResponse.from(e.getMessage());
}

@ExceptionHandler({RuntimeException.class, Exception.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ExceptionResponse handleRuntimeException(RuntimeException e) {
log.error("Unexpected exception occurred: {}", e.getMessage(), e);
return ExceptionResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, getMessage(e));
}

private String getMessage(Exception e) {
return Optional.ofNullable(e.getCause()).map(Throwable::getMessage).orElse(e.getMessage());
return ExceptionResponse.from(ErrorCode.INTERNAL_SEVER_ERROR.toString());
}

private String getViolationErrorMessage(ConstraintViolationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class CalculateSingleDetailGraduationService implements CalculateSingleDe
@Override
public DetailGraduationResult calculateSingleDetailGraduation(Long userId, GraduationCategory graduationCategory) {
User user = findUserUseCase.findUserById(userId);
user.getStudentCategory().validateGraduationCategoryInclusion(graduationCategory);
TakenLectureInventory takenLectures = findTakenLectureUseCase.findTakenLectures(userId);
CalculateDetailGraduationUseCase calculateDetailGraduationUseCase = determineCalculateDetailGraduationUseCase(
graduationCategory);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.plzgraduate.myongjigraduatebe.parsing.application.service;

import static com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode.*;
import static com.plzgraduate.myongjigraduatebe.user.domain.model.StudentCategory.ASSOCIATED_MAJOR;
import static com.plzgraduate.myongjigraduatebe.user.domain.model.StudentCategory.DOUBLE_SUB;

Expand All @@ -9,6 +10,7 @@
import org.springframework.transaction.annotation.Transactional;

import com.plzgraduate.myongjigraduatebe.completedcredit.application.usecase.GenerateOrModifyCompletedCreditUseCase;
import com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode;
import com.plzgraduate.myongjigraduatebe.core.exception.InvalidPdfException;
import com.plzgraduate.myongjigraduatebe.core.exception.PdfParsingException;
import com.plzgraduate.myongjigraduatebe.core.meta.UseCase;
Expand Down Expand Up @@ -81,7 +83,7 @@ private void validateParsingText(String parsingText) {

private void validateStudentNumber(User user, ParsingInformation parsingInformation) {
if (!user.compareStudentNumber(parsingInformation.getStudentNumber())) {
throw new InvalidPdfException("본인의 학번과 PDF 학번이 일치하지 않습니다.");
throw new InvalidPdfException(INCORRECT_STUDENT_NUMBER.toString());
}
}

Expand All @@ -100,7 +102,7 @@ private List<TakenLectureInformation> getSaveTakenLectureCommand(
private void checkUnSupportedUser(ParsingInformation parsingInformation) {
if (parsingInformation.getStudentCategory() == ASSOCIATED_MAJOR
|| parsingInformation.getStudentCategory() == DOUBLE_SUB) {
throw new IllegalArgumentException("연계전공, 복수+부전공은 참여가 어렵습니다. 빠른 시일 내에 업데이트하도록 하겠습니다.");
throw new IllegalArgumentException(ErrorCode.UNSUPPORTED_STUDENT_CATEGORY.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import org.springframework.transaction.annotation.Transactional;

import com.plzgraduate.myongjigraduatebe.core.exception.ErrorCode;
import com.plzgraduate.myongjigraduatebe.core.meta.UseCase;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.FindLecturesUseCase;
import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
Expand All @@ -18,10 +19,12 @@
import com.plzgraduate.myongjigraduatebe.user.domain.model.User;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@UseCase
@Transactional
@RequiredArgsConstructor
@Slf4j
class SaveTakenLectureFromParsingTextService implements SaveTakenLectureFromParsingTextUseCase {

private final SaveTakenLecturePort saveTakenLecturePort;
Expand All @@ -38,18 +41,20 @@ private List<TakenLecture> makeTakenLectures(User user, List<TakenLectureInforma
Map<String, Lecture> lectureMap) {
return takenLectureInformationList.stream()
.map(takenLectureInformation -> {
Lecture lecture = getLectureFromLectureMap(lectureMap, takenLectureInformation);
return TakenLecture.of(user, lecture, takenLectureInformation.getYear(),
takenLectureInformation.getSemester());
}
).collect(Collectors.toList());
Lecture lecture = getLectureFromLectureMap(lectureMap, takenLectureInformation);
return TakenLecture.of(user, lecture, takenLectureInformation.getYear(),
takenLectureInformation.getSemester());
}
).collect(Collectors.toList());
}

private Lecture getLectureFromLectureMap(Map<String, Lecture> lectureMap,
TakenLectureInformation takenLectureInformation) {
return Optional.ofNullable(lectureMap.get(takenLectureInformation.getLectureCode()))
.orElseThrow(
() -> new IllegalArgumentException(takenLectureInformation.getLectureCode() + "이 데이터베이스에 존재하지 않습니다."));
.orElseThrow(() -> {
log.warn("Not Found Lecture in Database: {}", takenLectureInformation.getLectureCode());
return new IllegalArgumentException(ErrorCode.NON_EXISTED_LECTURE.toString());
});
}

private Map<String, Lecture> makeLectureMapByLectureCodes(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.plzgraduate.myongjigraduatebe.user.api.findauthid;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.springframework.web.bind.annotation.PathVariable;

import com.plzgraduate.myongjigraduatebe.user.api.findauthid.dto.response.UserAuthIdResponse;
Expand All @@ -12,5 +15,5 @@
public interface FindAuthIdApiPresentation {

UserAuthIdResponse findUserAuthId(
@Parameter(name = "studentNumber", description = "학번", in = ParameterIn.PATH) @PathVariable String studentNumber);
@Parameter(name = "studentNumber", description = "학번", in = ParameterIn.PATH) @PathVariable @Pattern(regexp = "^60\\d{6}$", message = "INVALIDATED_STUDENT_NUMBER_TYPE") String studentNumber);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package com.plzgraduate.myongjigraduatebe.user.api.findauthid;

import javax.validation.constraints.Pattern;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.plzgraduate.myongjigraduatebe.core.meta.WebAdapter;
import com.plzgraduate.myongjigraduatebe.user.application.usecase.find.FindUserAuthIdUseCase;
import com.plzgraduate.myongjigraduatebe.user.api.findauthid.dto.response.UserAuthIdResponse;
import com.plzgraduate.myongjigraduatebe.user.application.usecase.find.FindUserAuthIdUseCase;

import lombok.RequiredArgsConstructor;

@WebAdapter
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Validated
public class FindAuthIdController implements FindAuthIdApiPresentation {

private final FindUserAuthIdUseCase findUserAuthIdUseCase;

@GetMapping("/{studentNumber}/auth-id")
public UserAuthIdResponse findUserAuthId(@PathVariable String studentNumber) {
public UserAuthIdResponse findUserAuthId(
@PathVariable @Pattern(regexp = "^60\\d{6}$", message = "INVALIDATED_STUDENT_NUMBER_TYPE") String studentNumber) {
String foundUserAuthId = findUserAuthIdUseCase.findUserAuthId(studentNumber);
return UserAuthIdResponse.of(foundUserAuthId, studentNumber);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.plzgraduate.myongjigraduatebe.user.api.resetpassword;

import javax.validation.Valid;
import javax.validation.constraints.Pattern;

import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -22,7 +23,7 @@ public interface ResetPasswordApiPresentation {
@Parameter(name = "auth-id", description = "아이디")
ValidateUserResponse validateUser(
@Parameter(name = "studentNumber", description = "학번", in = ParameterIn.PATH)
@PathVariable String studentNumber,
@PathVariable @Pattern(regexp = "^60\\d{6}$", message = "INVALIDATED_STUDENT_NUMBER_TYPE") String studentNumber,
@RequestParam("auth-id") String authId);

@PatchMapping("/password")
Expand Down
Loading

0 comments on commit ed4acc4

Please sign in to comment.