diff --git a/common/build.gradle b/common/build.gradle index 4eac71ee..014f0ecc 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,12 +1,12 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.5.7' + id 'java-library' + id 'maven-publish' id 'io.spring.dependency-management' version '1.1.7' } group = 'com.hubEleven' -version = '0.0.1-SNAPSHOT' -description = 'Demo project for Spring Boot' +version = '1.0.0' +description = 'Common utility module' java { toolchain { @@ -14,35 +14,38 @@ java { } } -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - repositories { mavenCentral() } -ext { - set('springCloudVersion', "2025.0.0") -} +publishing { + publications { + register(MavenPublication) { + from components.java + groupId = 'com.github.ElevenHub' + artifactId = 'common' + version = '1.0.0' -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + pom { + name.set('HubEleven Common') + description.set('Common module for shared utilities') + } + } + } } -dependencyManagement { - imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" - } +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web:3.5.7' + implementation 'org.springframework.boot:spring-boot-starter:3.5.7' + implementation 'org.springframework.data:spring-data-commons:3.5.5' + implementation 'org.springframework.data:spring-data-jpa:3.5.5' + implementation 'org.hibernate.orm:hibernate-core:6.6.33.Final' + implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0' + testImplementation 'org.springframework.boot:spring-boot-starter-test:3.5.7' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' + + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' } tasks.named('test') { diff --git a/common/src/main/java/com/hubEleven/common/annotation/SoftDeletable.java b/common/src/main/java/com/hubEleven/common/annotation/SoftDeletable.java new file mode 100644 index 00000000..a0c36b18 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/annotation/SoftDeletable.java @@ -0,0 +1,14 @@ +package com.hubEleven.common.annotation; + +import org.hibernate.annotations.SQLRestriction; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@SQLRestriction("deleted_at IS NULL") +public @interface SoftDeletable { +} diff --git a/common/src/main/java/com/hubEleven/common/code/ErrorCode.java b/common/src/main/java/com/hubEleven/common/code/ErrorCode.java new file mode 100644 index 00000000..f92689ad --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/code/ErrorCode.java @@ -0,0 +1,21 @@ +package com.hubEleven.common.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@RequiredArgsConstructor +@Getter +public enum ErrorCode implements StatusCode { + SERVER_ERROR(INTERNAL_SERVER_ERROR, "서버 에러가 발생하였습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public String getName() { + return this.name(); + } +} diff --git a/common/src/main/java/com/hubEleven/common/code/StatusCode.java b/common/src/main/java/com/hubEleven/common/code/StatusCode.java new file mode 100644 index 00000000..36c805df --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/code/StatusCode.java @@ -0,0 +1,9 @@ +package com.hubEleven.common.code; + +import org.springframework.http.HttpStatus; + +public interface StatusCode { + HttpStatus getHttpStatus(); + String getMessage(); + String getName(); +} diff --git a/common/src/main/java/com/hubEleven/common/code/SuccessCode.java b/common/src/main/java/com/hubEleven/common/code/SuccessCode.java new file mode 100644 index 00000000..ac492f02 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/code/SuccessCode.java @@ -0,0 +1,17 @@ +package com.hubEleven.common.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.*; + +@Getter +@RequiredArgsConstructor +public enum SuccessCode { + + SUCCESS(OK, "성공했습니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/common/src/main/java/com/hubEleven/common/exception/GlobalException.java b/common/src/main/java/com/hubEleven/common/exception/GlobalException.java new file mode 100644 index 00000000..7bae01e2 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/exception/GlobalException.java @@ -0,0 +1,11 @@ +package com.hubEleven.common.exception; + +import com.hubEleven.common.code.StatusCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class GlobalException extends RuntimeException { + private final StatusCode errorCode; +} diff --git a/common/src/main/java/com/hubEleven/common/exception/GlobalExceptionHandler.java b/common/src/main/java/com/hubEleven/common/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..ca41c1c3 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package com.hubEleven.common.exception; + +import com.hubEleven.common.response.ApiResponse; +import com.hubEleven.common.response.ApiResponseEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import static com.hubEleven.common.code.ErrorCode.*; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(GlobalException.class) + ResponseEntity globalExceptionHandler(GlobalException e) { + return ApiResponseEntity.onFailure(e.getErrorCode()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(final Exception e) { + return ApiResponseEntity.onFailure(SERVER_ERROR); + } +} diff --git a/common/src/main/java/com/hubEleven/common/model/BaseEntity.java b/common/src/main/java/com/hubEleven/common/model/BaseEntity.java new file mode 100644 index 00000000..f031b36e --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/model/BaseEntity.java @@ -0,0 +1,46 @@ +package com.hubEleven.common.model; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + @CreatedDate + @Column(updatable = false, nullable = false) + private LocalDateTime createdAt; + + @CreatedBy + @Column(updatable = false) + private Long createdBy; + + @LastModifiedDate + private LocalDateTime updatedAt; + + @LastModifiedBy + private Long updatedBy; + + private LocalDateTime deletedAt; + + private Long deletedBy; + + public void delete(Long deletedBy) { + this.deletedAt = LocalDateTime.now(); + this.deletedBy = deletedBy; + } + + public boolean isDeleted() { + return this.deletedAt != null; + } +} diff --git a/common/src/main/java/com/hubEleven/common/request/CommonPageRequest.java b/common/src/main/java/com/hubEleven/common/request/CommonPageRequest.java new file mode 100644 index 00000000..c1279cbb --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/request/CommonPageRequest.java @@ -0,0 +1,50 @@ +package com.hubEleven.common.request; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public record CommonPageRequest( + int page, + int size, + SortType sortType, + Sort.Direction direction, + String keyword +) { + public CommonPageRequest { + if (page < 0) { + page = 0; + } + + if (size != 10 && size != 30 && size != 50) { + size = 10; + } + + if (sortType == null) { + sortType = SortType.CREATED_AT; + } + + if (direction == null) { + direction = Sort.Direction.DESC; + } + } + + public Pageable toPageable() { + return PageRequest.of(page, size, Sort.by(direction, sortType.getFieldName())); + } + + public enum SortType { + CREATED_AT("createdAt"), + UPDATED_AT("updatedAt"); + + private final String fieldName; + + SortType(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldName() { + return fieldName; + } + } +} diff --git a/common/src/main/java/com/hubEleven/common/response/ApiResponse.java b/common/src/main/java/com/hubEleven/common/response/ApiResponse.java new file mode 100644 index 00000000..8190e69c --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/response/ApiResponse.java @@ -0,0 +1,12 @@ +package com.hubEleven.common.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"code", "message", "result"}) +public record ApiResponse ( + String code, + String message, + @JsonInclude(JsonInclude.Include.NON_NULL) T result +) { +} diff --git a/common/src/main/java/com/hubEleven/common/response/ApiResponseEntity.java b/common/src/main/java/com/hubEleven/common/response/ApiResponseEntity.java new file mode 100644 index 00000000..8af7d9ae --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/response/ApiResponseEntity.java @@ -0,0 +1,39 @@ +package com.hubEleven.common.response; + +import com.hubEleven.common.code.StatusCode; +import com.hubEleven.common.code.SuccessCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import java.net.URI; + +public class ApiResponseEntity { + + public static ResponseEntity> success(T result) { + return ResponseEntity.ok().body(new ApiResponse<>(SuccessCode.SUCCESS.name(), SuccessCode.SUCCESS.getMessage(), result)); + } + + public static ResponseEntity> from(StatusCode code, T result) { + return ResponseEntity.status(code.getHttpStatus()) + .body(new ApiResponse<>(code.getName(), code.getMessage(), result)); + } + + public static ResponseEntity> create(StatusCode code, String url, T result) { + return ResponseEntity.created(URI.create(url)) + .body(new ApiResponse<>(code.getName(), code.getMessage(), result)); + } + + public static ResponseEntity> onFailure(StatusCode code) { + return ResponseEntity.status(code.getHttpStatus()) + .body(new ApiResponse<>(code.getName(), code.getMessage(), null)); + } + + public static ResponseEntity> badRequest(String message) { + return ResponseEntity.badRequest() + .body(new ApiResponse<>(HttpStatus.BAD_REQUEST.name(), message, null)); + } + + public static ResponseEntity> ok(String message) { + return ResponseEntity.ok().body(new ApiResponse<>(HttpStatus.OK.name(), message, null)); + } +} diff --git a/common/src/main/java/com/hubEleven/common/response/CommonPageResponse.java b/common/src/main/java/com/hubEleven/common/response/CommonPageResponse.java new file mode 100644 index 00000000..c6db7d36 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/response/CommonPageResponse.java @@ -0,0 +1,27 @@ +package com.hubEleven.common.response; + +import org.springframework.data.domain.Page; + +import java.util.List; + +public record CommonPageResponse ( + List content, + int page, + int size, + long totalElements, + int totalPages, + boolean first, + boolean last +) { + public static CommonPageResponse of(Page page) { + return new CommonPageResponse<>( + page.getContent(), + page.getNumber(), + page.getSize(), + page.getTotalElements(), + page.getTotalPages(), + page.isFirst(), + page.isLast() + ); + } +} diff --git a/common/src/main/java/com/hubEleven/common/utils/PagingUtils.java b/common/src/main/java/com/hubEleven/common/utils/PagingUtils.java new file mode 100644 index 00000000..4540d5c8 --- /dev/null +++ b/common/src/main/java/com/hubEleven/common/utils/PagingUtils.java @@ -0,0 +1,13 @@ +package com.hubEleven.common.utils; + +import com.hubEleven.common.response.CommonPageResponse; +import org.springframework.data.domain.Page; + +import java.util.function.Function; + +public class PagingUtils { + + public static CommonPageResponse convert(Page page, Function mapper) { + return CommonPageResponse.of(page.map(mapper)); + } +} diff --git a/common/src/main/resources/application.properties b/common/src/main/resources/application.properties deleted file mode 100644 index d762b0f8..00000000 --- a/common/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=common diff --git a/common/src/main/resources/application.yml b/common/src/main/resources/application.yml new file mode 100644 index 00000000..ff103cc1 --- /dev/null +++ b/common/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + application: + name: common diff --git a/common/src/test/java/com/hubEleven/common/CommonApplicationTests.java b/common/src/test/java/com/hubEleven/common/CommonApplicationTests.java index 6ff074c7..5b206589 100644 --- a/common/src/test/java/com/hubEleven/common/CommonApplicationTests.java +++ b/common/src/test/java/com/hubEleven/common/CommonApplicationTests.java @@ -6,6 +6,4 @@ @SpringBootTest class CommonApplicationTests { - @Test - void contextLoads() {} } diff --git a/config/src/main/resources/config-repo/user-service.yml b/config/src/main/resources/config-repo/user-service.yml index 480efe09..420d4245 100644 --- a/config/src/main/resources/config-repo/user-service.yml +++ b/config/src/main/resources/config-repo/user-service.yml @@ -1,8 +1,8 @@ spring: datasource: url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=Asia/Seoul - username: root - password: "1234" + username: ${DB_USERNAME} + password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver jpa: diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..4729e0c7 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +jdk: + - openjdk17 + +before_install: + - cd common \ No newline at end of file