From 5eb3112dcca8211d9cba8102fcf3c07f05d6cae4 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 16:42:38 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20application.yml=EC=97=90=20rds=20ss?= =?UTF-8?q?h=20=ED=84=B0=EB=84=90=EB=A7=81=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b8d6bb9..141e814 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,6 +7,11 @@ spring: activate: on-profile: local --- +spring: + config: + activate: + on-profile: local-tunnel +--- spring: config: activate: From fda0b7a6bbede4774178d68677a6f544a972a39a Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 17:20:41 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EC=9D=91=EB=8B=B5=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/response/BaseResponseDTO.java | 43 +++++++++++++++++++ .../global/response/code/BaseCode.java | 7 +++ .../response/code/CommonSuccessCode.java | 26 +++++++++++ .../global/response/dto/ReasonDTO.java | 12 ++++++ 4 files changed, 88 insertions(+) create mode 100644 src/main/java/com/techfork/global/response/BaseResponseDTO.java create mode 100644 src/main/java/com/techfork/global/response/code/BaseCode.java create mode 100644 src/main/java/com/techfork/global/response/code/CommonSuccessCode.java create mode 100644 src/main/java/com/techfork/global/response/dto/ReasonDTO.java diff --git a/src/main/java/com/techfork/global/response/BaseResponseDTO.java b/src/main/java/com/techfork/global/response/BaseResponseDTO.java new file mode 100644 index 0000000..aa2832d --- /dev/null +++ b/src/main/java/com/techfork/global/response/BaseResponseDTO.java @@ -0,0 +1,43 @@ +package com.techfork.global.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.techfork.global.response.code.BaseCode; +import com.techfork.global.response.dto.ReasonDTO; +import lombok.Builder; +import org.springframework.http.ResponseEntity; + +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public record BaseResponseDTO( + boolean isSuccess, + String code, + String message, + T data +) { + public static ResponseEntity> success(BaseCode successCode) { + ReasonDTO reason = successCode.getReason(); + BaseResponseDTO body = BaseResponseDTO.builder() + .isSuccess(true) + .code(reason.code()) + .message(reason.message()) + .build(); + + return ResponseEntity + .status(reason.httpStatus()) + .body(body); + } + + public static ResponseEntity> success(BaseCode successCode, T data) { + ReasonDTO reason = successCode.getReason(); + BaseResponseDTO body = BaseResponseDTO.builder() + .isSuccess(true) + .code(reason.code()) + .message(reason.message()) + .data(data) + .build(); + + return ResponseEntity + .status(reason.httpStatus()) + .body(body); + } +} diff --git a/src/main/java/com/techfork/global/response/code/BaseCode.java b/src/main/java/com/techfork/global/response/code/BaseCode.java new file mode 100644 index 0000000..be1ada2 --- /dev/null +++ b/src/main/java/com/techfork/global/response/code/BaseCode.java @@ -0,0 +1,7 @@ +package com.techfork.global.response.code; + +import com.techfork.global.response.dto.ReasonDTO; + +public interface BaseCode { + ReasonDTO getReason(); +} diff --git a/src/main/java/com/techfork/global/response/code/CommonSuccessCode.java b/src/main/java/com/techfork/global/response/code/CommonSuccessCode.java new file mode 100644 index 0000000..d71671c --- /dev/null +++ b/src/main/java/com/techfork/global/response/code/CommonSuccessCode.java @@ -0,0 +1,26 @@ +package com.techfork.global.response.code; + +import com.techfork.global.response.dto.ReasonDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum CommonSuccessCode implements BaseCode{ + OK(HttpStatus.OK, "COMMON200", "요청에 성공했습니다."), + CREATED(HttpStatus.CREATED, "COMMON201", "요청이 성공적으로 처리되어 리소스가 생성되었습니다."), + ACCEPTED(HttpStatus.ACCEPTED, "COMMON202", "요청이 접수되었습니다."), + NO_CONTENT(HttpStatus.NO_CONTENT, "COMMON204", "요청이 성공적으로 처리되었으나 반환할 데이터가 없습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason(){ + return ReasonDTO.builder() + .code(code) + .message(message) + .httpStatus(httpStatus) + .build(); + } +} diff --git a/src/main/java/com/techfork/global/response/dto/ReasonDTO.java b/src/main/java/com/techfork/global/response/dto/ReasonDTO.java new file mode 100644 index 0000000..28ea36c --- /dev/null +++ b/src/main/java/com/techfork/global/response/dto/ReasonDTO.java @@ -0,0 +1,12 @@ +package com.techfork.global.response.dto; + +import lombok.Builder; +import org.springframework.http.HttpStatus; + +@Builder +public record ReasonDTO( + String code, + String message, + HttpStatus httpStatus +) { +} From fb8fec7bb30933e10ec68cbaee7e47a7293f417c Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 17:29:20 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/response/BaseResponseDTO.java | 13 +++++++ .../global/response/code/CommonErrorCode.java | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/java/com/techfork/global/response/code/CommonErrorCode.java diff --git a/src/main/java/com/techfork/global/response/BaseResponseDTO.java b/src/main/java/com/techfork/global/response/BaseResponseDTO.java index aa2832d..70bcf50 100644 --- a/src/main/java/com/techfork/global/response/BaseResponseDTO.java +++ b/src/main/java/com/techfork/global/response/BaseResponseDTO.java @@ -40,4 +40,17 @@ public static ResponseEntity> success(BaseCode successCod .status(reason.httpStatus()) .body(body); } + + public static ResponseEntity> error(BaseCode errorCode) { + ReasonDTO errorReason = errorCode.getReason(); + BaseResponseDTO body = BaseResponseDTO.builder() + .isSuccess(false) + .code(errorReason.code()) + .message(errorReason.message()) + .build(); + + return ResponseEntity + .status(errorReason.httpStatus()) + .body(body); + } } diff --git a/src/main/java/com/techfork/global/response/code/CommonErrorCode.java b/src/main/java/com/techfork/global/response/code/CommonErrorCode.java new file mode 100644 index 0000000..d76e203 --- /dev/null +++ b/src/main/java/com/techfork/global/response/code/CommonErrorCode.java @@ -0,0 +1,34 @@ +package com.techfork.global.response.code; + +import com.techfork.global.response.dto.ReasonDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum CommonErrorCode implements BaseCode{ + BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "접근 권한이 없는 요청입니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON404", "요청 리소스를 찾을 수 없습니다"), + + VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "COMMON400_VALIDATION", "입력값 검증에 실패했습니다"), + INVALID_PARAMETER_TYPE(HttpStatus.BAD_REQUEST, "COMMON400_TYPE", "파라미터 타입이 올바르지 않습니다"), + INVALID_REQUEST_FORMAT(HttpStatus.BAD_REQUEST, "COMMON400_FORMAT", "요청 형식이 올바르지 않습니다"), + MISSING_REQUIRED_PARAMETER(HttpStatus.BAD_REQUEST, "COMMON400_PARAM", "필수 파라미터가 누락되었습니다"), + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "COMMON503", "서버가 일시적으로 사용중지 되었습니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .httpStatus(httpStatus) + .code(code) + .message(message) + .build(); + } +} From fdeb9836f99f34d2bd4203bbb969c36ac443555a Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 17:34:37 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/response/BaseResponseDTO.java | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/techfork/global/response/BaseResponseDTO.java b/src/main/java/com/techfork/global/response/BaseResponseDTO.java index 70bcf50..a429f8c 100644 --- a/src/main/java/com/techfork/global/response/BaseResponseDTO.java +++ b/src/main/java/com/techfork/global/response/BaseResponseDTO.java @@ -14,23 +14,16 @@ public record BaseResponseDTO( String message, T data ) { - public static ResponseEntity> success(BaseCode successCode) { - ReasonDTO reason = successCode.getReason(); - BaseResponseDTO body = BaseResponseDTO.builder() - .isSuccess(true) - .code(reason.code()) - .message(reason.message()) - .build(); - - return ResponseEntity - .status(reason.httpStatus()) - .body(body); + public static ResponseEntity> of(BaseCode code) { + return of(code, null); } - public static ResponseEntity> success(BaseCode successCode, T data) { - ReasonDTO reason = successCode.getReason(); + public static ResponseEntity> of(BaseCode code, T data) { + ReasonDTO reason = code.getReason(); + boolean isSuccess = reason.httpStatus().is2xxSuccessful(); + BaseResponseDTO body = BaseResponseDTO.builder() - .isSuccess(true) + .isSuccess(isSuccess) .code(reason.code()) .message(reason.message()) .data(data) @@ -40,17 +33,4 @@ public static ResponseEntity> success(BaseCode successCod .status(reason.httpStatus()) .body(body); } - - public static ResponseEntity> error(BaseCode errorCode) { - ReasonDTO errorReason = errorCode.getReason(); - BaseResponseDTO body = BaseResponseDTO.builder() - .isSuccess(false) - .code(errorReason.code()) - .message(errorReason.message()) - .build(); - - return ResponseEntity - .status(errorReason.httpStatus()) - .body(body); - } } From 8be49627dd4695b24e5320fa4735127a4bb4c94b Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 20:10:40 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EC=A0=84=EC=97=AD=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/GeneralException.java | 17 ++ .../exception/GlobalExceptionHandler.java | 198 ++++++++++++++++++ .../global/response/BaseResponseDTO.java | 16 ++ .../global/response/dto/ErrorDetailDTO.java | 40 ++++ 4 files changed, 271 insertions(+) create mode 100644 src/main/java/com/techfork/global/exception/GeneralException.java create mode 100644 src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java diff --git a/src/main/java/com/techfork/global/exception/GeneralException.java b/src/main/java/com/techfork/global/exception/GeneralException.java new file mode 100644 index 0000000..ad72f0e --- /dev/null +++ b/src/main/java/com/techfork/global/exception/GeneralException.java @@ -0,0 +1,17 @@ +package com.techfork.global.exception; + +import com.techfork.global.response.code.BaseCode; +import com.techfork.global.response.dto.ReasonDTO; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class GeneralException extends RuntimeException { + + private final BaseCode code; + + public ReasonDTO getErrorReason() { + return this.code.getReason(); + } +} diff --git a/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java b/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..6fa0bb0 --- /dev/null +++ b/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,198 @@ +package com.techfork.global.exception; + +import com.techfork.global.response.BaseResponseDTO; +import com.techfork.global.response.code.CommonErrorCode; +import com.techfork.global.response.dto.ErrorDetailDTO; +import com.techfork.global.response.dto.ReasonDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + /** + * 비즈니스 로직 예외 + */ + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleGeneralException( + GeneralException ex, WebRequest request) { + + ReasonDTO errorReason = ex.getErrorReason(); + + if (errorReason.httpStatus().is4xxClientError()) { + log.info("Business Exception (4xx): uri={}, code={}, message={}", + getRequestURI(request), errorReason.code(), errorReason.message()); + } else { + log.warn("Business Exception (5xx): uri={}, code={}, message={}", + getRequestURI(request), errorReason.code(), errorReason.message()); + } + + return BaseResponseDTO.of(ex.getCode()); + } + + /** + * @Valid 어노테이션으로 binding error 발생 시 (@RequestBody) + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpHeaders headers, + HttpStatusCode status, WebRequest request) { + + List fieldErrors = extractFieldErrorDetails(ex); + + log.info("Validation failed: uri={}, errors={}", getRequestURI(request), fieldErrors); + + ErrorDetailDTO errorDetail = ErrorDetailDTO.of(fieldErrors); + + return BaseResponseDTO.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); + } + + /** + * @Validated 어노테이션으로 binding error 발생 시 (@PathVariable, @RequestParam) + */ + @ExceptionHandler(jakarta.validation.ConstraintViolationException.class) + public ResponseEntity handleConstraintViolation( + jakarta.validation.ConstraintViolationException ex, WebRequest request) { + + List violations = extractConstraintViolationDetails(ex); + + log.info("Constraint violation: uri={}, violations={}", getRequestURI(request), violations); + + ErrorDetailDTO errorDetail = ErrorDetailDTO.of(violations); + + return BaseResponseDTO.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); + } + + /** + * RequestParam 타입 변환 실패 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatch( + MethodArgumentTypeMismatchException ex, WebRequest request) { + + String expectedType = getExpectedType(ex); + + log.info("Type mismatch: uri={}, param={}, value={}, expectedType={}", + getRequestURI(request), ex.getName(), ex.getValue(), expectedType); + + String detail = String.format("파라미터 '%s'의 값 '%s'을(를) %s 타입으로 변환할 수 없습니다.", + ex.getName(), ex.getValue(), expectedType); + + ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); + + return BaseResponseDTO.ofObject(CommonErrorCode.INVALID_PARAMETER_TYPE, errorDetail); + } + + /** + * 필수 RequestParam 누락 + */ + @Override + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, HttpHeaders headers, + HttpStatusCode status, WebRequest request) { + + log.info("Missing parameter: uri={}, param={}", getRequestURI(request), ex.getParameterName()); + + String detail = String.format("필수 파라미터 '%s' (%s 타입)가 누락되었습니다.", + ex.getParameterName(), ex.getParameterType()); + + ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); + + return BaseResponseDTO.ofObject(CommonErrorCode.MISSING_REQUIRED_PARAMETER, errorDetail); + } + + /** + * JSON 파싱 오류 (@RequestBody) + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, HttpHeaders headers, + HttpStatusCode status, WebRequest request) { + + log.warn("JSON parsing error: uri={}, message={}", getRequestURI(request), ex.getMessage()); + + String detail = "요청 본문의 JSON 형식이 올바르지 않거나 필수 필드가 누락되었습니다."; + ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); + + return BaseResponseDTO.ofObject(CommonErrorCode.INVALID_REQUEST_FORMAT, errorDetail); + } + + /** + * 최종 예외 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleGlobalException( + Exception ex, WebRequest request) { + + log.error("Unexpected exception: uri={}, type={}, message={}, stackTrace={}", + getRequestURI(request), ex.getClass().getSimpleName(), ex.getMessage(), getStackTrace(ex)); + + ErrorDetailDTO errorDetail = ErrorDetailDTO.of("예상치 못한 오류가 발생했습니다. 관리자에게 문의해주세요."); + + return BaseResponseDTO.ofObject(CommonErrorCode.INTERNAL_SERVER_ERROR, errorDetail); + } + + private String getRequestURI(WebRequest request) { + return request.getDescription(false).replace("uri=", ""); + } + + private List extractFieldErrorDetails(MethodArgumentNotValidException ex) { + return ex.getBindingResult().getFieldErrors().stream() + .map(error -> ErrorDetailDTO.FieldErrorDTO.of( + error.getField(), + error.getRejectedValue(), + error.getDefaultMessage() + )) + .toList(); + } + + private List extractConstraintViolationDetails( + jakarta.validation.ConstraintViolationException ex) { + return ex.getConstraintViolations().stream() + .map(violation -> { + String propertyPath = violation.getPropertyPath().toString(); + + // 메서드명.파라미터명 형태에서 파라미터명만 추출 + String fieldName = propertyPath.contains(".") + ? propertyPath.substring(propertyPath.lastIndexOf('.') + 1) + : propertyPath; + + return ErrorDetailDTO.FieldErrorDTO.of( + fieldName, + violation.getInvalidValue(), + violation.getMessage() + ); + }) + .toList(); + } + + private String getExpectedType(MethodArgumentTypeMismatchException ex) { + return Optional.ofNullable(ex.getRequiredType()) + .map(Class::getSimpleName) + .orElse("unknown"); + } + + private String getStackTrace(Exception ex) { + return Arrays.stream(ex.getStackTrace()) + .limit(5) + .map(StackTraceElement::toString) + .collect(Collectors.joining(" | ")); + } +} diff --git a/src/main/java/com/techfork/global/response/BaseResponseDTO.java b/src/main/java/com/techfork/global/response/BaseResponseDTO.java index a429f8c..71a4056 100644 --- a/src/main/java/com/techfork/global/response/BaseResponseDTO.java +++ b/src/main/java/com/techfork/global/response/BaseResponseDTO.java @@ -33,4 +33,20 @@ public static ResponseEntity> of(BaseCode code, T data) { .status(reason.httpStatus()) .body(body); } + + public static ResponseEntity ofObject(BaseCode code, Object data) { + ReasonDTO reason = code.getReason(); + boolean isSuccess = reason.httpStatus().is2xxSuccessful(); + + BaseResponseDTO body = BaseResponseDTO.builder() + .isSuccess(isSuccess) + .code(reason.code()) + .message(reason.message()) + .data(data) + .build(); + + return ResponseEntity + .status(reason.httpStatus()) + .body(body); + } } diff --git a/src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java b/src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java new file mode 100644 index 0000000..eca66da --- /dev/null +++ b/src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java @@ -0,0 +1,40 @@ +package com.techfork.global.response.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; + +import java.util.List; + +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ErrorDetailDTO( + List fieldErrors, + String detail +) { + public static ErrorDetailDTO of(List fieldErrors) { + return ErrorDetailDTO.builder() + .fieldErrors(fieldErrors) + .build(); + } + + public static ErrorDetailDTO of(String detail) { + return ErrorDetailDTO.builder() + .detail(detail) + .build(); + } + + @Builder + public record FieldErrorDTO( + String field, + Object rejectedValue, + String message + ){ + public static FieldErrorDTO of(String field, Object rejectedValue, String message) { + return FieldErrorDTO.builder() + .field(field) + .rejectedValue(rejectedValue) + .message(message) + .build(); + } + } +} From ccd4ec9e5eb8e93e22642e5f0836a733543a9103 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Tue, 14 Oct 2025 20:36:30 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=AA=85=ED=99=95=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techfork/global/common/code/BaseCode.java | 7 ++++++ .../code/SuccessCode.java} | 6 ++--- .../code => exception}/CommonErrorCode.java | 7 +++--- .../global/exception/GeneralException.java | 4 ++-- .../exception/GlobalExceptionHandler.java | 23 +++++++++---------- ...BaseResponseDTO.java => BaseResponse.java} | 13 +++++------ .../response/{dto => }/ErrorDetailDTO.java | 2 +- .../global/response/{dto => }/ReasonDTO.java | 2 +- .../global/response/code/BaseCode.java | 7 ------ 9 files changed, 35 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/techfork/global/common/code/BaseCode.java rename src/main/java/com/techfork/global/{response/code/CommonSuccessCode.java => common/code/SuccessCode.java} (83%) rename src/main/java/com/techfork/global/{response/code => exception}/CommonErrorCode.java (87%) rename src/main/java/com/techfork/global/response/{BaseResponseDTO.java => BaseResponse.java} (71%) rename src/main/java/com/techfork/global/response/{dto => }/ErrorDetailDTO.java (92%) rename src/main/java/com/techfork/global/response/{dto => }/ReasonDTO.java (77%) delete mode 100644 src/main/java/com/techfork/global/response/code/BaseCode.java diff --git a/src/main/java/com/techfork/global/common/code/BaseCode.java b/src/main/java/com/techfork/global/common/code/BaseCode.java new file mode 100644 index 0000000..b6079e1 --- /dev/null +++ b/src/main/java/com/techfork/global/common/code/BaseCode.java @@ -0,0 +1,7 @@ +package com.techfork.global.common.code; + +import com.techfork.global.response.ReasonDTO; + +public interface BaseCode { + ReasonDTO getReason(); +} diff --git a/src/main/java/com/techfork/global/response/code/CommonSuccessCode.java b/src/main/java/com/techfork/global/common/code/SuccessCode.java similarity index 83% rename from src/main/java/com/techfork/global/response/code/CommonSuccessCode.java rename to src/main/java/com/techfork/global/common/code/SuccessCode.java index d71671c..53d665a 100644 --- a/src/main/java/com/techfork/global/response/code/CommonSuccessCode.java +++ b/src/main/java/com/techfork/global/common/code/SuccessCode.java @@ -1,11 +1,11 @@ -package com.techfork.global.response.code; +package com.techfork.global.common.code; -import com.techfork.global.response.dto.ReasonDTO; +import com.techfork.global.response.ReasonDTO; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @RequiredArgsConstructor -public enum CommonSuccessCode implements BaseCode{ +public enum SuccessCode implements BaseCode { OK(HttpStatus.OK, "COMMON200", "요청에 성공했습니다."), CREATED(HttpStatus.CREATED, "COMMON201", "요청이 성공적으로 처리되어 리소스가 생성되었습니다."), ACCEPTED(HttpStatus.ACCEPTED, "COMMON202", "요청이 접수되었습니다."), diff --git a/src/main/java/com/techfork/global/response/code/CommonErrorCode.java b/src/main/java/com/techfork/global/exception/CommonErrorCode.java similarity index 87% rename from src/main/java/com/techfork/global/response/code/CommonErrorCode.java rename to src/main/java/com/techfork/global/exception/CommonErrorCode.java index d76e203..0f3adc3 100644 --- a/src/main/java/com/techfork/global/response/code/CommonErrorCode.java +++ b/src/main/java/com/techfork/global/exception/CommonErrorCode.java @@ -1,11 +1,12 @@ -package com.techfork.global.response.code; +package com.techfork.global.exception; -import com.techfork.global.response.dto.ReasonDTO; +import com.techfork.global.common.code.BaseCode; +import com.techfork.global.response.ReasonDTO; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @RequiredArgsConstructor -public enum CommonErrorCode implements BaseCode{ +public enum CommonErrorCode implements BaseCode { BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "접근 권한이 없는 요청입니다."), diff --git a/src/main/java/com/techfork/global/exception/GeneralException.java b/src/main/java/com/techfork/global/exception/GeneralException.java index ad72f0e..fcf4849 100644 --- a/src/main/java/com/techfork/global/exception/GeneralException.java +++ b/src/main/java/com/techfork/global/exception/GeneralException.java @@ -1,7 +1,7 @@ package com.techfork.global.exception; -import com.techfork.global.response.code.BaseCode; -import com.techfork.global.response.dto.ReasonDTO; +import com.techfork.global.common.code.BaseCode; +import com.techfork.global.response.ReasonDTO; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java b/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java index 6fa0bb0..abe99f8 100644 --- a/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/techfork/global/exception/GlobalExceptionHandler.java @@ -1,9 +1,8 @@ package com.techfork.global.exception; -import com.techfork.global.response.BaseResponseDTO; -import com.techfork.global.response.code.CommonErrorCode; -import com.techfork.global.response.dto.ErrorDetailDTO; -import com.techfork.global.response.dto.ReasonDTO; +import com.techfork.global.response.BaseResponse; +import com.techfork.global.response.ErrorDetailDTO; +import com.techfork.global.response.ReasonDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; @@ -31,7 +30,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { * 비즈니스 로직 예외 */ @ExceptionHandler(GeneralException.class) - public ResponseEntity> handleGeneralException( + public ResponseEntity> handleGeneralException( GeneralException ex, WebRequest request) { ReasonDTO errorReason = ex.getErrorReason(); @@ -44,7 +43,7 @@ public ResponseEntity> handleGeneralException( getRequestURI(request), errorReason.code(), errorReason.message()); } - return BaseResponseDTO.of(ex.getCode()); + return BaseResponse.of(ex.getCode()); } /** @@ -61,7 +60,7 @@ protected ResponseEntity handleMethodArgumentNotValid( ErrorDetailDTO errorDetail = ErrorDetailDTO.of(fieldErrors); - return BaseResponseDTO.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); } /** @@ -77,7 +76,7 @@ public ResponseEntity handleConstraintViolation( ErrorDetailDTO errorDetail = ErrorDetailDTO.of(violations); - return BaseResponseDTO.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.VALIDATION_FAILED, errorDetail); } /** @@ -97,7 +96,7 @@ public ResponseEntity handleMethodArgumentTypeMismatch( ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); - return BaseResponseDTO.ofObject(CommonErrorCode.INVALID_PARAMETER_TYPE, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.INVALID_PARAMETER_TYPE, errorDetail); } /** @@ -115,7 +114,7 @@ protected ResponseEntity handleMissingServletRequestParameter( ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); - return BaseResponseDTO.ofObject(CommonErrorCode.MISSING_REQUIRED_PARAMETER, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.MISSING_REQUIRED_PARAMETER, errorDetail); } /** @@ -131,7 +130,7 @@ protected ResponseEntity handleHttpMessageNotReadable( String detail = "요청 본문의 JSON 형식이 올바르지 않거나 필수 필드가 누락되었습니다."; ErrorDetailDTO errorDetail = ErrorDetailDTO.of(detail); - return BaseResponseDTO.ofObject(CommonErrorCode.INVALID_REQUEST_FORMAT, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.INVALID_REQUEST_FORMAT, errorDetail); } /** @@ -146,7 +145,7 @@ public ResponseEntity handleGlobalException( ErrorDetailDTO errorDetail = ErrorDetailDTO.of("예상치 못한 오류가 발생했습니다. 관리자에게 문의해주세요."); - return BaseResponseDTO.ofObject(CommonErrorCode.INTERNAL_SERVER_ERROR, errorDetail); + return BaseResponse.ofObject(CommonErrorCode.INTERNAL_SERVER_ERROR, errorDetail); } private String getRequestURI(WebRequest request) { diff --git a/src/main/java/com/techfork/global/response/BaseResponseDTO.java b/src/main/java/com/techfork/global/response/BaseResponse.java similarity index 71% rename from src/main/java/com/techfork/global/response/BaseResponseDTO.java rename to src/main/java/com/techfork/global/response/BaseResponse.java index 71a4056..75a3214 100644 --- a/src/main/java/com/techfork/global/response/BaseResponseDTO.java +++ b/src/main/java/com/techfork/global/response/BaseResponse.java @@ -1,28 +1,27 @@ package com.techfork.global.response; import com.fasterxml.jackson.annotation.JsonInclude; -import com.techfork.global.response.code.BaseCode; -import com.techfork.global.response.dto.ReasonDTO; +import com.techfork.global.common.code.BaseCode; import lombok.Builder; import org.springframework.http.ResponseEntity; @Builder @JsonInclude(JsonInclude.Include.NON_NULL) -public record BaseResponseDTO( +public record BaseResponse( boolean isSuccess, String code, String message, T data ) { - public static ResponseEntity> of(BaseCode code) { + public static ResponseEntity> of(BaseCode code) { return of(code, null); } - public static ResponseEntity> of(BaseCode code, T data) { + public static ResponseEntity> of(BaseCode code, T data) { ReasonDTO reason = code.getReason(); boolean isSuccess = reason.httpStatus().is2xxSuccessful(); - BaseResponseDTO body = BaseResponseDTO.builder() + BaseResponse body = BaseResponse.builder() .isSuccess(isSuccess) .code(reason.code()) .message(reason.message()) @@ -38,7 +37,7 @@ public static ResponseEntity ofObject(BaseCode code, Object data) { ReasonDTO reason = code.getReason(); boolean isSuccess = reason.httpStatus().is2xxSuccessful(); - BaseResponseDTO body = BaseResponseDTO.builder() + BaseResponse body = BaseResponse.builder() .isSuccess(isSuccess) .code(reason.code()) .message(reason.message()) diff --git a/src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java b/src/main/java/com/techfork/global/response/ErrorDetailDTO.java similarity index 92% rename from src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java rename to src/main/java/com/techfork/global/response/ErrorDetailDTO.java index eca66da..82510c6 100644 --- a/src/main/java/com/techfork/global/response/dto/ErrorDetailDTO.java +++ b/src/main/java/com/techfork/global/response/ErrorDetailDTO.java @@ -1,4 +1,4 @@ -package com.techfork.global.response.dto; +package com.techfork.global.response; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; diff --git a/src/main/java/com/techfork/global/response/dto/ReasonDTO.java b/src/main/java/com/techfork/global/response/ReasonDTO.java similarity index 77% rename from src/main/java/com/techfork/global/response/dto/ReasonDTO.java rename to src/main/java/com/techfork/global/response/ReasonDTO.java index 28ea36c..41c9f6a 100644 --- a/src/main/java/com/techfork/global/response/dto/ReasonDTO.java +++ b/src/main/java/com/techfork/global/response/ReasonDTO.java @@ -1,4 +1,4 @@ -package com.techfork.global.response.dto; +package com.techfork.global.response; import lombok.Builder; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/techfork/global/response/code/BaseCode.java b/src/main/java/com/techfork/global/response/code/BaseCode.java deleted file mode 100644 index be1ada2..0000000 --- a/src/main/java/com/techfork/global/response/code/BaseCode.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.techfork.global.response.code; - -import com.techfork.global.response.dto.ReasonDTO; - -public interface BaseCode { - ReasonDTO getReason(); -} From 319ca37caee8453a2bd569688a12472343e36b25 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Wed, 15 Oct 2025 00:56:47 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=B4=88=EA=B8=B0=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=85=8B=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techfork/TechForkApplication.java | 2 + .../domain/activity/entity/ReadPost.java | 36 +++++++++++++++ .../domain/activity/entity/ScrabPost.java | 33 ++++++++++++++ .../entity/NotificationToken.java | 24 ++++++++++ .../domain/post/document/PostDocument.java | 4 ++ .../domain/post/document/PostVector.java | 8 ++++ .../techfork/domain/post/entity/Keyword.java | 21 +++++++++ .../com/techfork/domain/post/entity/Post.java | 39 ++++++++++++++++ .../entity/RecommendedPost.java | 31 +++++++++++++ .../domain/search/entity/SearchHistory.java | 26 +++++++++++ .../domain/source/entity/CrawlingHistory.java | 18 ++++++++ .../domain/source/entity/TechBlog.java | 32 ++++++++++++++ .../user/document/UserProfileDocument.java | 4 ++ .../techfork/domain/user/entity/Interest.java | 23 ++++++++++ .../com/techfork/domain/user/entity/User.java | 15 +++++++ .../domain/user/enums/EInterestCategory.java | 44 +++++++++++++++++++ .../techfork/global/common/BaseEntity.java | 16 +++++++ .../global/common/BaseTimeEntity.java | 22 ++++++++++ 18 files changed, 398 insertions(+) create mode 100644 src/main/java/com/techfork/domain/activity/entity/ReadPost.java create mode 100644 src/main/java/com/techfork/domain/activity/entity/ScrabPost.java create mode 100644 src/main/java/com/techfork/domain/notification/entity/NotificationToken.java create mode 100644 src/main/java/com/techfork/domain/post/document/PostDocument.java create mode 100644 src/main/java/com/techfork/domain/post/document/PostVector.java create mode 100644 src/main/java/com/techfork/domain/post/entity/Keyword.java create mode 100644 src/main/java/com/techfork/domain/post/entity/Post.java create mode 100644 src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java create mode 100644 src/main/java/com/techfork/domain/search/entity/SearchHistory.java create mode 100644 src/main/java/com/techfork/domain/source/entity/CrawlingHistory.java create mode 100644 src/main/java/com/techfork/domain/source/entity/TechBlog.java create mode 100644 src/main/java/com/techfork/domain/user/document/UserProfileDocument.java create mode 100644 src/main/java/com/techfork/domain/user/entity/Interest.java create mode 100644 src/main/java/com/techfork/domain/user/entity/User.java create mode 100644 src/main/java/com/techfork/domain/user/enums/EInterestCategory.java create mode 100644 src/main/java/com/techfork/global/common/BaseEntity.java create mode 100644 src/main/java/com/techfork/global/common/BaseTimeEntity.java diff --git a/src/main/java/com/techfork/TechForkApplication.java b/src/main/java/com/techfork/TechForkApplication.java index ee2220a..2ff6a9a 100644 --- a/src/main/java/com/techfork/TechForkApplication.java +++ b/src/main/java/com/techfork/TechForkApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class TechForkApplication { diff --git a/src/main/java/com/techfork/domain/activity/entity/ReadPost.java b/src/main/java/com/techfork/domain/activity/entity/ReadPost.java new file mode 100644 index 0000000..824d23d --- /dev/null +++ b/src/main/java/com/techfork/domain/activity/entity/ReadPost.java @@ -0,0 +1,36 @@ +package com.techfork.domain.activity.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.user.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "read_posts", + uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "post_id"}) + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReadPost extends BaseEntity { + + @Column(nullable = false) + private LocalDateTime readAt; + + private Integer readDurationSeconds; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; +} diff --git a/src/main/java/com/techfork/domain/activity/entity/ScrabPost.java b/src/main/java/com/techfork/domain/activity/entity/ScrabPost.java new file mode 100644 index 0000000..3daa6ce --- /dev/null +++ b/src/main/java/com/techfork/domain/activity/entity/ScrabPost.java @@ -0,0 +1,33 @@ +package com.techfork.domain.activity.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.user.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "scrap_posts", + uniqueConstraints = { + @UniqueConstraint(columnNames = {"user_id", "post_id"}) + } +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ScrabPost extends BaseEntity { + + private LocalDateTime scrappedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; +} diff --git a/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java b/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java new file mode 100644 index 0000000..9e40d54 --- /dev/null +++ b/src/main/java/com/techfork/domain/notification/entity/NotificationToken.java @@ -0,0 +1,24 @@ +package com.techfork.domain.notification.entity; + +import com.techfork.domain.user.entity.User; +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class NotificationToken extends BaseTimeEntity { + + @Column(nullable = false, length = 500) + private String token; + + @Column(nullable = false) + private Boolean isActive = true; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; +} diff --git a/src/main/java/com/techfork/domain/post/document/PostDocument.java b/src/main/java/com/techfork/domain/post/document/PostDocument.java new file mode 100644 index 0000000..6a8dac7 --- /dev/null +++ b/src/main/java/com/techfork/domain/post/document/PostDocument.java @@ -0,0 +1,4 @@ +package com.techfork.domain.post.document; + +public class PostDocument { +} diff --git a/src/main/java/com/techfork/domain/post/document/PostVector.java b/src/main/java/com/techfork/domain/post/document/PostVector.java new file mode 100644 index 0000000..9f0597d --- /dev/null +++ b/src/main/java/com/techfork/domain/post/document/PostVector.java @@ -0,0 +1,8 @@ +package com.techfork.domain.post.document; + +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; + +public class PostVector { +} diff --git a/src/main/java/com/techfork/domain/post/entity/Keyword.java b/src/main/java/com/techfork/domain/post/entity/Keyword.java new file mode 100644 index 0000000..d5100cb --- /dev/null +++ b/src/main/java/com/techfork/domain/post/entity/Keyword.java @@ -0,0 +1,21 @@ +package com.techfork.domain.post.entity; + +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "keywords") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Keyword extends BaseEntity { + + @Column(unique = true, nullable = false, length = 100) + private String keyword; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; +} diff --git a/src/main/java/com/techfork/domain/post/entity/Post.java b/src/main/java/com/techfork/domain/post/entity/Post.java new file mode 100644 index 0000000..a6be4ec --- /dev/null +++ b/src/main/java/com/techfork/domain/post/entity/Post.java @@ -0,0 +1,39 @@ +package com.techfork.domain.post.entity; + +import com.techfork.domain.source.entity.TechBlog; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "posts") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Post extends BaseEntity { + + @Column(nullable = false, length = 500) + private String title; + + @Column(columnDefinition = "TEXT") + private String fullContent; + + @Column(nullable = false) + private String company; + + @Column(unique = true, nullable = false, length = 1000) + private String url; + + @Column(nullable = false) + private LocalDateTime publishedAt; + + @Column(nullable = false) + private LocalDateTime crawledAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tech_blog_id", nullable = false) + private TechBlog techBlog; +} diff --git a/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java b/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java new file mode 100644 index 0000000..8677a75 --- /dev/null +++ b/src/main/java/com/techfork/domain/recommendation/entity/RecommendedPost.java @@ -0,0 +1,31 @@ +package com.techfork.domain.recommendation.entity; + +import com.techfork.domain.post.entity.Post; +import com.techfork.domain.user.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "recommended_posts") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecommendedPost extends BaseEntity { + + @Column(nullable = false) + private Double similarityScore; + + private LocalDateTime recommendedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; +} diff --git a/src/main/java/com/techfork/domain/search/entity/SearchHistory.java b/src/main/java/com/techfork/domain/search/entity/SearchHistory.java new file mode 100644 index 0000000..a23a18e --- /dev/null +++ b/src/main/java/com/techfork/domain/search/entity/SearchHistory.java @@ -0,0 +1,26 @@ +package com.techfork.domain.search.entity; + +import com.techfork.domain.user.entity.User; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "search_histories") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SearchHistory extends BaseEntity { + + @Column(nullable = false, length = 200) + private String searchWord; + + private LocalDateTime searchedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/com/techfork/domain/source/entity/CrawlingHistory.java b/src/main/java/com/techfork/domain/source/entity/CrawlingHistory.java new file mode 100644 index 0000000..4beed88 --- /dev/null +++ b/src/main/java/com/techfork/domain/source/entity/CrawlingHistory.java @@ -0,0 +1,18 @@ +package com.techfork.domain.source.entity; + +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "crawling_histories") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CrawlingHistory extends BaseTimeEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tech_blog_id", nullable = false) + private TechBlog techBlog; +} diff --git a/src/main/java/com/techfork/domain/source/entity/TechBlog.java b/src/main/java/com/techfork/domain/source/entity/TechBlog.java new file mode 100644 index 0000000..d59c2a1 --- /dev/null +++ b/src/main/java/com/techfork/domain/source/entity/TechBlog.java @@ -0,0 +1,32 @@ +package com.techfork.domain.source.entity; + +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "tech_blogs") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TechBlog extends BaseTimeEntity { + + @Column(nullable = false, length = 100) + private String companyName; + + @Column(unique = true, nullable = false, length = 500) + private String blogUrl; + + @Column(unique = true, nullable = false, length = 500) + private String rssUrl; + + @Column(length = 500) + private String logoUrl; + + private LocalDateTime lastCrawledAt; +} diff --git a/src/main/java/com/techfork/domain/user/document/UserProfileDocument.java b/src/main/java/com/techfork/domain/user/document/UserProfileDocument.java new file mode 100644 index 0000000..f2fa54b --- /dev/null +++ b/src/main/java/com/techfork/domain/user/document/UserProfileDocument.java @@ -0,0 +1,4 @@ +package com.techfork.domain.user.document; + +public class UserProfileDocument { +} diff --git a/src/main/java/com/techfork/domain/user/entity/Interest.java b/src/main/java/com/techfork/domain/user/entity/Interest.java new file mode 100644 index 0000000..2a4a0a8 --- /dev/null +++ b/src/main/java/com/techfork/domain/user/entity/Interest.java @@ -0,0 +1,23 @@ +package com.techfork.domain.user.entity; + +import com.techfork.domain.user.enums.EInterestCategory; +import com.techfork.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "interests") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Interest extends BaseEntity { + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 50) + private EInterestCategory category; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +} diff --git a/src/main/java/com/techfork/domain/user/entity/User.java b/src/main/java/com/techfork/domain/user/entity/User.java new file mode 100644 index 0000000..d90231c --- /dev/null +++ b/src/main/java/com/techfork/domain/user/entity/User.java @@ -0,0 +1,15 @@ +package com.techfork.domain.user.entity; + +import com.techfork.global.common.BaseTimeEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "users") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class User extends BaseTimeEntity { +} diff --git a/src/main/java/com/techfork/domain/user/enums/EInterestCategory.java b/src/main/java/com/techfork/domain/user/enums/EInterestCategory.java new file mode 100644 index 0000000..23dea0a --- /dev/null +++ b/src/main/java/com/techfork/domain/user/enums/EInterestCategory.java @@ -0,0 +1,44 @@ +package com.techfork.domain.user.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EInterestCategory { + + IOS("iOS"), + ANDROID("Android"), + + FRONTEND("Frontend"), + + BACKEND("Backend"), + + DATA_ENGINEERING("Data Engineering"), + DATA_SCIENCE("Data Science"), + DATABASE("Database"), + + AI_ML("AI/ML"), + + DEVOPS("DevOps"), + CLOUD("Cloud"), + SYSTEMS_OS("Systems/OS"), + NETWORKING("Networking"), + + SECURITY("Security"), + + GAME_DEV("Game Dev"), + AR_VR_XR("AR/VR/XR"), + + EMBEDDED_IOT("Embedded/IoT"), + + BLOCKCHAIN_WEB3("Blockchain/Web3"), + + QA_TEST("QA/Test"), + + PRODUCT_UX("Product/UX"), + + ARCHITECTURE("Architecture"); + + private final String displayName; +} diff --git a/src/main/java/com/techfork/global/common/BaseEntity.java b/src/main/java/com/techfork/global/common/BaseEntity.java new file mode 100644 index 0000000..2f965f1 --- /dev/null +++ b/src/main/java/com/techfork/global/common/BaseEntity.java @@ -0,0 +1,16 @@ +package com.techfork.global.common; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@MappedSuperclass +@Getter +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; +} diff --git a/src/main/java/com/techfork/global/common/BaseTimeEntity.java b/src/main/java/com/techfork/global/common/BaseTimeEntity.java new file mode 100644 index 0000000..cbce2f4 --- /dev/null +++ b/src/main/java/com/techfork/global/common/BaseTimeEntity.java @@ -0,0 +1,22 @@ +package com.techfork.global.common; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +@Getter +public abstract class BaseTimeEntity extends BaseEntity { + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} From 6d767bf9a344865e99c6ebc6047a229c1f7c2915 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Wed, 15 Oct 2025 00:58:18 +0900 Subject: [PATCH 8/9] =?UTF-8?q?chore:=20dev=20=ED=94=84=EB=A1=9C=ED=95=84?= =?UTF-8?q?=20ddl=20=EC=84=A4=EC=A0=95=20update=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 1c23f90..9b950ee 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -14,7 +14,7 @@ spring: jpa: hibernate: - ddl-auto: create + ddl-auto: update properties: hibernate: show_sql: true From c89c7ed8be6251a3e42b54e84560d97e7de87d57 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Wed, 15 Oct 2025 01:27:25 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +-- .../techfork/global/config/SwaggerConfig.java | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/techfork/global/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 96e8789..58d091f 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + // implementation 'org.springframework.boot:spring-boot-starter-batch' // testImplementation 'org.springframework.batch:spring-batch-test' @@ -51,9 +53,6 @@ dependencies { // implementation 'org.springframework.boot:spring-boot-starter-security' // testImplementation 'org.springframework.security:spring-security-test' - // developmentOnly 'org.springframework.boot:spring-boot-docker-compose' - // developmentOnly 'org.springframework.ai:spring-ai-spring-boot-docker-compose' - // implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' // implementation 'org.springframework.ai:spring-ai-advisors-vector-store' // implementation 'org.springframework.ai:spring-ai-starter-vector-store-elasticsearch' diff --git a/src/main/java/com/techfork/global/config/SwaggerConfig.java b/src/main/java/com/techfork/global/config/SwaggerConfig.java new file mode 100644 index 0000000..061fe3c --- /dev/null +++ b/src/main/java/com/techfork/global/config/SwaggerConfig.java @@ -0,0 +1,37 @@ +package com.techfork.global.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.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI techforkAPI() { + Info info = new Info() + .title("테크포크 API 문서") + .description("테크포크 API 명세서입니다.") + .version("1.0.0"); + + String jwtSchemeName = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName); + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .addServersItem(new Server().url("/")) + .info(info) + .addSecurityItem(securityRequirement) + .components(components); + } +}