diff --git a/build.gradle b/build.gradle index b8b286e..4d83c89 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation "org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.5" + implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5" compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' implementation("io.jsonwebtoken:jjwt-api:0.12.6") @@ -37,6 +39,10 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta' + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' } tasks.named('test') { diff --git a/src/main/java/com/retrip/auth/application/out/repository/ReadRepository.java b/src/main/java/com/retrip/auth/application/out/repository/ReadRepository.java new file mode 100644 index 0000000..9c1da1f --- /dev/null +++ b/src/main/java/com/retrip/auth/application/out/repository/ReadRepository.java @@ -0,0 +1,70 @@ +package com.retrip.auth.application.out.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface ReadRepository extends JpaRepository { + + @Override + default S save(S entity) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default S saveAndFlush(S entity) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default List saveAllAndFlush(Iterable entities) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAllInBatch(Iterable entities) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAllByIdInBatch(Iterable ids) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAllInBatch() { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default List saveAll(Iterable entities) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteById(ID id) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void delete(T entity) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAllById(Iterable ids) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAll(Iterable entities) { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } + + @Override + default void deleteAll() { + throw new UnsupportedOperationException("해당 Repository는 Read만 가능합니다."); + } +} diff --git a/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ApiResponse.java b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ApiResponse.java new file mode 100644 index 0000000..098cb94 --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ApiResponse.java @@ -0,0 +1,41 @@ +package com.retrip.auth.infra.adapter.in.rest.common; + +import static org.springframework.http.HttpStatus.*; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiResponse { + private boolean success; + private int status; + private String message; + private T data; + + public static ApiResponse created(T data) { + return success(data, CREATED); + } + + public static ApiResponse ok(T data) { + return success(data, OK); + } + + public static ApiResponse noContent() { + return success(null, NO_CONTENT); + } + + public static ApiResponse of(T data, HttpStatus status) { + return success(data, status); + } + + private static ApiResponse success(T data, HttpStatus status) { + return new ApiResponse<>(true, status.value(), status.getReasonPhrase(), data); + } + + public static ApiResponse of(ErrorResponse errorResponse) { + return new ApiResponse<>(false, errorResponse.getStatus(), valueOf(errorResponse.getStatus()).getReasonPhrase(), errorResponse); + } +} diff --git a/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ErrorResponse.java b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ErrorResponse.java new file mode 100644 index 0000000..7f0662d --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ErrorResponse.java @@ -0,0 +1,39 @@ +package com.retrip.auth.infra.adapter.in.rest.common; + +import com.retrip.auth.domain.exception.common.ErrorCode; + +import java.util.ArrayList; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +@Getter +@AllArgsConstructor +public class ErrorResponse { + private int status; + private String code; + private String message; + private String url = "Not available"; + private String method; + private List errors = new ArrayList<>(); + + private ErrorResponse(int status, String code, String message, String url, String method) { + this.status = status; + this.code = code; + this.message = message; + this.url = url; + this.method = method; + } + + public static ErrorResponse of(ErrorCode code, String url, String method, BindingResult bindingResult) { + return new ErrorResponse(code.getStatus().value(), code.getCode(), code.getMessage(), url, method, bindingResult.getFieldErrors()); + } + + public static ErrorResponse of(ErrorCode code, String url, String method) { + return new ErrorResponse(code.getStatus().value(), code.getCode(), code.getMessage(), url, method); + } +} diff --git a/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/GlobalExceptionHandler.java b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..282c3cf --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/GlobalExceptionHandler.java @@ -0,0 +1,53 @@ +package com.retrip.auth.infra.adapter.in.rest.common; + +import com.retrip.auth.domain.exception.common.BusinessException; +import com.retrip.auth.domain.exception.common.ErrorCode; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.nio.file.AccessDeniedException; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(AccessDeniedException.class) + public ApiResponse handleAccessDeniedException(HttpServletRequest request, AccessDeniedException e) { + log.error("handleAccessDeniedException: ", e); + return handle(ErrorCode.HANDLE_ACCESS_DENIED, request); + } + + @ExceptionHandler(BindException.class) + public ApiResponse handleBindException(HttpServletRequest request, BindException e) { + log.error("handleBindException: ", e); + return ApiResponse.of( + ErrorResponse.of( + ErrorCode.INVALID_INPUT_VALUE, + request.getRequestURL().toString(), + request.getMethod(), + e.getBindingResult() + )); + } + + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(HttpServletRequest request, BusinessException e) { + log.error("handleBusinessException: ", e); + return handle(e.getErrorCode(), request); + } + + + @ExceptionHandler(Exception.class) + public ApiResponse handleException(HttpServletRequest request, Exception e) { + log.error("handleException: ", e); + return handle(ErrorCode.SERVER_ERROR, request); + } + + private static ApiResponse handle(ErrorCode errorCode, HttpServletRequest request) { + return ApiResponse.of( + ErrorResponse.of( + errorCode, request.getRequestURL().toString(), request.getMethod() + )); + } +} diff --git a/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ScrollPageResponse.java b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ScrollPageResponse.java new file mode 100644 index 0000000..5c3d685 --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/adapter/in/rest/common/ScrollPageResponse.java @@ -0,0 +1,19 @@ +package com.retrip.auth.infra.adapter.in.rest.common; + +import java.util.List; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ScrollPageResponse { + private Long totalCount; + private boolean hasNext; + private List list; + + public static ScrollPageResponse of(Long totalCount, boolean hasNext, List list) { + return new ScrollPageResponse<>(totalCount, hasNext, list); + } +} diff --git a/src/main/java/com/retrip/auth/infra/config/QuerydslConfig.java b/src/main/java/com/retrip/auth/infra/config/QuerydslConfig.java new file mode 100644 index 0000000..ed5fec9 --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/config/QuerydslConfig.java @@ -0,0 +1,20 @@ +package com.retrip.auth.infra.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing +@RequiredArgsConstructor +@Configuration +public class QuerydslConfig { + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/retrip/auth/infra/config/SwaggerConfig.java b/src/main/java/com/retrip/auth/infra/config/SwaggerConfig.java new file mode 100644 index 0000000..b35fa66 --- /dev/null +++ b/src/main/java/com/retrip/auth/infra/config/SwaggerConfig.java @@ -0,0 +1,33 @@ +package com.retrip.auth.infra.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.servers.Server; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI springShopOpenAPI() { + return new OpenAPI() + .addServersItem(new Server().url("/")) + .components( + new Components() + ).info( + new Info() + .title("Auth Application") + .version("v0.0.1") + ); + } + @Bean + public GroupedOpenApi authApi(){ + return GroupedOpenApi.builder() + .group("auth") + .pathsToMatch("/auth/**") + .build(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 95952ce..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=auth diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..f3741b1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,31 @@ +spring: + application: + name: auth + #dataSource + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL + username: sa + #JPA + jpa: + properties: + hibernate: + show_sql: true + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: create-drop + open-in-view: false + + #logging +logging: + level: + org: + hibernate: + type: + descriptor: + sql: trace + springframework: + web: + client: + RestTemplate: DEBUG