Skip to content

Commit

Permalink
Merge pull request #67 from makevook/issue/62,64,65,66
Browse files Browse the repository at this point in the history
feat, docs: bean validator 추가, API 공통 반환 형식 변경, OpenAPI 리팩토링
  • Loading branch information
seungyeop-lee authored Jun 7, 2024
2 parents 016acd1 + da0ccf3 commit 2a505fa
Show file tree
Hide file tree
Showing 26 changed files with 286 additions and 117 deletions.
5 changes: 4 additions & 1 deletion api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-validation'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'com.vaadin.external.google', module: 'android-json'
}

// lombok
compileOnly 'org.projectlombok:lombok'
Expand Down
36 changes: 36 additions & 0 deletions api/src/main/java/vook/server/api/web/common/ApiResponseCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package vook.server.api.web.common;

public interface ApiResponseCode {

String code();

enum Ok implements ApiResponseCode {

SUCCESS;

@Override
public String code() {
return this.name();
}
}

enum BadRequest implements ApiResponseCode {

INVALID_PARAMETER;

@Override
public String code() {
return this.name();
}
}

enum ServerError implements ApiResponseCode {

UNHANDLED_ERROR;

@Override
public String code() {
return this.name();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,50 @@

public class CommonApiException {
public static abstract class Exception extends RuntimeException {
protected String message;

public Exception(Throwable cause) {
super(cause);
this.message = cause.getMessage();
}

public Exception(String message) {
super(message);
this.message = message;
}
protected ApiResponseCode code;

public Exception(String message, Throwable cause) {
super(message, cause);
this.message = message;
public Exception(ApiResponseCode code, Throwable cause) {
super(code.code(), cause);
this.code = code;
}

abstract CommonApiResponse<?> response();

abstract int statusCode();
}

public static class BadRequest extends Exception {
public BadRequest(String message, Throwable cause) {
super(message, cause);
}

public BadRequest(String message) {
super(message);
public BadRequest(ApiResponseCode code, Throwable cause) {
super(code, cause);
}

@Override
public CommonApiResponse<?> response() {
return CommonApiResponse.noResult(400, message);
return CommonApiResponse.noResult(code);
}

@Override
int statusCode() {
return 400;
}
}

public static class ServerError extends Exception {
public ServerError(Throwable cause) {
super(cause);

public ServerError(ApiResponseCode code, Throwable cause) {
super(code, cause);
}

@Override
public CommonApiResponse<?> response() {
return CommonApiResponse.noResult(500, "처리되지 않은 서버 에러가 발생하였습니다.");
return CommonApiResponse.noResult(code);
}

@Override
int statusCode() {
return 500;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommonApiResponse<T> {

@Schema(description = "응답 코드")
private Integer code;
@Schema(description = "응답 메시지")
private String message;
@Schema(description = "결과 코드", requiredMode = Schema.RequiredMode.REQUIRED, example = "SUCCESS")
private String code;
private T result;

public static <T> CommonApiResponse<T> ok() {
return noResult(200, "API 요청이 성공했습니다.");
return noResult(ApiResponseCode.Ok.SUCCESS);
}

public static <T> CommonApiResponse<T> okWithResult(T result) {
Expand All @@ -24,10 +22,16 @@ public static <T> CommonApiResponse<T> okWithResult(T result) {
return response;
}

public static <T> CommonApiResponse<T> noResult(Integer code, String message) {
public static <T> CommonApiResponse<T> noResult(ApiResponseCode code) {
CommonApiResponse<T> response = new CommonApiResponse<>();
response.code = code;
response.message = message;
response.code = code.code();
return response;
}

public static <T> CommonApiResponse<T> withResult(ApiResponseCode code, T result) {
CommonApiResponse<T> response = new CommonApiResponse<>();
response.code = code.code();
response.result = result;
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;

Expand All @@ -12,18 +13,19 @@ public class GlobalRestControllerAdvice {
@ExceptionHandler(CommonApiException.Exception.class)
public ResponseEntity<?> handleCommonApiException(CommonApiException.Exception e) {
log.error(e.getMessage(), e);
return ResponseEntity.status(e.statusCode()).body(e.response());
}

CommonApiResponse<?> response = e.response();

return ResponseEntity.status(response.getCode()).body(response);
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
CommonApiException.BadRequest badRequest = new CommonApiException.BadRequest(ApiResponseCode.BadRequest.INVALID_PARAMETER, e);
return ResponseEntity.status(badRequest.statusCode()).body(badRequest.response());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception e) {
log.error(e.getMessage(), e);

CommonApiResponse<?> response = new CommonApiException.ServerError(e).response();

return ResponseEntity.status(response.getCode()).body(response);
CommonApiException.ServerError serverError = new CommonApiException.ServerError(ApiResponseCode.ServerError.UNHANDLED_ERROR, e);
return ResponseEntity.status(serverError.statusCode()).body(serverError.response());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface AuthApi {
summary = "토큰 갱신",
description = """
리프레시 토큰을 이용하여 엑세스 토큰과 리프레시 토큰을 갱신합니다.
리프레시 토큰은 최상위 Description에 Authorzation 항목을 참고하세요."""
리프레시 토큰은 최상위 Description의 Authorzation 항목을 참고하세요."""
)
@ApiResponses(value = {
@ApiResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public interface DemoApi {
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = SearchApiTermResponse.class)
)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;

Expand All @@ -15,7 +16,8 @@ public interface HealthApi {
@ApiResponse(
responseCode = "200",
content = @Content(
mediaType = "text/plain"
mediaType = "text/plain",
examples = @ExampleObject(name = "성공", value = "OK")
)
)
String health();
Expand Down
11 changes: 2 additions & 9 deletions api/src/main/java/vook/server/api/web/routes/init/InitApi.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package vook.server.api.web.routes.init;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import vook.server.api.web.common.CommonApiResponse;

@Tag(name = "init", description = "초기화 API")
public interface InitApi {
Expand All @@ -12,11 +11,5 @@ public interface InitApi {
summary = "데이터 초기화",
description = "모든 데이터를 삭제하고, 데모용 데이터를 생성시킨 상태로 초기화 시킵니다."
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
})
void init();
CommonApiResponse<Void> init();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import vook.server.api.devhelper.InitService;
import vook.server.api.web.common.CommonApiResponse;

@Profile({"local", "dev", "stag"})
@RestController
Expand All @@ -16,7 +17,8 @@ public class InitController implements InitApi {
private final InitService initService;

@PostMapping
public void init() {
public CommonApiResponse<Void> init() {
initService.init();
return CommonApiResponse.ok();
}
}
18 changes: 9 additions & 9 deletions api/src/main/java/vook/server/api/web/routes/user/UserApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
Expand All @@ -12,6 +13,7 @@
import vook.server.api.web.routes.user.reqres.UserInfoResponse;
import vook.server.api.web.routes.user.reqres.UserOnboardingCompleteRequest;
import vook.server.api.web.routes.user.reqres.UserRegisterRequest;
import vook.server.api.web.swagger.ComponentRefConsts;

@Tag(name = "user", description = "사용자 관련 API")
public interface UserApi {
Expand All @@ -25,8 +27,8 @@ public interface UserApi {
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = UserApiUerInfoResponse.class)
)
),
Expand All @@ -44,8 +46,12 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공"
responseCode = "400",
content = @Content(
mediaType = "application/json",
schema = @Schema(ref = ComponentRefConsts.Schema.COMMON_API_RESPONSE),
examples = @ExampleObject(name = "유효하지 않은 파라미터", ref = ComponentRefConsts.Example.INVALID_PARAMETER)
)
),
})
CommonApiResponse<Void> register(VookLoginUser user, UserRegisterRequest request);
Expand All @@ -56,11 +62,5 @@ class UserApiUerInfoResponse extends CommonApiResponse<UserInfoResponse> {
@SecurityRequirement(name = "AccessToken")
}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
})
CommonApiResponse<Void> onboardingComplete(VookLoginUser user, UserOnboardingCompleteRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import vook.server.api.config.auth.common.VookLoginUser;
import vook.server.api.web.common.CommonApiResponse;
Expand Down Expand Up @@ -31,7 +32,7 @@ public CommonApiResponse<UserInfoResponse> userInfo(
@PostMapping("/register")
public CommonApiResponse<Void> register(
@AuthenticationPrincipal VookLoginUser user,
@RequestBody UserRegisterRequest request
@Validated @RequestBody UserRegisterRequest request
) {
service.register(user, request);
return CommonApiResponse.ok();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package vook.server.api.web.routes.user.reqres;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import vook.server.api.app.user.data.RegisterCommand;

@Data
public class UserRegisterRequest {

@NotBlank
private String nickname;

private boolean requiredTermsAgree;

private boolean marketingEmailOptIn;

public RegisterCommand toCommand(String userUid) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package vook.server.api.web.swagger;

/**
* OpenAPI ComponentRef 상수
*/
public class ComponentRefConsts {

public static class Schema {
public static final String COMMON_API_RESPONSE = "#/components/schemas/CommonApiResponse";
}

public static class Example {
public static final String SUCCESS = "#/components/examples/Success";
public static final String INVALID_PARAMETER = "#/components/examples/InvalidParameter";
public static final String UNHANDLED_ERROR = "#/components/examples/UnhandledError";
}
}
Loading

0 comments on commit 2a505fa

Please sign in to comment.