Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import clap.server.application.service.auth.LoginAttemptService;
import clap.server.exception.AuthException;
import clap.server.exception.code.CommonErrorCode;
import clap.server.exception.code.GlobalErrorCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -35,7 +35,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

if (request.getRequestURI().equals(LOGIN_ENDPOINT)) {
if (sessionId == null) {
throw new AuthException(CommonErrorCode.BAD_REQUEST);
throw new AuthException(GlobalErrorCode.BAD_REQUEST);
}
loginAttemptService.checkAccountIsLocked(sessionId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import clap.server.exception.JwtException;
import clap.server.exception.code.AuthErrorCode;
import clap.server.exception.code.BaseErrorCode;
import clap.server.exception.code.CommonErrorCode;
import clap.server.exception.code.GlobalErrorCode;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
Expand Down Expand Up @@ -36,7 +36,7 @@ public static BaseErrorCode determineErrorCode(Exception exception, BaseErrorCod
public static JwtException determineAuthErrorException(Exception exception) {
return findAuthErrorException(exception).orElseGet(
() -> {
BaseErrorCode errorCode = determineErrorCode(exception, CommonErrorCode.INTERNAL_SERVER_ERROR);
BaseErrorCode errorCode = determineErrorCode(exception, GlobalErrorCode.INTERNAL_SERVER_ERROR);
log.debug(exception.getMessage(), exception);
return new JwtException(errorCode);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package clap.server.adapter.inbound.web.example;

import clap.server.common.annotation.architecture.WebAdapter;
import clap.server.common.annotation.swagger.ApiErrorCodes;
import clap.server.common.annotation.swagger.DevelopOnlyApi;
import clap.server.exception.code.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Tag(name = "*. 에러 응답")
@WebAdapter
@RequestMapping("/api/examples")
public class ErrorExampleController {

@GetMapping("/global")
@DevelopOnlyApi
@Operation(summary = "글로벌 (aop, 서버 내부 오류등) 관련 에러 코드 나열")
@ApiErrorCodes(GlobalErrorCode.class)
public void getGlobalErrorCode() {}

@GetMapping("/member")
@DevelopOnlyApi
@Operation(summary = "회원 도메인 관련 에러 코드 나열")
@ApiErrorCodes(MemberErrorCode.class)
public void getMemberErrorCode() {}

@GetMapping("/auth")
@DevelopOnlyApi
@Operation(summary = "인증 및 인가 관련 에러 코드 나열")
@ApiErrorCodes(MemberErrorCode.class)
public void getAuthErrorCode() {}

@GetMapping("/task")
@DevelopOnlyApi
@Operation(summary = "작업 도메인 관련 에러 코드 나열")
@ApiErrorCodes(TaskErrorCode.class)
public void getTaskErrorCode() {}

@GetMapping("/notification")
@DevelopOnlyApi
@Operation(summary = "알림 도메인 및 웹훅 관련 에러 코드 나열")
@ApiErrorCodes(TaskErrorCode.class)
public void getNotificationErrorCode() {}

@GetMapping("/comment")
@DevelopOnlyApi
@Operation(summary = "댓글 도메인 관련 에러 코드 나열")
@ApiErrorCodes(CommentErrorCode.class)
public void getCommentErrorCode() {}

@GetMapping("/statistic")
@DevelopOnlyApi
@Operation(summary = "작업 통계 관련 에러 코드 나열")
@ApiErrorCodes(LabelErrorCode.class)
public void getStatisticsErrorCode() {}

@GetMapping("/label")
@DevelopOnlyApi
@Operation(summary = "라벨 도메인 관련 에러 코드 나열")
@ApiErrorCodes(LabelErrorCode.class)
public void getLabelErrorCode() {}

@GetMapping("/department")
@DevelopOnlyApi
@Operation(summary = "부서 도메인 관련 에러 코드 나열")
@ApiErrorCodes(DepartmentErrorCode.class)
public void getDepartmentErrorCode() {}

@GetMapping("/file")
@DevelopOnlyApi
@Operation(summary = "파일 처리 관련 에러 코드 나열")
@ApiErrorCodes(FileErrorcode.class)
public void getFileErrorCode() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import clap.server.config.s3.KakaoS3Config;
import clap.server.common.constants.FilePathConstants;
import clap.server.exception.S3Exception;
import clap.server.exception.code.AttachmentErrorcode;
import clap.server.exception.code.FileErrorcode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
Expand Down Expand Up @@ -39,7 +39,7 @@ public String uploadSingleFile(FilePathConstants filePrefix, MultipartFile file)
Files.delete(filePath);
return getFileUrl(objectKey);
} catch (IOException e) {
throw new S3Exception(AttachmentErrorcode.FILE_UPLOAD_REQUEST_FAILED);
throw new S3Exception(FileErrorcode.FILE_UPLOAD_REQUEST_FAILED);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void checkAccountIsLocked(String sessionId) {
if (minutesSinceLastAttemptInMillis <= LOCK_TIME_DURATION) {
throw new AuthException(AuthErrorCode.ACCOUNT_IS_LOCKED);
}
commandLoginLogPort.deleteById(sessionId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import clap.server.common.utils.FileUtils;
import clap.server.domain.model.member.Member;
import clap.server.exception.ApplicationException;
import clap.server.exception.code.AttachmentErrorcode;
import clap.server.exception.code.MemberErrorCode;
import clap.server.exception.code.FileErrorcode;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -27,7 +26,7 @@ class UpdateMemberInfoService implements UpdateMemberInfoUsecase {
@Override
public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, MultipartFile profileImage) throws IOException {
if (!FileUtils.validImageFile(profileImage.getInputStream())) {
throw new ApplicationException(AttachmentErrorcode.UNSUPPORTED_FILE_TYPE);
throw new ApplicationException(FileErrorcode.UNSUPPORTED_FILE_TYPE);
}
Member member = memberService.findActiveMember(memberId);
String profileImageUrl = s3UploadPort.uploadSingleFile(FilePathConstants.MEMBER_IMAGE, profileImage);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package clap.server.common.annotation.swagger;

import clap.server.exception.code.BaseErrorCode;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiErrorCodes {
Class<? extends BaseErrorCode> value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package clap.server.common.annotation.swagger;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DevelopOnlyApi {}
8 changes: 8 additions & 0 deletions src/main/java/clap/server/config/swagger/ErrorExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package clap.server.config.swagger;

public record ErrorExample(
int code,
String customCode,
String message
) {
}
86 changes: 86 additions & 0 deletions src/main/java/clap/server/config/swagger/SwaggerConfig.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
package clap.server.config.swagger;

import clap.server.common.annotation.swagger.ApiErrorCodes;
import clap.server.exception.code.BaseErrorCode;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static clap.server.common.constants.AuthConstants.AUTHORIZATION;
import static java.util.stream.Collectors.groupingBy;

@Configuration
public class SwaggerConfig {
Expand Down Expand Up @@ -59,4 +72,77 @@ private static Components getComponents() {
return new Components()
.addSecuritySchemes(AUTHORIZATION.getValue(), securityScheme);
}

@Bean
public OperationCustomizer customize() {
return (Operation operation, HandlerMethod handlerMethod) -> {
ApiErrorCodes apiErrorCodeExample =
handlerMethod.getMethodAnnotation(ApiErrorCodes.class);

if (apiErrorCodeExample != null) {
generateErrorCodeResponse(operation, apiErrorCodeExample.value());
}
return operation;
};
}

private void generateErrorCodeResponse(Operation operation, Class<? extends BaseErrorCode> type) {
ApiResponses responses = operation.getResponses();
BaseErrorCode[] errorCodes = type.getEnumConstants();
Map<Integer, List<ErrorExampleHolder>> statusWithExampleHolders = Arrays.stream(errorCodes)
.map(errorCode -> ErrorExampleHolder.builder()
.example(getSwaggerExample(errorCode))
.name(errorCode.name())
.code(errorCode.getHttpStatus().value())
.build())
.collect(groupingBy(ErrorExampleHolder::getCode));

addExamplesToResponses(responses, statusWithExampleHolders);
}


/**
* {@code @ApiErrorCodes} 어노테이션이 존재할 경우 {@code ApiResponses}에 {@code Example}를 추가하는 메소드
*
* @param responses
* @param statusWithExampleHolders
*/
private void addExamplesToResponses(
ApiResponses responses,
Map<Integer, List<ErrorExampleHolder>> statusWithExampleHolders
) {
statusWithExampleHolders.forEach(
(status, v) -> {
Content content = new Content();
MediaType mediaType = new MediaType();
ApiResponse apiResponse = new ApiResponse();

v.forEach(
exampleHolder -> mediaType.addExamples(
exampleHolder.getName(),
exampleHolder.getExample()
)
);

content.addMediaType("application/json", mediaType);
apiResponse.setContent(content);
responses.addApiResponse(String.valueOf(status), apiResponse);
});
}


/**
* {@code BaseErrorCode}를 통해 {@code Example}를 생성하는 메소드
*
* @param errorCode
* @return
*/
private Example getSwaggerExample(BaseErrorCode errorCode) {
ErrorExample errorExample = new ErrorExample(errorCode.getHttpStatus().value(), errorCode.getCustomCode(), errorCode.getMessage());
Example example = new Example();
example.setValue(errorExample);

return example;
}

}
10 changes: 5 additions & 5 deletions src/main/java/clap/server/exception/ExceptionAdvice.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import clap.server.exception.code.AuthErrorCode;
import clap.server.exception.code.BaseErrorCode;
import clap.server.exception.code.CommonErrorCode;
import clap.server.exception.code.GlobalErrorCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
Expand Down Expand Up @@ -48,7 +48,7 @@ public ResponseEntity<Object> handleMethodArgumentNotValid(
return handleExceptionInternalArgs(
e,
HttpHeaders.EMPTY,
CommonErrorCode.BAD_REQUEST,
GlobalErrorCode.BAD_REQUEST,
request,
errors
);
Expand All @@ -61,7 +61,7 @@ public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequ
.findFirst()
.orElseThrow(() -> new RuntimeException("ConstraintViolationException Error"));

return handleExceptionInternalConstraint(e, CommonErrorCode.valueOf(errorMessage), HttpHeaders.EMPTY, request);
return handleExceptionInternalConstraint(e, GlobalErrorCode.valueOf(errorMessage), HttpHeaders.EMPTY, request);
}

@ExceptionHandler
Expand All @@ -70,9 +70,9 @@ public ResponseEntity<Object> exception(Exception e, WebRequest request) {

return handleExceptionInternalFalse(
e,
CommonErrorCode.INTERNAL_SERVER_ERROR,
GlobalErrorCode.INTERNAL_SERVER_ERROR,
HttpHeaders.EMPTY,
CommonErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus(),
GlobalErrorCode.INTERNAL_SERVER_ERROR.getHttpStatus(),
request,
e.getMessage()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

@Getter
@RequiredArgsConstructor
public enum AttachmentErrorcode implements BaseErrorCode {
public enum FileErrorcode implements BaseErrorCode {
FILE_UPLOAD_REQUEST_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "FILE_001", "파일 업로드에 실패하였습니다."),
UNSUPPORTED_FILE_TYPE(HttpStatus.BAD_REQUEST, "FILE_002", "이미지가 아닌 파일 유형입니다."),;

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

@Getter
@AllArgsConstructor
public enum CommonErrorCode implements BaseErrorCode {
public enum GlobalErrorCode implements BaseErrorCode {

/**
* Common Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@Getter
@RequiredArgsConstructor
public enum StatisticsErrorCode implements BaseErrorCode{
STATISTICS_BAD_REQUEST(HttpStatus.BAD_REQUEST, "STATISTICS_001", "잘못된 통계 조회 파라미터 입력.");
STATISTICS_BAD_REQUEST(HttpStatus.BAD_REQUEST, "STATISTICS_001", "잘못된 통계 조회 파라미터 입력입니다.");

private final HttpStatus httpStatus;
private final String customCode;
Expand Down