From 2eab551747b1b5d151c81ea963705e61b50322e3 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 28 Mar 2025 00:41:55 +0900 Subject: [PATCH 01/39] =?UTF-8?q?feat:=20#1=20API=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/apiPayload/ApiResponse.java | 37 +++++++++++++ .../LogIT/apiPayload/code/BaseCode.java | 9 ++++ .../LogIT/apiPayload/code/BaseErrorCode.java | 8 +++ .../LogIT/apiPayload/code/ErrorReasonDTO.java | 43 +++++++++++++++ .../LogIT/apiPayload/code/ReasonDTO.java | 45 ++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 51 ++++++++++++++++++ .../apiPayload/code/status/SuccessStatus.java | 52 +++++++++++++++++++ 7 files changed, 245 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/ApiResponse.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/BaseCode.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/BaseErrorCode.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/ErrorReasonDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/ReasonDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/code/status/SuccessStatus.java diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/ApiResponse.java b/src/main/java/LogITBackend/LogIT/apiPayload/ApiResponse.java new file mode 100644 index 0000000..2d4c9f7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/ApiResponse.java @@ -0,0 +1,37 @@ +package LogITBackend.LogIT.apiPayload; + +import LogITBackend.LogIT.apiPayload.code.BaseCode; +import LogITBackend.LogIT.apiPayload.code.status.SuccessStatus; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + // 성공한 경우 응답 생성 + + public static ApiResponse onSuccess(T result){ + return new ApiResponse<>(true, SuccessStatus._OK.getCode(), SuccessStatus._OK.getMessage(), result); + } + + public static ApiResponse of(BaseCode code, T result){ + return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result); + } + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T data){ + return new ApiResponse<>(false, code, message, data); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseCode.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseCode.java new file mode 100644 index 0000000..b31deb2 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseCode.java @@ -0,0 +1,9 @@ +package LogITBackend.LogIT.apiPayload.code; + +public interface BaseCode { + + public ReasonDTO getReason(); + + public ReasonDTO getReasonHttpStatus(); + +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseErrorCode.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..442453e --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,8 @@ +package LogITBackend.LogIT.apiPayload.code; + +public interface BaseErrorCode { + + public ErrorReasonDTO getReason(); + + public ErrorReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/ErrorReasonDTO.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/ErrorReasonDTO.java new file mode 100644 index 0000000..5d363c7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/ErrorReasonDTO.java @@ -0,0 +1,43 @@ +package LogITBackend.LogIT.apiPayload.code; + +import lombok.Builder; +import org.springframework.http.HttpStatus; + +public class ErrorReasonDTO { + private Boolean isSuccess; + private String code; + private String message; + + private HttpStatus httpStatus; + + public Boolean getSuccess() { + return isSuccess; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + +// @Builder +// public ErrorReasonDTO(Boolean isSuccess, String code, String message) { +// this.isSuccess = isSuccess; +// this.code = code; +// this.message = message; +// } + + @Builder + public ErrorReasonDTO(Boolean isSuccess, String code, String message, HttpStatus httpStatus) { + this.isSuccess = isSuccess; + this.code = code; + this.message = message; + this.httpStatus = httpStatus; + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/ReasonDTO.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/ReasonDTO.java new file mode 100644 index 0000000..5759b88 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/ReasonDTO.java @@ -0,0 +1,45 @@ +package LogITBackend.LogIT.apiPayload.code; + +import lombok.Builder; +import org.springframework.http.HttpStatus; + +public class ReasonDTO { + + private Boolean isSuccess; + private String code; + private String message; + + private HttpStatus httpStatus; + + public Boolean getSuccess() { + return isSuccess; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } + public HttpStatus getHttpStatus() { + return httpStatus; + } + +// @Builder +// public ReasonDTO(Boolean isSuccess, String code, String message) { +// this.isSuccess = isSuccess; +// this.code = code; +// this.message = message; +// } + + @Builder + public ReasonDTO(Boolean isSuccess, String code, String message, HttpStatus httpStatus) { + this.isSuccess = isSuccess; + this.code = code; + this.message = message; + this.httpStatus = httpStatus; + } + + //..g +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java new file mode 100644 index 0000000..7f5a4b7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,51 @@ +package LogITBackend.LogIT.apiPayload.code.status; + +import LogITBackend.LogIT.apiPayload.code.BaseErrorCode; +import LogITBackend.LogIT.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + // 가장 일반적인 응답 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + + // 회원 관련 응답 1000 + USER_ID_NULL(HttpStatus.BAD_REQUEST, "USER_1001", "사용자 아이디는 필수 입니다."), + USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER_1002", "해당하는 사용자가 존재하지 않습니다."), + NICKNAME_NOT_EXIST(HttpStatus.BAD_REQUEST, "USER_1003", "닉네임은 필수 입니다."), + EMAIL_ALREADY_EXIST(HttpStatus.BAD_REQUEST, "USER_1004", "이미 존재하는 이메일 입니다."), + NAME_NOT_EQUAL(HttpStatus.BAD_REQUEST, "USER_1005", "이름이 일치하지 않습니다."), + ID_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER_1006", "해당하는 ID가 존재하지 않습니다."), + ID_NOT_EQUAL(HttpStatus.BAD_REQUEST, "USER_1007", "ID가 일치하지 않습니다."), + SAME_PASSWORD(HttpStatus.BAD_REQUEST, "USER_1008", "이전 비밀번호와 동일합니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/SuccessStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/SuccessStatus.java new file mode 100644 index 0000000..ef9d046 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/SuccessStatus.java @@ -0,0 +1,52 @@ +package LogITBackend.LogIT.apiPayload.code.status; + +import LogITBackend.LogIT.apiPayload.code.BaseCode; +import LogITBackend.LogIT.apiPayload.code.ReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + // 가장 일반적인 응답 + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + +// public String getCode() { +// return "2000"; +// } +// +// public ReasonDTO getMessage() { +// return ReasonDTO.builder() +// .message(message) +// .code(code) +// .isSuccess(false) +// .httpStatus(httpStatus) +// .build() +// ; +// } + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .build(); + } + + @Override + public ReasonDTO getReasonHttpStatus() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build(); + } +} From d37f5ca7d307de6f33e5b9daa5c6484c538d8e79 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 28 Mar 2025 00:48:58 +0900 Subject: [PATCH 02/39] =?UTF-8?q?feat:=20#3=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 +- .../apiPayload/exception/ExceptionAdvice.java | 120 ++++++++++++++++++ .../exception/GeneralException.java | 21 +++ .../exception/handler/ExceptionHandler.java | 11 ++ .../exception/handler/TempHandler.java | 12 ++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/exception/ExceptionAdvice.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/exception/GeneralException.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/ExceptionHandler.java create mode 100644 src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/TempHandler.java diff --git a/build.gradle b/build.gradle index 5363bc3..6bf84de 100644 --- a/build.gradle +++ b/build.gradle @@ -24,12 +24,22 @@ repositories { } dependencies { + // web implementation 'org.springframework.boot:spring-boot-starter-web' + + // lombok compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + + // mysql + runtimeOnly 'com.mysql:mysql-connector-j' + + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // validation + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/exception/ExceptionAdvice.java b/src/main/java/LogITBackend/LogIT/apiPayload/exception/ExceptionAdvice.java new file mode 100644 index 0000000..55796f6 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/exception/ExceptionAdvice.java @@ -0,0 +1,120 @@ +package LogITBackend.LogIT.apiPayload.exception; + +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.apiPayload.code.ErrorReasonDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +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.ServletWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@RestControllerAdvice(annotations = {RestController.class}) +public class ExceptionAdvice extends ResponseEntityExceptionHandler { + + + @ExceptionHandler + public ResponseEntity validation(ConstraintViolationException e, WebRequest request) { + String errorMessage = e.getConstraintViolations().stream() + .map(constraintViolation -> constraintViolation.getMessage()) + .findFirst() + .orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생")); + + return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request); + } + + @Override + public ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + + Map errors = new LinkedHashMap<>(); + + e.getBindingResult().getFieldErrors().stream() + .forEach(fieldError -> { + String fieldName = fieldError.getField(); + String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse(""); + errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage); + }); + + return handleExceptionInternalArgs(e,HttpHeaders.EMPTY,ErrorStatus.valueOf("_BAD_REQUEST"),request,errors); + } + + @ExceptionHandler + public ResponseEntity exception(Exception e, WebRequest request) { + e.printStackTrace(); + + return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage()); + } + + @ExceptionHandler(value = GeneralException.class) + public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) { + ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus(); + return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request); + } + + private ResponseEntity handleExceptionInternal(Exception e, ErrorReasonDTO reason, + HttpHeaders headers, HttpServletRequest request) { + + ApiResponse body = ApiResponse.onFailure(reason.getCode(),reason.getMessage(),null); +// e.printStackTrace(); + + WebRequest webRequest = new ServletWebRequest(request); + return super.handleExceptionInternal( + e, + body, + headers, + reason.getHttpStatus(), + webRequest + ); + } + + private ResponseEntity handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint); + return super.handleExceptionInternal( + e, + body, + headers, + status, + request + ); + } + + private ResponseEntity handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus, + WebRequest request, Map errorArgs) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } + + private ResponseEntity handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus, + HttpHeaders headers, WebRequest request) { + ApiResponse body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null); + return super.handleExceptionInternal( + e, + body, + headers, + errorCommonStatus.getHttpStatus(), + request + ); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/exception/GeneralException.java b/src/main/java/LogITBackend/LogIT/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..0ee5849 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/exception/GeneralException.java @@ -0,0 +1,21 @@ +package LogITBackend.LogIT.apiPayload.exception; + +import LogITBackend.LogIT.apiPayload.code.BaseErrorCode; +import LogITBackend.LogIT.apiPayload.code.ErrorReasonDTO; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class GeneralException extends RuntimeException { + + private BaseErrorCode code; + + public ErrorReasonDTO getErrorReason() { + return this.code.getReason(); + } + + public ErrorReasonDTO getErrorReasonHttpStatus(){ + return this.code.getReasonHttpStatus(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/ExceptionHandler.java b/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/ExceptionHandler.java new file mode 100644 index 0000000..1c7cf12 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/ExceptionHandler.java @@ -0,0 +1,11 @@ +package LogITBackend.LogIT.apiPayload.exception.handler; + + +import LogITBackend.LogIT.apiPayload.code.BaseErrorCode; +import LogITBackend.LogIT.apiPayload.exception.GeneralException; + +public class ExceptionHandler extends GeneralException { + public ExceptionHandler(BaseErrorCode code) { + super(code); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/TempHandler.java b/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/TempHandler.java new file mode 100644 index 0000000..4f79852 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/exception/handler/TempHandler.java @@ -0,0 +1,12 @@ +package LogITBackend.LogIT.apiPayload.exception.handler; + + +import LogITBackend.LogIT.apiPayload.code.BaseErrorCode; +import LogITBackend.LogIT.apiPayload.exception.GeneralException; + +public class TempHandler extends GeneralException { + + public TempHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} From 189b6c04bcbdc19f37a78a841e26d7918ecadff2 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 28 Mar 2025 01:08:13 +0900 Subject: [PATCH 03/39] =?UTF-8?q?feat:=20#5=20=ED=9A=8C=EC=9B=90,=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C,=20=EC=BD=94=EB=93=9C=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20domain=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + build.gradle | 3 + .../LogITBackend/LogIT/LogItApplication.java | 2 + .../LogIT/domain/CodeCategories.java | 48 ++++++++++++ .../java/LogITBackend/LogIT/domain/Codes.java | 73 +++++++++++++++++++ .../java/LogITBackend/LogIT/domain/Users.java | 70 ++++++++++++++++++ .../LogIT/domain/common/BaseEntity.java | 22 ++++++ .../LogIT/domain/enums/Gender.java | 5 ++ .../LogIT/domain/enums/LoginType.java | 5 ++ .../LogIT/domain/enums/UserStatus.java | 5 ++ src/main/resources/application.properties | 1 - 11 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/main/java/LogITBackend/LogIT/domain/CodeCategories.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/Codes.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/Users.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/common/BaseEntity.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/enums/Gender.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/enums/LoginType.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/enums/UserStatus.java delete mode 100644 src/main/resources/application.properties diff --git a/.gitignore b/.gitignore index c2065bc..f8bc0b0 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +application.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6bf84de..828b0c4 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,9 @@ dependencies { // validation implementation 'org.springframework.boot:spring-boot-starter-validation' + + // jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' } tasks.named('test') { diff --git a/src/main/java/LogITBackend/LogIT/LogItApplication.java b/src/main/java/LogITBackend/LogIT/LogItApplication.java index 83e3bb5..16d01f2 100644 --- a/src/main/java/LogITBackend/LogIT/LogItApplication.java +++ b/src/main/java/LogITBackend/LogIT/LogItApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing public class LogItApplication { public static void main(String[] args) { diff --git a/src/main/java/LogITBackend/LogIT/domain/CodeCategories.java b/src/main/java/LogITBackend/LogIT/domain/CodeCategories.java new file mode 100644 index 0000000..5eb9913 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/CodeCategories.java @@ -0,0 +1,48 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class CodeCategories extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users users; + + @OneToMany(mappedBy = "codeCategories", cascade = CascadeType.ALL) + private List codesList = new ArrayList<>(); + + public void setUsers(Users users) { + // 기존에 이미 등록되어 있던 관계를 제거 + if (this.users != null) { + this.users.getCodeCategoriesList().remove(this); + } + + this.users = users; + + // 양방향 관계를 설정 + if (users != null) { + users.getCodeCategoriesList().add(this); + } + } +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Codes.java b/src/main/java/LogITBackend/LogIT/domain/Codes.java new file mode 100644 index 0000000..489ad86 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Codes.java @@ -0,0 +1,73 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Codes extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 40) + private String title; + + @Column(columnDefinition = "TEXT") + private String content; + + private LocalDateTime date; + + @Column(nullable = false, length = 50) + private String fileLocation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users users; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "code_category_id") + private CodeCategories codeCategories; + + public void setUsers(Users users) { + // 기존에 이미 등록되어 있던 관계를 제거 + if (this.users != null) { + this.users.getCodesList().remove(this); + } + + this.users = users; + + // 양방향 관계를 설정 + if (users != null) { + users.getCodesList().add(this); + } + } + + public void setDiaryCategories(CodeCategories codeCategories) { + // 기존에 이미 등록되어 있던 관계를 제거 + if (this.codeCategories != null) { + this.codeCategories.getCodesList().remove(this); + } + + this.codeCategories = codeCategories; + + // 양방향 관계를 설정 + if (codeCategories != null) { + codeCategories.getCodesList().add(this); + } + } + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java new file mode 100644 index 0000000..f0cca4f --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -0,0 +1,70 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import LogITBackend.LogIT.domain.enums.Gender; +import LogITBackend.LogIT.domain.enums.LoginType; +import LogITBackend.LogIT.domain.enums.UserStatus; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Users extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private String name; + + @Column(nullable = false, length = 8) + private String nickname; + + @Column(length = 15) + private String username; + + @Column(length = 100) + private String password; + + @Enumerated(EnumType.STRING) + @Column + private LoginType loginType; + + @Column(length = 50) + private String email; + + @Column(columnDefinition = "TEXT") + private String profileImage; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private Gender gender; + + @Enumerated(EnumType.STRING) + @Column + private UserStatus status; + + private LocalDateTime inactiveDate; + + // 로그인 관련 +// private LocalDateTime lastLogin; + + @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) + private List codesList = new ArrayList<>(); + + @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) + private List codeCategoriesList = new ArrayList<>(); +} diff --git a/src/main/java/LogITBackend/LogIT/domain/common/BaseEntity.java b/src/main/java/LogITBackend/LogIT/domain/common/BaseEntity.java new file mode 100644 index 0000000..39b4537 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/common/BaseEntity.java @@ -0,0 +1,22 @@ +package LogITBackend.LogIT.domain.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; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/src/main/java/LogITBackend/LogIT/domain/enums/Gender.java b/src/main/java/LogITBackend/LogIT/domain/enums/Gender.java new file mode 100644 index 0000000..4ec209f --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/enums/Gender.java @@ -0,0 +1,5 @@ +package LogITBackend.LogIT.domain.enums; + +public enum Gender { + MALE, FEMALE +} diff --git a/src/main/java/LogITBackend/LogIT/domain/enums/LoginType.java b/src/main/java/LogITBackend/LogIT/domain/enums/LoginType.java new file mode 100644 index 0000000..4ebb22e --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/enums/LoginType.java @@ -0,0 +1,5 @@ +package LogITBackend.LogIT.domain.enums; + +public enum LoginType { + REGULAR +} diff --git a/src/main/java/LogITBackend/LogIT/domain/enums/UserStatus.java b/src/main/java/LogITBackend/LogIT/domain/enums/UserStatus.java new file mode 100644 index 0000000..d506507 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/enums/UserStatus.java @@ -0,0 +1,5 @@ +package LogITBackend.LogIT.domain.enums; + +public enum UserStatus { + ACTIVE, INACTIVE +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 1eb08c0..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=LogIT From de76acbd42861156ee810d5ed33b25ec3fbd0f55 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 28 Mar 2025 01:14:03 +0900 Subject: [PATCH 04/39] =?UTF-8?q?feat:=20#7=20swagger=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../LogIT/config/SwaggerConfig.java | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/config/SwaggerConfig.java diff --git a/build.gradle b/build.gradle index 828b0c4..e6d67b3 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,9 @@ dependencies { // jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4' } tasks.named('test') { diff --git a/src/main/java/LogITBackend/LogIT/config/SwaggerConfig.java b/src/main/java/LogITBackend/LogIT/config/SwaggerConfig.java new file mode 100644 index 0000000..249af4d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/SwaggerConfig.java @@ -0,0 +1,48 @@ +package LogITBackend.LogIT.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 LogITAPI() { + Info info = new Info() + .title("LogIT BE API") + .description("LogIT 백엔드 API 명세서") + .version("1.0.0"); + + String jwtSchemeName = "JWT access token"; + String refreshKey = "JWT refresh token"; + // API 요청헤더에 인증정보 포함 + SecurityRequirement securityRequirement = new SecurityRequirement() + .addList(jwtSchemeName) + .addList(refreshKey); + // SecuritySchemes 등록 + Components components = new Components() + .addSecuritySchemes(jwtSchemeName, new SecurityScheme() + .name(jwtSchemeName) + .type(SecurityScheme.Type.HTTP) // HTTP 방식 + .scheme("bearer") + .bearerFormat("JWT") + ) + .addSecuritySchemes(refreshKey, new SecurityScheme() + .name("RefreshToken") + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + ); + + return new OpenAPI() + .addServersItem(new Server().url("/")) + .info(info) + .addSecurityItem(securityRequirement) + .components(components); + } +} From c29eaf5b127c6a34c646828b31069c214fb341e3 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 31 Mar 2025 01:56:58 +0900 Subject: [PATCH 05/39] =?UTF-8?q?feat:=20#10=20category=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D,=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/CategoryRequestDTO.java | 18 ++++++++ .../LogIT/DTO/CategoryResponseDTO.java | 17 +++++++ .../apiPayload/code/status/ErrorStatus.java | 6 ++- .../LogIT/controller/CategoryController.java | 31 +++++++++++++ .../LogIT/controller/CodeController.java | 18 ++++++++ .../LogIT/repository/CategoryRepository.java | 14 ++++++ .../LogIT/repository/UserRepository.java | 11 +++++ .../LogIT/service/CategoryService.java | 12 +++++ .../LogIT/service/CategoryServiceImpl.java | 46 +++++++++++++++++++ 9 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CategoryRequestDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CategoryResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/controller/CategoryController.java create mode 100644 src/main/java/LogITBackend/LogIT/controller/CodeController.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/UserRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/service/CategoryService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/CategoryRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CategoryRequestDTO.java new file mode 100644 index 0000000..cea2330 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CategoryRequestDTO.java @@ -0,0 +1,18 @@ +package LogITBackend.LogIT.DTO; + +import LogITBackend.LogIT.domain.CodeCategories; +import LogITBackend.LogIT.domain.Users; +import lombok.Getter; + +@Getter +public class CategoryRequestDTO { + private String category; + + public CodeCategories ToEntity(Users user) { + return CodeCategories.builder() + .name(category) + .users(user) + .build(); + + } +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/CategoryResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CategoryResponseDTO.java new file mode 100644 index 0000000..c63decc --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CategoryResponseDTO.java @@ -0,0 +1,17 @@ +package LogITBackend.LogIT.DTO; + +import LogITBackend.LogIT.domain.CodeCategories; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class CategoryResponseDTO { + private String category; + + public static CategoryResponseDTO ToDTO(CodeCategories category) { + return CategoryResponseDTO.builder() + .category(category.getName()) + .build(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index 7f5a4b7..e9e9d1f 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -24,7 +24,11 @@ public enum ErrorStatus implements BaseErrorCode { NAME_NOT_EQUAL(HttpStatus.BAD_REQUEST, "USER_1005", "이름이 일치하지 않습니다."), ID_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER_1006", "해당하는 ID가 존재하지 않습니다."), ID_NOT_EQUAL(HttpStatus.BAD_REQUEST, "USER_1007", "ID가 일치하지 않습니다."), - SAME_PASSWORD(HttpStatus.BAD_REQUEST, "USER_1008", "이전 비밀번호와 동일합니다."); + SAME_PASSWORD(HttpStatus.BAD_REQUEST, "USER_1008", "이전 비밀번호와 동일합니다."), + + // 카테고리 관련 응답 2000 + CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_1001", "카테고리가 존재하지 않습니다."); + private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/LogITBackend/LogIT/controller/CategoryController.java b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java new file mode 100644 index 0000000..c88914b --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java @@ -0,0 +1,31 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.DTO.CategoryRequestDTO; +import LogITBackend.LogIT.DTO.CategoryResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.service.CategoryService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class CategoryController { + + private final CategoryService categoryService; + + + @GetMapping("") + public ApiResponse> getCategories() { + List categories = categoryService.getCategories(); + return ApiResponse.onSuccess(categories); + } + + @PostMapping("") + public ApiResponse createCategory(@RequestBody CategoryRequestDTO request) { + CategoryResponseDTO category = categoryService.createCategory(request); + return ApiResponse.onSuccess(category); + } +} diff --git a/src/main/java/LogITBackend/LogIT/controller/CodeController.java b/src/main/java/LogITBackend/LogIT/controller/CodeController.java new file mode 100644 index 0000000..58f3162 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/CodeController.java @@ -0,0 +1,18 @@ +package LogITBackend.LogIT.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/codes") +public class CodeController { + +// @PostMapping("") +// public ResponseEntity addCode(@RequestParam("code") String code) { +// return code; +// } + +} diff --git a/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java new file mode 100644 index 0000000..6222298 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java @@ -0,0 +1,14 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.CodeCategories; +import jakarta.validation.constraints.NotNull; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.lang.NonNullApi; + +import java.util.List; + +public interface CategoryRepository extends JpaRepository { + + List findAll(); + CodeCategories save(CodeCategories category); +} diff --git a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java new file mode 100644 index 0000000..5be60bd --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -0,0 +1,11 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Users; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + @Override + Optional findById(Long id); +} diff --git a/src/main/java/LogITBackend/LogIT/service/CategoryService.java b/src/main/java/LogITBackend/LogIT/service/CategoryService.java new file mode 100644 index 0000000..442269d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/CategoryService.java @@ -0,0 +1,12 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CategoryRequestDTO; +import LogITBackend.LogIT.DTO.CategoryResponseDTO; + +import java.util.List; + +public interface CategoryService { + List getCategories(); + + CategoryResponseDTO createCategory(CategoryRequestDTO request); +} diff --git a/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java new file mode 100644 index 0000000..c6d6a5c --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java @@ -0,0 +1,46 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CategoryRequestDTO; +import LogITBackend.LogIT.DTO.CategoryResponseDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import LogITBackend.LogIT.apiPayload.exception.GeneralException; +import LogITBackend.LogIT.domain.CodeCategories; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.CategoryRepository; + +import LogITBackend.LogIT.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.sql.SQLOutput; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final CategoryRepository categoryRepository; + private final UserRepository userRepository; + + @Override + public List getCategories() { + List categories = categoryRepository.findAll(); + if (categories.isEmpty()) { + throw new GeneralException(ErrorStatus.CATEGORY_NOT_FOUND); + } + return categories.stream() + .map(category -> CategoryResponseDTO.builder() + .category(category.getName()) + .build()) + .toList(); + } + + @Override + public CategoryResponseDTO createCategory(CategoryRequestDTO request) { + Users dummyUser = userRepository.findById(1L) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + System.out.println("dummyUser = " + dummyUser.getName()); + CodeCategories category = request.ToEntity(dummyUser); + System.out.println("category.getName() = " + category.getName()); + return CategoryResponseDTO.ToDTO(categoryRepository.save(category)); + } +} From b8cabc6acf779843abb05d5181ddeb5ff87a802f Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 31 Mar 2025 11:36:06 +0900 Subject: [PATCH 06/39] =?UTF-8?q?=EC=9D=91=EB=8B=B5=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/controller/CategoryController.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/LogITBackend/LogIT/controller/CategoryController.java b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java index c88914b..a2d87fe 100644 --- a/src/main/java/LogITBackend/LogIT/controller/CategoryController.java +++ b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java @@ -5,6 +5,7 @@ import LogITBackend.LogIT.apiPayload.ApiResponse; import LogITBackend.LogIT.service.CategoryService; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -18,14 +19,14 @@ public class CategoryController { @GetMapping("") - public ApiResponse> getCategories() { + public ResponseEntity>> getCategories() { List categories = categoryService.getCategories(); - return ApiResponse.onSuccess(categories); + return ResponseEntity.ok(ApiResponse.onSuccess(categories)); } @PostMapping("") - public ApiResponse createCategory(@RequestBody CategoryRequestDTO request) { + public ResponseEntity> createCategory(@RequestBody CategoryRequestDTO request) { CategoryResponseDTO category = categoryService.createCategory(request); - return ApiResponse.onSuccess(category); + return ResponseEntity.ok(ApiResponse.onSuccess(category)); } } From 5b8f88e86736bf890625246e2151a127f414c4d6 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Mon, 31 Mar 2025 13:35:10 +0900 Subject: [PATCH 07/39] =?UTF-8?q?feat:=20#9=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++ .../LogIT/config/security/SecurityConfig.java | 42 +++++++++++++++++++ .../LogIT/converter/UserConverter.java | 28 +++++++++++++ .../java/LogITBackend/LogIT/domain/Users.java | 4 ++ .../LogIT/repository/UserRepository.java | 12 ++++++ .../LogIT/service/UserCommandService.java | 8 ++++ .../LogIT/service/UserCommandServiceImpl.java | 27 ++++++++++++ .../LogIT/web/controller/UserController.java | 37 ++++++++++++++++ .../LogIT/web/dto/UserRequestDTO.java | 29 +++++++++++++ .../LogIT/web/dto/UserResponseDTO.java | 22 ++++++++++ 10 files changed, 213 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java create mode 100644 src/main/java/LogITBackend/LogIT/converter/UserConverter.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/UserRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/service/UserCommandService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java create mode 100644 src/main/java/LogITBackend/LogIT/web/controller/UserController.java create mode 100644 src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java diff --git a/build.gradle b/build.gradle index e6d67b3..fedd5f6 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,10 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.4' + + // spring security + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java new file mode 100644 index 0000000..9ed5750 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -0,0 +1,42 @@ +package LogITBackend.LogIT.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@EnableWebSecurity +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + +// private final JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) // 추가해주어야함. + // 폼 로그인 비활성화 + .formLogin(AbstractHttpConfigurer::disable) + // HTTP Basic 인증 비활성화 + .httpBasic(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 Stateless로 설정 + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/**").permitAll()); +// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java new file mode 100644 index 0000000..cbb99e1 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java @@ -0,0 +1,28 @@ +package LogITBackend.LogIT.converter; + +import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.web.dto.UserResponseDTO; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.domain.enums.LoginType; + +import java.util.ArrayList; + +public class UserConverter { + public static Users toUsers(UserRequestDTO.SignUpRequestDTO request) { + return Users.builder() +// .name(request.getName()) +// .email(request.getEmail()) + .nickname(request.getNickname()) + .username(request.getUsername()) +// .loginType(LoginType.REGULAR) + .build(); + } + public static UserResponseDTO.UserSignUpResultDTO toUserSignUpResultDTO(Users users) { + return UserResponseDTO.UserSignUpResultDTO.builder() + .userId(users.getId()) + .nickname(users.getNickname()) + .loginType(users.getLoginType()) + .createdAt(users.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index f0cca4f..2d2631b 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -67,4 +67,8 @@ public class Users extends BaseEntity { @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) private List codeCategoriesList = new ArrayList<>(); + + public void encodePassword(String password) { + this.password = password; + } } diff --git a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java new file mode 100644 index 0000000..071c8e3 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -0,0 +1,12 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Users; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java new file mode 100644 index 0000000..675189d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -0,0 +1,8 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.domain.Users; + +public interface UserCommandService { + Users signUp(UserRequestDTO.SignUpRequestDTO request); +} diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java new file mode 100644 index 0000000..b224c86 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -0,0 +1,27 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.converter.UserConverter; +import LogITBackend.LogIT.repository.UserRepository; +import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.domain.Users; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserCommandServiceImpl implements UserCommandService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Override + @Transactional // ??? + public Users signUp(UserRequestDTO.SignUpRequestDTO request) { + Users newUser = UserConverter.toUsers(request); + newUser.encodePassword(passwordEncoder.encode(request.getPassword())); + return userRepository.save(newUser); + } +} diff --git a/src/main/java/LogITBackend/LogIT/web/controller/UserController.java b/src/main/java/LogITBackend/LogIT/web/controller/UserController.java new file mode 100644 index 0000000..254a6b5 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/web/controller/UserController.java @@ -0,0 +1,37 @@ +package LogITBackend.LogIT.web.controller; + +import LogITBackend.LogIT.converter.UserConverter; +import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.web.dto.UserResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.service.UserCommandService; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/users") +public class UserController { + private final UserCommandService userCommandService; + + @Operation(summary = "회원가입", description = + "# 회원가입 API 입니다. 닉네임과 아이디, 패스워드를 body에 입력해주세요." + ) + @PostMapping("/signup") + public ApiResponse signUp( + @RequestBody @Valid UserRequestDTO.SignUpRequestDTO request + ) { + Users newUser = userCommandService.signUp(request); + return ApiResponse.onSuccess( + UserConverter.toUserSignUpResultDTO( + newUser + ) + ); + } +} diff --git a/src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java new file mode 100644 index 0000000..935f609 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java @@ -0,0 +1,29 @@ +package LogITBackend.LogIT.web.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class UserRequestDTO { + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class SignUpRequestDTO { +// @NotBlank(message = "이름은 빈값일 수 없습니다.") +// private String name; +// @NotBlank(message = "이메일은 빈값일 수 없습니다.") +// private String email; + @NotBlank(message = "닉네임은 빈값일 수 없습니다.") + @Size(max = 8, message = "닉네임은 1 ~ 8자이어야 합니다.") + private String nickname; + @NotBlank(message = "아이디는 빈값일 수 없습니다.") + @Size(min = 6, max = 15, message = "아이디는 6 ~ 15자이어야 합니다.") + private String username; + @NotBlank(message = "비밀번호는 빈값일 수 없습니다.") + private String password; + } +} diff --git a/src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java b/src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java new file mode 100644 index 0000000..92832d8 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java @@ -0,0 +1,22 @@ +package LogITBackend.LogIT.web.dto; + +import LogITBackend.LogIT.domain.enums.LoginType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class UserResponseDTO { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserSignUpResultDTO { + Long userId; + String nickname; + LoginType loginType; + LocalDateTime createdAt; + } +} From 55c1785c158022d3b2d2dc8b6a6a5b06543d834b Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Mon, 31 Mar 2025 19:57:23 +0900 Subject: [PATCH 08/39] =?UTF-8?q?refactor:=20#14=20controller=20=EB=B0=8F?= =?UTF-8?q?=20DTO=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=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 --- .../LogIT/{web/dto => DTO}/UserRequestDTO.java | 2 +- .../LogIT/{web/dto => DTO}/UserResponseDTO.java | 2 +- .../LogIT/{web => }/controller/UserController.java | 6 +++--- .../java/LogITBackend/LogIT/converter/UserConverter.java | 7 ++----- .../LogITBackend/LogIT/service/UserCommandService.java | 2 +- .../LogITBackend/LogIT/service/UserCommandServiceImpl.java | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) rename src/main/java/LogITBackend/LogIT/{web/dto => DTO}/UserRequestDTO.java (96%) rename src/main/java/LogITBackend/LogIT/{web/dto => DTO}/UserResponseDTO.java (92%) rename src/main/java/LogITBackend/LogIT/{web => }/controller/UserController.java (89%) diff --git a/src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java similarity index 96% rename from src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java rename to src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java index 935f609..66154a4 100644 --- a/src/main/java/LogITBackend/LogIT/web/dto/UserRequestDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java @@ -1,4 +1,4 @@ -package LogITBackend.LogIT.web.dto; +package LogITBackend.LogIT.DTO; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; diff --git a/src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java similarity index 92% rename from src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java rename to src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java index 92832d8..31f5225 100644 --- a/src/main/java/LogITBackend/LogIT/web/dto/UserResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java @@ -1,4 +1,4 @@ -package LogITBackend.LogIT.web.dto; +package LogITBackend.LogIT.DTO; import LogITBackend.LogIT.domain.enums.LoginType; import lombok.AllArgsConstructor; diff --git a/src/main/java/LogITBackend/LogIT/web/controller/UserController.java b/src/main/java/LogITBackend/LogIT/controller/UserController.java similarity index 89% rename from src/main/java/LogITBackend/LogIT/web/controller/UserController.java rename to src/main/java/LogITBackend/LogIT/controller/UserController.java index 254a6b5..9c75dad 100644 --- a/src/main/java/LogITBackend/LogIT/web/controller/UserController.java +++ b/src/main/java/LogITBackend/LogIT/controller/UserController.java @@ -1,8 +1,8 @@ -package LogITBackend.LogIT.web.controller; +package LogITBackend.LogIT.controller; import LogITBackend.LogIT.converter.UserConverter; -import LogITBackend.LogIT.web.dto.UserRequestDTO; -import LogITBackend.LogIT.web.dto.UserResponseDTO; +import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserResponseDTO; import LogITBackend.LogIT.apiPayload.ApiResponse; import LogITBackend.LogIT.domain.Users; import LogITBackend.LogIT.service.UserCommandService; diff --git a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java index cbb99e1..310434f 100644 --- a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java +++ b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java @@ -1,11 +1,8 @@ package LogITBackend.LogIT.converter; -import LogITBackend.LogIT.web.dto.UserRequestDTO; -import LogITBackend.LogIT.web.dto.UserResponseDTO; +import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserResponseDTO; import LogITBackend.LogIT.domain.Users; -import LogITBackend.LogIT.domain.enums.LoginType; - -import java.util.ArrayList; public class UserConverter { public static Users toUsers(UserRequestDTO.SignUpRequestDTO request) { diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java index 675189d..af3600c 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -1,6 +1,6 @@ package LogITBackend.LogIT.service; -import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserRequestDTO; import LogITBackend.LogIT.domain.Users; public interface UserCommandService { diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java index b224c86..28b4fa0 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -2,7 +2,7 @@ import LogITBackend.LogIT.converter.UserConverter; import LogITBackend.LogIT.repository.UserRepository; -import LogITBackend.LogIT.web.dto.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserRequestDTO; import LogITBackend.LogIT.domain.Users; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; From 81fdb3e79ac35b18e64d46b2a0518a87d96bfe6b Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Mon, 31 Mar 2025 21:51:01 +0900 Subject: [PATCH 09/39] =?UTF-8?q?feat:=20#14=20Spring=20security=EC=99=80?= =?UTF-8?q?=20Jwt=EB=A5=BC=20=ED=99=9C=EC=9A=A9=ED=95=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8(=EC=9D=B8=EC=A6=9D)=20=EB=B0=8F=20=EC=9D=B8?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spring security를 활용해 로그인(인증)기능 구현 jwt생성 기능 구현 Spring security를 활용하여 jwt검증 기능(인가) 구현 --- build.gradle | 11 ++ .../LogIT/DTO/UserRequestDTO.java | 11 ++ .../LogIT/DTO/UserResponseDTO.java | 11 ++ .../LogIT/config/RedisConfig.java | 17 ++ .../CustomAuthenticationProvider.java | 29 ++++ .../config/security/CustomDetailsService.java | 8 + .../security/CustomUserDetailsService.java | 39 +++++ .../security/JwtAuthenticationFilter.java | 153 ++++++++++++++++ .../LogIT/config/security/SecurityConfig.java | 7 +- .../LogIT/controller/UserController.java | 12 ++ .../LogIT/converter/UserConverter.java | 9 + .../java/LogITBackend/LogIT/jwt/JwtUtils.java | 163 ++++++++++++++++++ .../exception/CustomExpiredJwtException.java | 16 ++ .../jwt/exception/CustomJwtException.java | 7 + .../LogIT/repository/UserRepository.java | 1 + .../LogIT/service/UserCommandService.java | 2 + .../LogIT/service/UserCommandServiceImpl.java | 50 ++++++ 17 files changed, 543 insertions(+), 3 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/config/RedisConfig.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/CustomAuthenticationProvider.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/CustomDetailsService.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/CustomUserDetailsService.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java create mode 100644 src/main/java/LogITBackend/LogIT/jwt/JwtUtils.java create mode 100644 src/main/java/LogITBackend/LogIT/jwt/exception/CustomExpiredJwtException.java create mode 100644 src/main/java/LogITBackend/LogIT/jwt/exception/CustomJwtException.java diff --git a/build.gradle b/build.gradle index fedd5f6..c8cb5ba 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,17 @@ dependencies { // spring security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + + //Jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // GSON + implementation 'com.google.code.gson:gson:2.11.0' + + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } tasks.named('test') { diff --git a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java index 66154a4..3b8e9b1 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java @@ -26,4 +26,15 @@ public static class SignUpRequestDTO { @NotBlank(message = "비밀번호는 빈값일 수 없습니다.") private String password; } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class SignInRequestDTO { + @NotBlank(message = "아이디는 빈값일 수 없습니다.") + private String username; + @NotBlank(message = "비밀번호는 빈값일 수 없습니다.") + private String password; + } } diff --git a/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java index 31f5225..7e0a025 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java @@ -19,4 +19,15 @@ public static class UserSignUpResultDTO { LoginType loginType; LocalDateTime createdAt; } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserSignInResultDTO { + String accessToken; + String refreshToken; + String nickname; + LocalDateTime createdAt; + } } diff --git a/src/main/java/LogITBackend/LogIT/config/RedisConfig.java b/src/main/java/LogITBackend/LogIT/config/RedisConfig.java new file mode 100644 index 0000000..3a0e277 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/RedisConfig.java @@ -0,0 +1,17 @@ +package LogITBackend.LogIT.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(connectionFactory); + return redisTemplate; + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/CustomAuthenticationProvider.java b/src/main/java/LogITBackend/LogIT/config/security/CustomAuthenticationProvider.java new file mode 100644 index 0000000..9851a60 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/CustomAuthenticationProvider.java @@ -0,0 +1,29 @@ +package LogITBackend.LogIT.config.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationProvider implements AuthenticationProvider { + + private final CustomDetailsService customDetailsSerivce; + + @Override + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + UserDetails userDetails = customDetailsSerivce.loadUserByUsername((String) authentication.getPrincipal(), (String) authentication.getCredentials()); + + return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities()); + } + + @Override + public boolean supports(Class authentication) { + return authentication.equals(UsernamePasswordAuthenticationToken.class); + } +} \ No newline at end of file diff --git a/src/main/java/LogITBackend/LogIT/config/security/CustomDetailsService.java b/src/main/java/LogITBackend/LogIT/config/security/CustomDetailsService.java new file mode 100644 index 0000000..2e1907e --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/CustomDetailsService.java @@ -0,0 +1,8 @@ +package LogITBackend.LogIT.config.security; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +public interface CustomDetailsService { + UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException; +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/CustomUserDetailsService.java b/src/main/java/LogITBackend/LogIT/config/security/CustomUserDetailsService.java new file mode 100644 index 0000000..c2716a9 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/CustomUserDetailsService.java @@ -0,0 +1,39 @@ +package LogITBackend.LogIT.config.security; + +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements CustomDetailsService { + + private final PasswordEncoder passwordEncoder; + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username, String password) throws UsernameNotFoundException { + Users user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("해당 아이디를 가진 유저가 존재하지 않습니다: " + username)); + + if (!passwordEncoder.matches(password, user.getPassword())) { + throw new BadCredentialsException("Password가 일치하지 않습니다."); + } + + User securityUser = new User( + user.getUsername(), + "", + Collections.emptyList() + ); + + return securityUser; + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java b/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f76b161 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java @@ -0,0 +1,153 @@ +package LogITBackend.LogIT.config.security; + +import LogITBackend.LogIT.jwt.JwtUtils; +import LogITBackend.LogIT.jwt.exception.CustomExpiredJwtException; +import LogITBackend.LogIT.jwt.exception.CustomJwtException; +import com.google.gson.Gson; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.PatternMatchUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Map; + +@RequiredArgsConstructor +@Component +@Slf4j //??? +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Value("${jwt.JWT_HEADER}") + private String jwtHeader; + private static final String[] whitelist = { + "/users/signup", + "/users/signup/**", + "/users/signin", + "/users/signin/**", + "/swagger-ui/**", + "/v3/**", + "/users/admin/**", +// "/refresh", "/", +// "/index.html" + }; + private final JwtUtils jwtUtils; + private final RedisTemplate redisTemplate; + + private static void checkAuthorizationHeader(String header) { + if(header == null) { + throw new CustomJwtException("토큰이 전달되지 않았습니다"); + } else if (!header.startsWith("Bearer ")) { + throw new CustomJwtException("Bearer 로 시작하지 않는 올바르지 않은 토큰 형식입니다"); + } + } + + // 필터를 거치지 않을 URL(로그인, 회원가입) 을 설정하고, true 를 return 하면 현재 필터를 건너뛰고 다음 필터로 이동 + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + String requestURI = request.getRequestURI(); + return PatternMatchUtils.simpleMatch(whitelist, requestURI); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authHeader = request.getHeader(jwtHeader); + + String requestUri = request.getRequestURI(); + String refreshToken = request.getHeader("refreshToken"); + + // 특정 경로에서만 Refresh Token 처리 + if ("/users/refresh".equals(requestUri)) { // if문 마지막에 return하지 말고 refresh token검증하는 로직을 service로 옮겨야 하나??? + if (refreshToken != null) { + try { + // access token 검증 + checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 + String token = JwtUtils.getTokenFromHeader(authHeader); + Claims claims = jwtUtils.validateTokenOnlySignature(token); // 토큰 검증 + Authentication authentication = jwtUtils.getAuthenticationFromExpiredAccessToken(token); // 사용자 인증 정보 생성 + SecurityContextHolder.getContext().setAuthentication(authentication); // 사용자 인증 정보 저장 + // refresh token 검증 + jwtUtils.validateRefreshToken(refreshToken); // 토큰 검증 + filterChain.doFilter(request, response); // 다음 필터로 이동 + } catch (Exception e) { + Gson gson = new Gson(); + String json = ""; + if (e instanceof CustomExpiredJwtException) { + json = gson.toJson(Map.of("Token_Expired", e.getMessage())); + } else { + json = gson.toJson(Map.of("error", e.getMessage())); + } + + response.setContentType("application/json; charset=UTF-8"); + PrintWriter printWriter = response.getWriter(); + printWriter.println(json); + printWriter.close(); + } + } + // refreshToken != null 처리해주어야함. + return; // return을 해주는게 맞나?? dofilter안해줘도 되나??(try에서 해주고 있어서 필요없어 보임.) 일단 /users/refresh로 요청 들어온 상황이면 아래 상황 필요없어서 return함. + } + + try { + log.info("------------------------------------------------------"); + checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 + String accessToken = JwtUtils.getTokenFromHeader(authHeader); + jwtUtils.validateToken(accessToken); // 토큰 검증 + jwtUtils.isTokenBlacklisted(authHeader); // 🚨 블랙리스트 확인 + } catch (Exception e) { + Gson gson = new Gson(); + String json = ""; + if (e instanceof CustomExpiredJwtException) { + json = gson.toJson(Map.of("Token_Expired", e.getMessage())); + } else { + json = gson.toJson(Map.of("error", e.getMessage())); + } + + response.setContentType("application/json; charset=UTF-8"); + PrintWriter printWriter = response.getWriter(); + printWriter.println(json); + printWriter.close(); + + return; + } + // accessToken != null 처리해주어야함. + + log.info("--------------------------- JwtVerifyFilter ---------------------------"); + + try { + checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 + String token = JwtUtils.getTokenFromHeader(authHeader); + jwtUtils.validateToken(token); // 토큰 검증 + jwtUtils.isExpired(token); // 토큰 만료 검증 + + Authentication authentication = jwtUtils.getAuthentication(token); // 사용자 인증 정보 생성 + log.info("authentication = {}", authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); // 다음 필터로 이동 + } catch (Exception e) { + Gson gson = new Gson(); + String json = ""; + if (e instanceof CustomExpiredJwtException) { + json = gson.toJson(Map.of("Token_Expired", e.getMessage())); + } else { + json = gson.toJson(Map.of("error", e.getMessage())); + } + + response.setContentType("application/json; charset=UTF-8"); + PrintWriter printWriter = response.getWriter(); + printWriter.println(json); + printWriter.close(); + } + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java index 9ed5750..238bab5 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -10,13 +10,14 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @EnableWebSecurity @Configuration @RequiredArgsConstructor public class SecurityConfig { -// private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -29,8 +30,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 Stateless로 설정 .authorizeHttpRequests((requests) -> requests - .requestMatchers("/**").permitAll()); -// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + .requestMatchers("/**").permitAll()) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/LogITBackend/LogIT/controller/UserController.java b/src/main/java/LogITBackend/LogIT/controller/UserController.java index 9c75dad..a2be584 100644 --- a/src/main/java/LogITBackend/LogIT/controller/UserController.java +++ b/src/main/java/LogITBackend/LogIT/controller/UserController.java @@ -34,4 +34,16 @@ public ApiResponse signUp( ) ); } + + @Operation(summary = "로그인", description = + "# 로그인 API 입니다. 아이디와 패스워드를 body에 입력해주세요." + ) + @PostMapping("/signin") + public ApiResponse signIn( + @RequestBody @Valid UserRequestDTO.SignInRequestDTO request + ) { + return ApiResponse.onSuccess( + userCommandService.signIn(request) + ); + } } diff --git a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java index 310434f..493ef33 100644 --- a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java +++ b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java @@ -22,4 +22,13 @@ public static UserResponseDTO.UserSignUpResultDTO toUserSignUpResultDTO(Users us .createdAt(users.getCreatedAt()) .build(); } + + public static UserResponseDTO.UserSignInResultDTO toUserSignInResultDTO(Users users, String accessToken, String refreshToken) { + return UserResponseDTO.UserSignInResultDTO.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .nickname(users.getNickname()) + .createdAt(users.getCreatedAt()) + .build(); + } } diff --git a/src/main/java/LogITBackend/LogIT/jwt/JwtUtils.java b/src/main/java/LogITBackend/LogIT/jwt/JwtUtils.java new file mode 100644 index 0000000..7c1515d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/jwt/JwtUtils.java @@ -0,0 +1,163 @@ +package LogITBackend.LogIT.jwt; + +import LogITBackend.LogIT.jwt.exception.CustomExpiredJwtException; +import LogITBackend.LogIT.jwt.exception.CustomJwtException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +@Component +@RequiredArgsConstructor +public class JwtUtils { + + @Value("${jwt.secret}") + public String secretKey; + private final RedisTemplate redisTemplate; + + // 헤더에 "Bearer XXX" 형식으로 담겨온 토큰을 추출한다 + public static String getTokenFromHeader(String header) { + return header.split(" ")[1]; + } + + public String generateToken(Map valueMap, int validTime) { // static 제거 + SecretKey key = null; + try { + key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } catch(Exception e){ + throw new RuntimeException(e.getMessage()); + } + return Jwts.builder() + .setHeader(Map.of("typ","JWT")) + .setClaims(valueMap) + .setIssuedAt(Date.from(ZonedDateTime.now().toInstant())) + .setExpiration(Date.from(ZonedDateTime.now().plusMinutes(validTime).toInstant())) + .signWith(key) + .compact(); + } + + public Authentication getAuthentication(String token) { // context에 넣을 Authentication를 jwt의 userId를 넣어 생성 // static 제거 + Map claims = validateToken(token); + System.out.println("userId type: " + (claims.get("userId") != null ? claims.get("userId").getClass().getName() : "null")); + +// String email = (String) claims.get("email"); + Long userId = ((Integer) claims.get("userId")).longValue(); + + return new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList()); + } + + public Map validateToken(String token) { // static 제거 + Map claim = null; + try { + SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + claim = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) // 파싱 및 검증, 실패 시 에러 + .getBody(); + } catch(ExpiredJwtException expiredJwtException){ + throw new CustomExpiredJwtException("access token이 만료되었습니다", expiredJwtException); + } catch(Exception e){ + throw new CustomJwtException("Error"); + } + return claim; + } + + public Authentication getAuthenticationFromExpiredAccessToken(String token) { // context에 넣을 Authentication를 jwt의 userId를 넣어 생성 // static 제거 + Map claims = validateTokenOnlySignature(token); + System.out.println("userId type: " + (claims.get("userId") != null ? claims.get("userId").getClass().getName() : "null")); + +// String email = (String) claims.get("email"); + Long userId = ((Integer) claims.get("userId")).longValue(); + + return new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList()); + } + + public Claims validateTokenOnlySignature(String token) { // static 제거 + Claims claims = null; + try { + SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) // 파싱 및 검증, 실패 시 에러 + .getBody(); + } catch(ExpiredJwtException expiredJwtException){ + return expiredJwtException.getClaims(); // ✅ 만료된 토큰에서도 Claims 추출 + } catch(Exception e){ + throw new CustomJwtException("Error access token 검증 못함"); + } + return claims; + } + + public void validateRefreshToken(String token) { // static 제거 + Map claim = null; + try { + SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + claim = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) // 파싱 및 검증, 실패 시 에러 + .getBody(); + } catch(ExpiredJwtException expiredJwtException){ + throw new CustomExpiredJwtException("refresh token이 만료되었습니다. 재로그인 해주세요.", expiredJwtException); + } catch(Exception e){ + throw new CustomJwtException("Error"); + } + } + + // 토큰이 만료되었는지 판단하는 메서드 + public boolean isExpired(String token) { // static 제거 + try { + validateToken(token); + } catch (Exception e) { + return (e instanceof CustomExpiredJwtException); + } + return false; + } + + // 토큰의 남은 만료시간 계산 + public long tokenRemainTimeSecond(String header) { // static 제거 + String accessToken = getTokenFromHeader(header); + SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(accessToken) + .getBody(); + + Date expDate = claims.getExpiration(); // 만료 시간 반환 (Date 타입) + long remainMs = expDate.getTime() - System.currentTimeMillis(); + return remainMs / 1000; + } + + // access token redis의 블랙리스트에서 확인 + public void isTokenBlacklisted(String accessToken) { + Set keys = redisTemplate.keys("blackList:*"); // "blackList:*" 패턴의 모든 Key 검색 + if (keys == null || keys.isEmpty()) { + return; // 블랙리스트가 비어있다면 return + } + + // 모든 Key에 대해 해당 Token이 Value로 존재하는지 확인 + for (String key : keys) { + String value = redisTemplate.opsForValue().get(key); + if (accessToken.equals(value)) { + throw new CustomJwtException("로그아웃된 유저입니다."); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/LogITBackend/LogIT/jwt/exception/CustomExpiredJwtException.java b/src/main/java/LogITBackend/LogIT/jwt/exception/CustomExpiredJwtException.java new file mode 100644 index 0000000..65b4a08 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/jwt/exception/CustomExpiredJwtException.java @@ -0,0 +1,16 @@ +package LogITBackend.LogIT.jwt.exception; + +import io.jsonwebtoken.ExpiredJwtException; + +public class CustomExpiredJwtException extends ExpiredJwtException { + private final String message; + + public CustomExpiredJwtException(String message, ExpiredJwtException source) { + super(source.getHeader(), source.getClaims(), source.getMessage()); + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/LogITBackend/LogIT/jwt/exception/CustomJwtException.java b/src/main/java/LogITBackend/LogIT/jwt/exception/CustomJwtException.java new file mode 100644 index 0000000..295ccc7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/jwt/exception/CustomJwtException.java @@ -0,0 +1,7 @@ +package LogITBackend.LogIT.jwt.exception; + +public class CustomJwtException extends RuntimeException { + public CustomJwtException(String msg) { + super(msg); + } +} diff --git a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java index 22623b5..ae4b28f 100644 --- a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -10,4 +10,5 @@ public interface UserRepository extends JpaRepository { @Override Optional findById(Long id); + Optional findByUsername(String username); } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java index af3600c..f40cd8a 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -1,8 +1,10 @@ package LogITBackend.LogIT.service; import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserResponseDTO; import LogITBackend.LogIT.domain.Users; public interface UserCommandService { Users signUp(UserRequestDTO.SignUpRequestDTO request); + UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDTO request); } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java index 28b4fa0..fdd1f2a 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -1,21 +1,41 @@ package LogITBackend.LogIT.service; +import LogITBackend.LogIT.DTO.UserResponseDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import LogITBackend.LogIT.apiPayload.exception.handler.ExceptionHandler; import LogITBackend.LogIT.converter.UserConverter; +import LogITBackend.LogIT.jwt.JwtUtils; import LogITBackend.LogIT.repository.UserRepository; import LogITBackend.LogIT.DTO.UserRequestDTO; import LogITBackend.LogIT.domain.Users; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + @Service @RequiredArgsConstructor @Slf4j public class UserCommandServiceImpl implements UserCommandService { + @Value("${jwt.ACCESS_EXP_TIME}") + private int accessExpTime; + @Value("${jwt.REFRESH_EXP_TIME}") + private int refreshExpTime; private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final AuthenticationManagerBuilder authenticationManagerBuilder; + private final JwtUtils jwtUtils; + private final RedisTemplate redisTemplate; @Override @Transactional // ??? @@ -24,4 +44,34 @@ public Users signUp(UserRequestDTO.SignUpRequestDTO request) { newUser.encodePassword(passwordEncoder.encode(request.getPassword())); return userRepository.save(newUser); } + + @Override + @Transactional // ??? + public UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDTO request) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); +// SecurityContextHolder.getContext().setAuthentication(authentication); // 로그인을 한 후 인증 정보를 사용할 일은 없을 것 같다. + + Users getUser = userRepository.findByUsername(authentication.getName()).orElseThrow(() -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); + String key = "users:" + getUser.getId().toString(); + String accessToken = generateAccessToken(getUser.getId(), accessExpTime); + String refreshToken = generateAndSaveRefreshToken(key, refreshExpTime); + + return UserConverter.toUserSignInResultDTO(getUser, accessToken, refreshToken); + } + + private String generateAccessToken(Long userId, int accessExpTime) { + // 인증 완료 후 jwt토큰(accessToken) 생성 + Map valueMap = Map.of( + "userId", userId // String으로 저장??? 그래서 SecurityUtil에서 Long으로 타입변환 해주나? + ); + return jwtUtils.generateToken(valueMap, accessExpTime); + } + + private String generateAndSaveRefreshToken(String key, int refreshExpTime) { + // 인증 완료 후 jwt토큰(refreshToken) 생성 + String refreshToken = jwtUtils.generateToken(Collections.emptyMap(), refreshExpTime); + redisTemplate.opsForValue().set(key, refreshToken, refreshExpTime, TimeUnit.MINUTES); + return refreshToken; + } } From c209e7e47e44a92958abbc42f37049e2226f738a Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Mon, 31 Mar 2025 22:01:54 +0900 Subject: [PATCH 10/39] =?UTF-8?q?feat:=20#16=20access=20token=EC=97=90?= =?UTF-8?q?=EC=84=9C=20userId=EC=B6=94=EC=B6=9C=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/config/security/SecurityUtil.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/config/security/SecurityUtil.java diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityUtil.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityUtil.java new file mode 100644 index 0000000..eea301d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityUtil.java @@ -0,0 +1,23 @@ +package LogITBackend.LogIT.config.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +@Slf4j +public class SecurityUtil { + + private SecurityUtil() { } + + // SecurityContext 에 유저 정보가 저장되는 시점 + // Request 가 들어올 때 JwtAuthenticationFilter 의 doFilterInternal 에서 저장 + public static Long getCurrentUserId() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || authentication.getName() == null) { + throw new RuntimeException("Security Context 에 인증 정보가 없습니다."); + } + + return Long.parseLong(authentication.getName()); + } +} From e26ffd0bb42c316eed017b501e8e14e377fc25a8 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 7 Apr 2025 19:41:20 +0900 Subject: [PATCH 11/39] =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/CodeRequestDTO.java | 30 +++++++++++++ .../LogIT/DTO/CodeResponseDTO.java | 25 +++++++++++ .../LogIT/controller/CategoryController.java | 4 +- .../LogIT/controller/CodeController.java | 23 ++++++---- .../java/LogITBackend/LogIT/domain/Codes.java | 6 +++ .../LogIT/repository/CategoryRepository.java | 7 ++-- .../LogIT/repository/CodeRepository.java | 11 +++++ .../LogIT/service/CategoryService.java | 2 +- .../LogIT/service/CategoryServiceImpl.java | 19 +++++---- .../LogIT/service/CodeService.java | 10 +++++ .../LogIT/service/CodeServiceImpl.java | 42 +++++++++++++++++++ 11 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CodeRequestDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CodeResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/CodeRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/service/CodeService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/CodeServiceImpl.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/CodeRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CodeRequestDTO.java new file mode 100644 index 0000000..914cb96 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CodeRequestDTO.java @@ -0,0 +1,30 @@ +package LogITBackend.LogIT.DTO; + +import LogITBackend.LogIT.domain.CodeCategories; +import LogITBackend.LogIT.domain.Codes; +import LogITBackend.LogIT.domain.Users; +import lombok.Getter; + +@Getter +public class CodeRequestDTO { + public String title; + public String filePath; + public int line; + public String content; + public String code; + public String category; + + public Codes toEntity(Users user, CodeCategories category) { + return Codes.builder() + .users(user) + .fileLocation(filePath) + .title(title) + .code(code) + .content(content) + .line(line) + .codeCategories(category) + .build(); + + + } +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/CodeResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CodeResponseDTO.java new file mode 100644 index 0000000..99ae970 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CodeResponseDTO.java @@ -0,0 +1,25 @@ +package LogITBackend.LogIT.DTO; + +import LogITBackend.LogIT.domain.Codes; +import lombok.Builder; + +@Builder +public class CodeResponseDTO { + public String title; + public String filePath; + public int line; + public String content; + public String code; + public String category; + + public static CodeResponseDTO toDTO(Codes codes) { + return CodeResponseDTO.builder() + .title(codes.getTitle()) + .content(codes.getContent()) + .line(codes.getLine()) + .filePath(codes.getFileLocation()) + .category(codes.getCodeCategories().getName()) + .code(codes.getCode()) + .build(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/controller/CategoryController.java b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java index a2d87fe..14a176d 100644 --- a/src/main/java/LogITBackend/LogIT/controller/CategoryController.java +++ b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java @@ -19,8 +19,8 @@ public class CategoryController { @GetMapping("") - public ResponseEntity>> getCategories() { - List categories = categoryService.getCategories(); + public ResponseEntity>> getCategories() { + List categories = categoryService.getCategories(); return ResponseEntity.ok(ApiResponse.onSuccess(categories)); } diff --git a/src/main/java/LogITBackend/LogIT/controller/CodeController.java b/src/main/java/LogITBackend/LogIT/controller/CodeController.java index 58f3162..d406636 100644 --- a/src/main/java/LogITBackend/LogIT/controller/CodeController.java +++ b/src/main/java/LogITBackend/LogIT/controller/CodeController.java @@ -1,18 +1,25 @@ package LogITBackend.LogIT.controller; +import LogITBackend.LogIT.DTO.CategoryResponseDTO; +import LogITBackend.LogIT.DTO.CodeRequestDTO; +import LogITBackend.LogIT.DTO.CodeResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.service.CodeService; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/codes") +@RequiredArgsConstructor public class CodeController { -// @PostMapping("") -// public ResponseEntity addCode(@RequestParam("code") String code) { -// return code; -// } + private final CodeService codeService; + + @PostMapping("") + public ResponseEntity> addCode(@RequestBody CodeRequestDTO request) { + CodeResponseDTO codeResponseDTO = codeService.addCode(request); + return ResponseEntity.ok(ApiResponse.onSuccess(codeResponseDTO)); + } } diff --git a/src/main/java/LogITBackend/LogIT/domain/Codes.java b/src/main/java/LogITBackend/LogIT/domain/Codes.java index 489ad86..3669d7c 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Codes.java +++ b/src/main/java/LogITBackend/LogIT/domain/Codes.java @@ -29,8 +29,14 @@ public class Codes extends BaseEntity { @Column(columnDefinition = "TEXT") private String content; + @Column(columnDefinition = "TEXT") + private String code; + private LocalDateTime date; + @Column(nullable = false) + private Integer line; + @Column(nullable = false, length = 50) private String fileLocation; diff --git a/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java index 6222298..7974560 100644 --- a/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java @@ -6,9 +6,10 @@ import org.springframework.lang.NonNullApi; import java.util.List; +import java.util.Optional; -public interface CategoryRepository extends JpaRepository { - - List findAll(); +public interface CategoryRepository extends JpaRepository { + List findAllByUsersId(@NotNull Long userId); CodeCategories save(CodeCategories category); + Optional findByUsersIdAndName(Long userId, String name); } diff --git a/src/main/java/LogITBackend/LogIT/repository/CodeRepository.java b/src/main/java/LogITBackend/LogIT/repository/CodeRepository.java new file mode 100644 index 0000000..efac972 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CodeRepository.java @@ -0,0 +1,11 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.CodeCategories; +import LogITBackend.LogIT.domain.Codes; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CodeRepository extends JpaRepository { + +} diff --git a/src/main/java/LogITBackend/LogIT/service/CategoryService.java b/src/main/java/LogITBackend/LogIT/service/CategoryService.java index 442269d..535f308 100644 --- a/src/main/java/LogITBackend/LogIT/service/CategoryService.java +++ b/src/main/java/LogITBackend/LogIT/service/CategoryService.java @@ -6,7 +6,7 @@ import java.util.List; public interface CategoryService { - List getCategories(); + List getCategories(); CategoryResponseDTO createCategory(CategoryRequestDTO request); } diff --git a/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java index c6d6a5c..aeacddf 100644 --- a/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java @@ -4,6 +4,7 @@ import LogITBackend.LogIT.DTO.CategoryResponseDTO; import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; import LogITBackend.LogIT.apiPayload.exception.GeneralException; +import LogITBackend.LogIT.config.security.SecurityUtil; import LogITBackend.LogIT.domain.CodeCategories; import LogITBackend.LogIT.domain.Users; import LogITBackend.LogIT.repository.CategoryRepository; @@ -22,25 +23,25 @@ public class CategoryServiceImpl implements CategoryService { private final UserRepository userRepository; @Override - public List getCategories() { - List categories = categoryRepository.findAll(); + public List getCategories() { + Long userId = SecurityUtil.getCurrentUserId(); + + List categories = categoryRepository.findAllByUsersId(userId); if (categories.isEmpty()) { throw new GeneralException(ErrorStatus.CATEGORY_NOT_FOUND); } return categories.stream() - .map(category -> CategoryResponseDTO.builder() - .category(category.getName()) - .build()) + .map(CodeCategories::getName) .toList(); } @Override public CategoryResponseDTO createCategory(CategoryRequestDTO request) { - Users dummyUser = userRepository.findById(1L) + Long userId = SecurityUtil.getCurrentUserId(); + Users user = userRepository.findById(userId) .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); - System.out.println("dummyUser = " + dummyUser.getName()); - CodeCategories category = request.ToEntity(dummyUser); - System.out.println("category.getName() = " + category.getName()); + + CodeCategories category = request.ToEntity(user); return CategoryResponseDTO.ToDTO(categoryRepository.save(category)); } } diff --git a/src/main/java/LogITBackend/LogIT/service/CodeService.java b/src/main/java/LogITBackend/LogIT/service/CodeService.java new file mode 100644 index 0000000..5a5a54b --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/CodeService.java @@ -0,0 +1,10 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CodeRequestDTO; +import LogITBackend.LogIT.DTO.CodeResponseDTO; +import LogITBackend.LogIT.domain.Codes; +import org.springframework.web.bind.annotation.RequestBody; + +public interface CodeService { + CodeResponseDTO addCode(@RequestBody CodeRequestDTO request); +} diff --git a/src/main/java/LogITBackend/LogIT/service/CodeServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/CodeServiceImpl.java new file mode 100644 index 0000000..cedc5b5 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/CodeServiceImpl.java @@ -0,0 +1,42 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CategoryResponseDTO; +import LogITBackend.LogIT.DTO.CodeRequestDTO; +import LogITBackend.LogIT.DTO.CodeResponseDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import LogITBackend.LogIT.apiPayload.exception.GeneralException; +import LogITBackend.LogIT.config.security.SecurityUtil; +import LogITBackend.LogIT.domain.CodeCategories; +import LogITBackend.LogIT.domain.Codes; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.CategoryRepository; +import LogITBackend.LogIT.repository.CodeRepository; +import LogITBackend.LogIT.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CodeServiceImpl implements CodeService { + + private final CodeRepository codeRepository; + private final UserRepository userRepository; + private final CategoryRepository categoryRepository; + + + @Override + @Transactional + public CodeResponseDTO addCode(CodeRequestDTO request) { + Long userId = SecurityUtil.getCurrentUserId(); + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + CodeCategories category = categoryRepository.findByUsersIdAndName(userId, request.getCategory()) + .orElseThrow(() -> new GeneralException(ErrorStatus.CATEGORY_NOT_FOUND)); + + Codes code = request.toEntity(user, category); + + return CodeResponseDTO.toDTO(codeRepository.save(code)); + } +} From 4334a2b077639b5a6dfb9603eb18164792dcca69 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 7 Apr 2025 22:19:03 +0900 Subject: [PATCH 12/39] =?UTF-8?q?github=EA=B4=80=EB=A0=A8=20entity=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/domain/Commit.java | 33 +++++++++++++++++++ .../LogIT/domain/CommitParent.java | 23 +++++++++++++ .../java/LogITBackend/LogIT/domain/File.java | 28 ++++++++++++++++ .../java/LogITBackend/LogIT/domain/Users.java | 3 ++ 4 files changed, 87 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/domain/Commit.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/CommitParent.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/File.java diff --git a/src/main/java/LogITBackend/LogIT/domain/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java new file mode 100644 index 0000000..7f37c92 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -0,0 +1,33 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "Commits") +public class Commit extends BaseEntity { + + @Id + @Column(length = 40) + private String id; // commit SHA + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private Users user; + + @Column(length = 50) + private String message; + + @Column(length = 100) + private String stats; + + private LocalDateTime date; + + @OneToMany(mappedBy = "commit", cascade = CascadeType.ALL) + private List files = new ArrayList<>(); + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/CommitParent.java b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java new file mode 100644 index 0000000..24f4e2d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java @@ -0,0 +1,23 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; + +@Entity +@Table(name = "commit_parents", + uniqueConstraints = @UniqueConstraint(columnNames = {"commit_id", "parent_id"})) +public class CommitParent extends BaseEntity { + + @Id // 단일 id 방식 + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commit_id") + private Commit commit; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Commit parent; + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/File.java b/src/main/java/LogITBackend/LogIT/domain/File.java new file mode 100644 index 0000000..f929bc8 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/File.java @@ -0,0 +1,28 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "files") +public class File extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commit_id", nullable = false) + private Commit commit; + + @Column(length = 255) + private String filename; + + private Long additions; + + private Long deletions; + + @Column(columnDefinition = "TEXT") + private String patch; +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 2d2631b..9430d6d 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -49,6 +49,9 @@ public class Users extends BaseEntity { @Column(columnDefinition = "TEXT") private String profileImage; + @Column(length = 50) + private String githubNickname; + @Enumerated(EnumType.STRING) @Column(columnDefinition = "VARCHAR(10)") private Gender gender; From c06d7a4a85a40ca72864104d61e33584029d262f Mon Sep 17 00:00:00 2001 From: mino Date: Tue, 8 Apr 2025 23:11:03 +0900 Subject: [PATCH 13/39] =?UTF-8?q?git=20commit-list=20db=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/CommitResponseDTO.java | 19 ++++ .../LogIT/controller/CommitController.java | 29 ++++++ .../LogITBackend/LogIT/domain/Commit.java | 9 +- .../LogIT/repository/CommitRepository.java | 8 ++ .../LogIT/service/GithubService.java | 10 ++ .../LogIT/service/GithubServiceImpl.java | 96 +++++++++++++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/controller/CommitController.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/CommitRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/service/GithubService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java new file mode 100644 index 0000000..a2b313a --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java @@ -0,0 +1,19 @@ +package LogITBackend.LogIT.DTO; + + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommitResponseDTO { + private String id; //Commit id + private Long user_id; + private String message; + private String stats; + private LocalDateTime date; +} diff --git a/src/main/java/LogITBackend/LogIT/controller/CommitController.java b/src/main/java/LogITBackend/LogIT/controller/CommitController.java new file mode 100644 index 0000000..6b857b3 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/CommitController.java @@ -0,0 +1,29 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.service.GithubService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/githubs") +public class CommitController { + + private final GithubService githubService; + + @GetMapping("/commits") + public ResponseEntity> getCommits( + @RequestParam String owner, + @RequestParam String repo, + @RequestParam String token + ) { + List commits = githubService.getInitialCommits(owner, repo, token); + return ResponseEntity.ok(ApiResponse.onSuccess(commits)); + } + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java index 7f37c92..412f429 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Commit.java +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -1,7 +1,11 @@ package LogITBackend.LogIT.domain; import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.annotation.Nonnull; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.ArrayList; @@ -9,6 +13,9 @@ @Entity @Table(name = "Commits") +@Getter +@AllArgsConstructor +@NoArgsConstructor public class Commit extends BaseEntity { @Id @@ -19,7 +26,7 @@ public class Commit extends BaseEntity { @JoinColumn(name = "user_id", nullable = false) private Users user; - @Column(length = 50) + @Column(length = 255) private String message; @Column(length = 100) diff --git a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java new file mode 100644 index 0000000..2d2a21f --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java @@ -0,0 +1,8 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Commit; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommitRepository extends JpaRepository { + +} diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java new file mode 100644 index 0000000..1cc7095 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -0,0 +1,10 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CommitResponseDTO; + +import java.util.List; + +public interface GithubService { + List getInitialCommits(String owner, String repo, String accessToken); + void getCommitsFromWebhook(String payload); +} diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java new file mode 100644 index 0000000..970ea65 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -0,0 +1,96 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.domain.Commit; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.domain.enums.Gender; +import LogITBackend.LogIT.domain.enums.LoginType; +import LogITBackend.LogIT.domain.enums.UserStatus; +import LogITBackend.LogIT.repository.CommitRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class GithubServiceImpl implements GithubService { + + private final CommitRepository commitRepository; + + @Override + public List getInitialCommits(String owner, String repo, String accessToken) { + String url = String.format("https://api.github.com/repos/%s/%s/commits", owner, repo); + + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.set("Accept", "application/vnd.github+json"); + headers.set("X-GitHub-Api-Version", "2022-11-28"); + + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + + Users dummyUser = Users.builder() + .id(1L) // DB에 실제 저장하지 않아도 됨, 그냥 임의의 ID + .nickname("dummy01") + .username("dummyuser") + .password("dummy1234!") // 사용 안 하므로 무관 + .loginType(LoginType.REGULAR) + .status(UserStatus.ACTIVE) + .githubNickname("dummyGithub") + .email("dummy@example.com") + .gender(Gender.MALE) + .build(); + + ResponseEntity>> response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + new ParameterizedTypeReference>>() {} + ); + + List> body = response.getBody(); + if (body == null) return Collections.emptyList(); + + List savedCommits = body.stream().map(item -> { + String sha = (String) item.get("sha"); + Map commit = (Map) item.get("commit"); + String message = (String) commit.get("message"); + Map author = (Map) commit.get("author"); + String dateStr = (String) author.get("date"); + LocalDateTime date = LocalDateTime.parse(dateStr.replace("Z", "")); + + return new Commit( + sha, + dummyUser, + message, + null, // stats 필드는 이후에 계산할 수 있음 + date, + null + ); + }).collect(Collectors.toList()); + + commitRepository.saveAll(savedCommits); + + return savedCommits.stream() + .map(c -> new CommitResponseDTO(c.getId(), c.getUser().getId() ,c.getMessage(), c.getStats(), c.getDate())) + .collect(Collectors.toList()); + + } + + @Override + public void getCommitsFromWebhook(String payload) { + + } +} From 71a6e45fafcc22f4ae52c214f71d1506d2bd71fe Mon Sep 17 00:00:00 2001 From: mino Date: Wed, 9 Apr 2025 14:19:40 +0900 Subject: [PATCH 14/39] =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EB=B6=80=EB=AA=A8-?= =?UTF-8?q?=EC=9E=90=EC=8B=9D=20=EA=B4=80=EA=B3=84=20=EC=82=BD=EC=9E=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/domain/CommitParent.java | 8 +++++ .../repository/CommitParentRepository.java | 8 +++++ .../LogIT/service/GithubServiceImpl.java | 35 +++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/repository/CommitParentRepository.java diff --git a/src/main/java/LogITBackend/LogIT/domain/CommitParent.java b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java index 24f4e2d..e8e5f5b 100644 --- a/src/main/java/LogITBackend/LogIT/domain/CommitParent.java +++ b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java @@ -2,10 +2,18 @@ import LogITBackend.LogIT.domain.common.BaseEntity; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @Table(name = "commit_parents", uniqueConstraints = @UniqueConstraint(columnNames = {"commit_id", "parent_id"})) +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class CommitParent extends BaseEntity { @Id // 단일 id 방식 diff --git a/src/main/java/LogITBackend/LogIT/repository/CommitParentRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitParentRepository.java new file mode 100644 index 0000000..4a5254a --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CommitParentRepository.java @@ -0,0 +1,8 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.CommitParent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommitParentRepository extends JpaRepository { + +} diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 970ea65..5279a33 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -2,10 +2,12 @@ import LogITBackend.LogIT.DTO.CommitResponseDTO; import LogITBackend.LogIT.domain.Commit; +import LogITBackend.LogIT.domain.CommitParent; import LogITBackend.LogIT.domain.Users; import LogITBackend.LogIT.domain.enums.Gender; import LogITBackend.LogIT.domain.enums.LoginType; import LogITBackend.LogIT.domain.enums.UserStatus; +import LogITBackend.LogIT.repository.CommitParentRepository; import LogITBackend.LogIT.repository.CommitRepository; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; @@ -20,6 +22,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @Service @@ -27,6 +30,7 @@ public class GithubServiceImpl implements GithubService { private final CommitRepository commitRepository; + private final CommitParentRepository commitParentRepository; @Override public List getInitialCommits(String owner, String repo, String accessToken) { @@ -71,6 +75,8 @@ public List getInitialCommits(String owner, String repo, Stri String dateStr = (String) author.get("date"); LocalDateTime date = LocalDateTime.parse(dateStr.replace("Z", "")); + + return new Commit( sha, dummyUser, @@ -83,6 +89,35 @@ public List getInitialCommits(String owner, String repo, Stri commitRepository.saveAll(savedCommits); + // 커밋을 SHA 기준으로 Map으로 변환 (List 참조 대신 성능 개선함) + Map shaToCommitMap = savedCommits.stream() + .collect(Collectors.toMap(Commit::getId, c -> c)); + + List savedParents = body.stream() + .flatMap(item -> { + String childSha = (String) item.get("sha"); + Commit child = shaToCommitMap.get(childSha); + List> parents = (List>) item.get("parents"); + + return parents.stream() + .map(parentMap -> { + String parentSha = (String) parentMap.get("sha"); + Commit parent = shaToCommitMap.get(parentSha); + if (child != null && parent != null) { + CommitParent cp = new CommitParent(); + cp.setCommit(child); + cp.setParent(parent); + return cp; + } + return null; + }) + .filter(Objects::nonNull); + }) + .collect(Collectors.toList()); + + commitParentRepository.saveAll(savedParents); + + return savedCommits.stream() .map(c -> new CommitResponseDTO(c.getId(), c.getUser().getId() ,c.getMessage(), c.getStats(), c.getDate())) .collect(Collectors.toList()); From 0cf7b0454feb7a6652765fe488e971e4ce6ba6f9 Mon Sep 17 00:00:00 2001 From: mino Date: Sat, 12 Apr 2025 21:46:57 +0900 Subject: [PATCH 15/39] =?UTF-8?q?commit=20detail=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/CommitDetailResponseDTO.java | 15 ++++ .../LogIT/DTO/CommitResponseDTO.java | 13 ++++ .../LogIT/DTO/FileResponseDTO.java | 40 +++++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 +- .../LogIT/controller/CommitController.java | 29 -------- .../LogIT/controller/GithubController.java | 42 +++++++++++ .../LogITBackend/LogIT/domain/Commit.java | 4 ++ .../java/LogITBackend/LogIT/domain/File.java | 10 +++ .../LogIT/repository/FileRepository.java | 14 ++++ .../LogIT/service/GithubService.java | 3 + .../LogIT/service/GithubServiceImpl.java | 72 +++++++++++++++++-- 11 files changed, 213 insertions(+), 34 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/CommitDetailResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/DTO/FileResponseDTO.java delete mode 100644 src/main/java/LogITBackend/LogIT/controller/CommitController.java create mode 100644 src/main/java/LogITBackend/LogIT/controller/GithubController.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/FileRepository.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/CommitDetailResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CommitDetailResponseDTO.java new file mode 100644 index 0000000..2205ee7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitDetailResponseDTO.java @@ -0,0 +1,15 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CommitDetailResponseDTO { + private CommitResponseDTO commitResponseDTO; + private List files; +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java index a2b313a..f0c59fe 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java @@ -1,11 +1,14 @@ package LogITBackend.LogIT.DTO; +import LogITBackend.LogIT.domain.Commit; +import LogITBackend.LogIT.domain.File; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; @Data @AllArgsConstructor @@ -16,4 +19,14 @@ public class CommitResponseDTO { private String message; private String stats; private LocalDateTime date; + + public static CommitResponseDTO fromEntity(Commit commit) { + return new CommitResponseDTO( + commit.getId(), + commit.getUser().getId(), + commit.getMessage(), + commit.getStats(), + commit.getDate() + ); + } } diff --git a/src/main/java/LogITBackend/LogIT/DTO/FileResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/FileResponseDTO.java new file mode 100644 index 0000000..4929b57 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/FileResponseDTO.java @@ -0,0 +1,40 @@ +package LogITBackend.LogIT.DTO; + +import LogITBackend.LogIT.domain.File; +import jakarta.persistence.Column; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.LastModifiedDate; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FileResponseDTO { + private Long id; + + private String filename; + + private Long additions; + + private Long deletions; + + private String patch; + + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public static FileResponseDTO fromEntity(File file) { + return new FileResponseDTO( + file.getId(), + file.getFilename(), + file.getAdditions(), + file.getDeletions(), + file.getPatch(), + file.getCreatedAt(), + file.getUpdatedAt() + ); + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index e9e9d1f..cb30d5a 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -27,7 +27,10 @@ public enum ErrorStatus implements BaseErrorCode { SAME_PASSWORD(HttpStatus.BAD_REQUEST, "USER_1008", "이전 비밀번호와 동일합니다."), // 카테고리 관련 응답 2000 - CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_1001", "카테고리가 존재하지 않습니다."); + CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_2001", "카테고리가 존재하지 않습니다."), + + // commit 관련 응답 3000 + COMMIT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMIT_3001", "커밋이 존재하지 않습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/LogITBackend/LogIT/controller/CommitController.java b/src/main/java/LogITBackend/LogIT/controller/CommitController.java deleted file mode 100644 index 6b857b3..0000000 --- a/src/main/java/LogITBackend/LogIT/controller/CommitController.java +++ /dev/null @@ -1,29 +0,0 @@ -package LogITBackend.LogIT.controller; - -import LogITBackend.LogIT.DTO.CommitResponseDTO; -import LogITBackend.LogIT.apiPayload.ApiResponse; -import LogITBackend.LogIT.service.GithubService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/githubs") -public class CommitController { - - private final GithubService githubService; - - @GetMapping("/commits") - public ResponseEntity> getCommits( - @RequestParam String owner, - @RequestParam String repo, - @RequestParam String token - ) { - List commits = githubService.getInitialCommits(owner, repo, token); - return ResponseEntity.ok(ApiResponse.onSuccess(commits)); - } - -} diff --git a/src/main/java/LogITBackend/LogIT/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java new file mode 100644 index 0000000..8e2bc4e --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -0,0 +1,42 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; +import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.service.GithubService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/githubs") +public class GithubController { + + private final GithubService githubService; + + @GetMapping("/{owners}/{repos}/commits") + public ResponseEntity> getCommits( + @PathVariable("owners") String owners, + @PathVariable("repos") String repos, + @RequestParam String token + ) { + List commits = githubService.getInitialCommits(owners, repos, token); + return ResponseEntity.ok(ApiResponse.onSuccess(commits)); + } + + @GetMapping("/{owners}/{repos}/commits/{id}/details") + public ResponseEntity> getCommitDetails( + @PathVariable("owners") String owners, + @PathVariable("repos") String repos, + @PathVariable("id") String commitId, + @RequestParam String token + ) { + + CommitDetailResponseDTO commitDetail = githubService.getCommitDetails(owners, repos, commitId, token); + return ResponseEntity.ok(ApiResponse.onSuccess(commitDetail)); + } + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java index 412f429..ab08502 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Commit.java +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -1,11 +1,13 @@ package LogITBackend.LogIT.domain; import LogITBackend.LogIT.domain.common.BaseEntity; +import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.annotation.Nonnull; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDateTime; import java.util.ArrayList; @@ -14,6 +16,7 @@ @Entity @Table(name = "Commits") @Getter +@Setter @AllArgsConstructor @NoArgsConstructor public class Commit extends BaseEntity { @@ -35,6 +38,7 @@ public class Commit extends BaseEntity { private LocalDateTime date; @OneToMany(mappedBy = "commit", cascade = CascadeType.ALL) + @JsonManagedReference private List files = new ArrayList<>(); } diff --git a/src/main/java/LogITBackend/LogIT/domain/File.java b/src/main/java/LogITBackend/LogIT/domain/File.java index f929bc8..8befa2a 100644 --- a/src/main/java/LogITBackend/LogIT/domain/File.java +++ b/src/main/java/LogITBackend/LogIT/domain/File.java @@ -1,12 +1,21 @@ package LogITBackend.LogIT.domain; import LogITBackend.LogIT.domain.common.BaseEntity; +import com.fasterxml.jackson.annotation.JsonBackReference; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDateTime; @Entity @Table(name = "files") +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor public class File extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -14,6 +23,7 @@ public class File extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "commit_id", nullable = false) + @JsonBackReference private Commit commit; @Column(length = 255) diff --git a/src/main/java/LogITBackend/LogIT/repository/FileRepository.java b/src/main/java/LogITBackend/LogIT/repository/FileRepository.java new file mode 100644 index 0000000..daad1ef --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/FileRepository.java @@ -0,0 +1,14 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.File; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FileRepository extends JpaRepository { + @Query("SELECT f FROM File f WHERE f.commit.id = :commitId") + List findAllByCommitId(@Param("commitId") String commitId); + +} diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index 1cc7095..7daea4b 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -1,5 +1,6 @@ package LogITBackend.LogIT.service; +import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; import java.util.List; @@ -7,4 +8,6 @@ public interface GithubService { List getInitialCommits(String owner, String repo, String accessToken); void getCommitsFromWebhook(String payload); + + CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId, String token); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 5279a33..29d245b 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -1,14 +1,20 @@ package LogITBackend.LogIT.service; +import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.DTO.FileResponseDTO; +import LogITBackend.LogIT.apiPayload.exception.GeneralException; import LogITBackend.LogIT.domain.Commit; import LogITBackend.LogIT.domain.CommitParent; +import LogITBackend.LogIT.domain.File; import LogITBackend.LogIT.domain.Users; import LogITBackend.LogIT.domain.enums.Gender; import LogITBackend.LogIT.domain.enums.LoginType; import LogITBackend.LogIT.domain.enums.UserStatus; import LogITBackend.LogIT.repository.CommitParentRepository; import LogITBackend.LogIT.repository.CommitRepository; +import LogITBackend.LogIT.repository.FileRepository; +import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; @@ -16,23 +22,25 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; +import static LogITBackend.LogIT.apiPayload.code.status.ErrorStatus.COMMIT_NOT_FOUND; + @Service @RequiredArgsConstructor public class GithubServiceImpl implements GithubService { private final CommitRepository commitRepository; private final CommitParentRepository commitParentRepository; + private final FileRepository fileRepository; @Override + @Transactional public List getInitialCommits(String owner, String repo, String accessToken) { String url = String.format("https://api.github.com/repos/%s/%s/commits", owner, repo); @@ -128,4 +136,60 @@ public List getInitialCommits(String owner, String repo, Stri public void getCommitsFromWebhook(String payload) { } + + @Override + @Transactional + public CommitDetailResponseDTO getCommitDetails(String owner, String repo, String commitId, String token) { + Commit commit = commitRepository.findById(commitId) + .orElseThrow(() -> new GeneralException(COMMIT_NOT_FOUND)); + + // stats가 null이면 GitHub에서 정보 요청 + if (commit.getStats() == null) { + RestTemplate restTemplate = new RestTemplate(); + + // GitHub API 호출 + String url = String.format("https://api.github.com/repos/%s/%s/commits/%s", owner, repo, commitId); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + token); + headers.set("Accept", "application/vnd.github+json"); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, JsonNode.class); + JsonNode body = response.getBody(); + + // stats 정보 세팅 + JsonNode statsNode = body.get("stats"); + if (statsNode != null) { + int additions = statsNode.get("additions").asInt(); + int deletions = statsNode.get("deletions").asInt(); + int total = statsNode.get("total").asInt(); + String statsString = String.format("%d additions, %d deletions (total: %d)", additions, deletions, total); + commit.setStats(statsString); + } + // files 저장 + List fileList = new ArrayList<>(); + for (JsonNode fileNode : body.get("files")) { + File file = new File(); + file.setCommit(commit); + file.setFilename(fileNode.get("filename").asText()); + file.setAdditions(fileNode.get("additions").asLong()); + file.setDeletions(fileNode.get("deletions").asLong()); + file.setPatch(fileNode.has("patch") ? fileNode.get("patch").asText() : null); + fileList.add(file); + } + fileRepository.saveAll(fileList); + + // 업데이트 저장 + commitRepository.save(commit); + } + + List files = fileRepository.findAllByCommitId(commitId); + + List fileResponses = files.stream() + .map(FileResponseDTO::fromEntity) + .collect(Collectors.toList()); + + CommitResponseDTO commitResponseDTO = CommitResponseDTO.fromEntity(commit); + return new CommitDetailResponseDTO(commitResponseDTO, fileResponses); + } } From 06784b54929c128a612034fd00a6c941687a869e Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Sat, 12 Apr 2025 22:08:14 +0900 Subject: [PATCH 16/39] =?UTF-8?q?feat:=20#18=20=EA=B9=83=ED=97=88=EB=B8=8C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프론트엔드에서 받은 authorization code를 통해 accesstoken를 받아오고 accesstoken으로 깃허브 리소스 서버에서 유저 정보 받아오기, 이는 모두 Spring Security로 처리 받아온 유저 정보로 회원가입 로그인 처리 회원가입 시 유저의 고유ID(providerId), 닉네임, PAT(깃허브가 제공하는 토큰) 자체 db에 저장 --- build.gradle | 1 + .../LogIT/DTO/UserRequestDTO.java | 10 +++ .../security/JwtAuthenticationFilter.java | 8 +- .../security/PasswordEncoderConfig.java | 14 ++++ .../LogIT/config/security/SecurityConfig.java | 23 ++++-- .../oauth/CustomAuthExceptionHandler.java | 25 ++++++ .../oauth/CustomOAuth2SuccessHandler.java | 82 +++++++++++++++++++ .../oauth/CustomOAuth2UserService.java | 59 +++++++++++++ .../LogIT/converter/UserConverter.java | 7 ++ .../java/LogITBackend/LogIT/domain/Users.java | 10 +++ .../LogIT/repository/UserRepository.java | 1 + .../LogIT/service/UserCommandService.java | 2 + .../LogIT/service/UserCommandServiceImpl.java | 4 +- 13 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/config/security/PasswordEncoderConfig.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/oauth/CustomAuthExceptionHandler.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java create mode 100644 src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java diff --git a/build.gradle b/build.gradle index c8cb5ba..edbe9ce 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,7 @@ dependencies { // spring security implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' //Jwt implementation 'io.jsonwebtoken:jjwt-api:0.11.5' diff --git a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java index 3b8e9b1..70eb8bb 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java @@ -37,4 +37,14 @@ public static class SignInRequestDTO { @NotBlank(message = "비밀번호는 빈값일 수 없습니다.") private String password; } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class GithubSignUpRequestDTO { + private String providerId; + private String nickname; + private String githubAccessToken; + } } diff --git a/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java b/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java index f76b161..33d00d9 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java @@ -37,7 +37,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/users/signin/**", "/swagger-ui/**", "/v3/**", - "/users/admin/**", + "/users/admin/**" +// "/**" // "/refresh", "/", // "/index.html" }; @@ -45,6 +46,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final RedisTemplate redisTemplate; private static void checkAuthorizationHeader(String header) { + log.info("-------------------#@@@@@------------------"); if(header == null) { throw new CustomJwtException("토큰이 전달되지 않았습니다"); } else if (!header.startsWith("Bearer ")) { @@ -56,6 +58,9 @@ private static void checkAuthorizationHeader(String header) { @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { String requestURI = request.getRequestURI(); + log.info("---------------$$$$$$------------------"); + log.info("boolean: {}", PatternMatchUtils.simpleMatch(whitelist, requestURI)); + log.info("requestURI: {}", requestURI); return PatternMatchUtils.simpleMatch(whitelist, requestURI); } @@ -104,6 +109,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String accessToken = JwtUtils.getTokenFromHeader(authHeader); jwtUtils.validateToken(accessToken); // 토큰 검증 jwtUtils.isTokenBlacklisted(authHeader); // 🚨 블랙리스트 확인 + log.info("------------------------------------------------------"); } catch (Exception e) { Gson gson = new Gson(); String json = ""; diff --git a/src/main/java/LogITBackend/LogIT/config/security/PasswordEncoderConfig.java b/src/main/java/LogITBackend/LogIT/config/security/PasswordEncoderConfig.java new file mode 100644 index 0000000..1763366 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/PasswordEncoderConfig.java @@ -0,0 +1,14 @@ +package LogITBackend.LogIT.config.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java index 238bab5..cb85ba6 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -1,8 +1,13 @@ package LogITBackend.LogIT.config.security; +import LogITBackend.LogIT.config.security.oauth.CustomAuthExceptionHandler; +import LogITBackend.LogIT.config.security.oauth.CustomOAuth2SuccessHandler; +import LogITBackend.LogIT.config.security.oauth.CustomOAuth2UserService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -15,9 +20,13 @@ @EnableWebSecurity @Configuration @RequiredArgsConstructor +@Slf4j public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final CustomOAuth2UserService customOAuth2UserService; + private final CustomOAuth2SuccessHandler customOAuth2SuccessHandler; + private final CustomAuthExceptionHandler customAuthExceptionHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -31,13 +40,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 Stateless로 설정 .authorizeHttpRequests((requests) -> requests .requestMatchers("/**").permitAll()) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - + .oauth2Login(oauth -> oauth + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + ) + .successHandler(customOAuth2SuccessHandler) + .failureHandler(customAuthExceptionHandler) + ); return http.build(); } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } } diff --git a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomAuthExceptionHandler.java b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomAuthExceptionHandler.java new file mode 100644 index 0000000..fd7dde6 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomAuthExceptionHandler.java @@ -0,0 +1,25 @@ +package LogITBackend.LogIT.config.security.oauth; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +@Slf4j +public class CustomAuthExceptionHandler implements AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + // 여기에 로그인 실패 후 처리할 내용을 작성하기! +// response.sendRedirect("/login-failure"); + log.info("Fail!----------------"); + log.info("❌ OAuth2 로그인 실패: {}", exception.getMessage()); // ✅ 여기! + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java new file mode 100644 index 0000000..69318e2 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java @@ -0,0 +1,82 @@ +package LogITBackend.LogIT.config.security.oauth; + +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.jwt.JwtUtils; +import LogITBackend.LogIT.repository.UserRepository; +import LogITBackend.LogIT.service.UserCommandService; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; + +@Component +@Slf4j +@RequiredArgsConstructor +public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler { + + @Value("${jwt.REDIRECT_URI}") + private String redirectUri; // 프론트엔드로 Jwt 토큰을 리다이렉트할 URI + @Value("${jwt.ACCESS_EXP_TIME}") + private int accessExpTime; + @Value("${jwt.REFRESH_EXP_TIME}") + private int refreshExpTime; + private final UserRepository userRepository; + private final UserCommandService userCommandService; + private final JwtUtils jwtUtils; + private final RedisTemplate redisTemplate; + + @Override + @Transactional + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + // 여기에 로그인 성공 후 처리할 내용을 작성하기! + DefaultOAuth2User oAuth2User = (DefaultOAuth2User) authentication.getPrincipal(); + log.info("Suceess!---------------------"); + + // 프론트엔드에 보내줄 accessToken 및 refreshToken생성 + Users getUser = userRepository.findByProviderId(oAuth2User.getName()).orElseThrow(null); + String accessToken = userCommandService.generateAccessToken(getUser.getId(), accessExpTime); + String key = "users:" + getUser.getId().toString(); + String refreshToken = userCommandService.generateAndSaveRefreshToken(key, refreshExpTime); + + // db에 인가처리할 accessToken저장 (개발자 테스트용) + getUser.updateAccessToken(accessToken); + + // 프론트엔드에 accessToken과 refreshToken를 redirect url로 보내주기 + // ✅ refreshToken 쿠키에 저장 + Cookie cookie = new Cookie("refreshToken", refreshToken); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setMaxAge(7 * 24 * 60 * 60 * 2); // 14일 + response.addCookie(cookie); + + // ✅ accessToken 쿼리 파라미터로 전달 + String redirectUrl = UriComponentsBuilder + .fromUriString(redirectUri) + .queryParam("accessToken", accessToken) + .build() + .toUriString(); + + response.sendRedirect(redirectUrl); + } + +// private boolean isUser(DefaultOAuth2User oAuth2User) { +// return oAuth2User.getAuthorities().stream() +// .anyMatch(authority -> authority.getAuthority().equals("ROLE_USER")); +// } +} + diff --git a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java new file mode 100644 index 0000000..59afdc7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java @@ -0,0 +1,59 @@ +package LogITBackend.LogIT.config.security.oauth; + +import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.converter.UserConverter; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + private final UserRepository userRepository; + + // 깃허브로 부터 받은 userRequest 데이터에 대한 후처리 되는 함수 + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{ + log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + log.info("getAccessToken: {}", userRequest.getAccessToken()); + log.info("getAttributes: {}", super.loadUser(userRequest).getAttributes()); + System.out.println("userRequest:"+userRequest); + System.out.println("getClientRegistraion:"+userRequest.getClientRegistration()); //client에 대한 정보들이 받아짐 + System.out.println("getAccessToken:"+userRequest.getAccessToken().getTokenValue()); + System.out.println("getAttributes:"+super.loadUser(userRequest).getAttributes()); //유저 정보를 받아옴 + + OAuth2User oAuth2User = super.loadUser(userRequest); // 실제 사용자 정보 요청 + + // 사용자 정보 map + Map attributes = oAuth2User.getAttributes(); + + // 정보 추출 + String providerId = String.valueOf(attributes.get("id")); + String nickname = (String) attributes.get("login"); + + Users existUser = userRepository.findByProviderId(providerId).orElse(null); + + if (existUser == null) { + // user dto 생성해서 user저장 + Users newUser = UserConverter.githubDatatoUsers( + UserRequestDTO.GithubSignUpRequestDTO.builder() + .providerId(providerId) + .nickname(nickname) + .githubAccessToken(userRequest.getAccessToken().getTokenValue()) + .build() + ); + userRepository.save(newUser); + } + + return oAuth2User; + } +} diff --git a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java index 493ef33..edb3c68 100644 --- a/src/main/java/LogITBackend/LogIT/converter/UserConverter.java +++ b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java @@ -14,6 +14,13 @@ public static Users toUsers(UserRequestDTO.SignUpRequestDTO request) { // .loginType(LoginType.REGULAR) .build(); } + public static Users githubDatatoUsers(UserRequestDTO.GithubSignUpRequestDTO request) { + return Users.builder() + .providerId(request.getProviderId()) + .nickname(request.getNickname()) + .githubAccesstoken(request.getGithubAccessToken()) + .build(); + } public static UserResponseDTO.UserSignUpResultDTO toUserSignUpResultDTO(Users users) { return UserResponseDTO.UserSignUpResultDTO.builder() .userId(users.getId()) diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 2d2631b..25128b2 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -59,6 +59,12 @@ public class Users extends BaseEntity { private LocalDateTime inactiveDate; + private String providerId; + + private String githubAccesstoken; + + private String accesstoken; + // 로그인 관련 // private LocalDateTime lastLogin; @@ -71,4 +77,8 @@ public class Users extends BaseEntity { public void encodePassword(String password) { this.password = password; } + + public void updateAccessToken(String accessToken) { + this.accesstoken = accessToken; + } } diff --git a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java index ae4b28f..8c873b1 100644 --- a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -11,4 +11,5 @@ public interface UserRepository extends JpaRepository { @Override Optional findById(Long id); Optional findByUsername(String username); + Optional findByProviderId(String providerId); } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java index f40cd8a..81047d6 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -7,4 +7,6 @@ public interface UserCommandService { Users signUp(UserRequestDTO.SignUpRequestDTO request); UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDTO request); + String generateAccessToken(Long userId, int accessExpTime); + String generateAndSaveRefreshToken(String key, int refreshExpTime); } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java index fdd1f2a..b8faed5 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -60,7 +60,7 @@ public UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDT return UserConverter.toUserSignInResultDTO(getUser, accessToken, refreshToken); } - private String generateAccessToken(Long userId, int accessExpTime) { + public String generateAccessToken(Long userId, int accessExpTime) { // 인증 완료 후 jwt토큰(accessToken) 생성 Map valueMap = Map.of( "userId", userId // String으로 저장??? 그래서 SecurityUtil에서 Long으로 타입변환 해주나? @@ -68,7 +68,7 @@ private String generateAccessToken(Long userId, int accessExpTime) { return jwtUtils.generateToken(valueMap, accessExpTime); } - private String generateAndSaveRefreshToken(String key, int refreshExpTime) { + public String generateAndSaveRefreshToken(String key, int refreshExpTime) { // 인증 완료 후 jwt토큰(refreshToken) 생성 String refreshToken = jwtUtils.generateToken(Collections.emptyMap(), refreshExpTime); redisTemplate.opsForValue().set(key, refreshToken, refreshExpTime, TimeUnit.MINUTES); From 24b983f79257a9fe9875852719552ed2da62c386 Mon Sep 17 00:00:00 2001 From: mino Date: Sun, 13 Apr 2025 18:05:28 +0900 Subject: [PATCH 17/39] =?UTF-8?q?org,=20orgRepo,=20PrivateRepo=20entity=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/domain/OrgRepos.java | 28 +++++++++++++++++++ .../LogIT/domain/Organization.java | 27 ++++++++++++++++++ .../LogIT/domain/PrivateRepo.java | 26 +++++++++++++++++ .../java/LogITBackend/LogIT/domain/Users.java | 6 ++++ 4 files changed, 87 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/domain/OrgRepos.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/Organization.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java diff --git a/src/main/java/LogITBackend/LogIT/domain/OrgRepos.java b/src/main/java/LogITBackend/LogIT/domain/OrgRepos.java new file mode 100644 index 0000000..4c64685 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/OrgRepos.java @@ -0,0 +1,28 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class OrgRepos extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 🔗 ManyToOne 연관관계 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "repo_id") + private Organization organization; + + @Column(length = 30, nullable = false) + private String repoName; + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Organization.java b/src/main/java/LogITBackend/LogIT/domain/Organization.java new file mode 100644 index 0000000..7bd2a94 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Organization.java @@ -0,0 +1,27 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Organization extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 🔗 ManyToOne 연관관계 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users user; + + @Column(length = 30, nullable = false) + private String orgName; +} diff --git a/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java b/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java new file mode 100644 index 0000000..0aad0e8 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java @@ -0,0 +1,26 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PrivateRepo extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users user; + + @Column(length = 40, nullable = false) + private String repoName; +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 3641eed..3318c66 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -77,6 +77,12 @@ public class Users extends BaseEntity { @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) private List codeCategoriesList = new ArrayList<>(); + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List organizationList = new ArrayList<>(); + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List privateRepoList = new ArrayList<>(); + public void encodePassword(String password) { this.password = password; } From 4395e972eb25f4d0daea9d9dbedcd34bc10fdc9a Mon Sep 17 00:00:00 2001 From: mino Date: Sun, 13 Apr 2025 23:00:32 +0900 Subject: [PATCH 18/39] =?UTF-8?q?commit=20list,=20detail=20api=EC=97=90=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/RepositoryResponseDTO.java | 18 +++ .../apiPayload/code/status/ErrorStatus.java | 4 + .../LogIT/controller/GithubController.java | 19 ++- .../domain/{OrgRepos.java => OrgRepo.java} | 2 +- .../LogIT/domain/PrivateRepo.java | 10 +- .../repository/PrivateRepoRepository.java | 11 ++ .../LogIT/service/GithubService.java | 7 +- .../LogIT/service/GithubServiceImpl.java | 131 +++++++++++++----- 8 files changed, 160 insertions(+), 42 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java rename src/main/java/LogITBackend/LogIT/domain/{OrgRepos.java => OrgRepo.java} (93%) create mode 100644 src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java new file mode 100644 index 0000000..30813b5 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java @@ -0,0 +1,18 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RepositoryResponseDTO { + private Long id; + private Long user_id; + private String repoName; + LocalDateTime createdAt; + LocalDateTime updatedAt; +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index cb30d5a..92ecddf 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -25,6 +25,7 @@ public enum ErrorStatus implements BaseErrorCode { ID_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER_1006", "해당하는 ID가 존재하지 않습니다."), ID_NOT_EQUAL(HttpStatus.BAD_REQUEST, "USER_1007", "ID가 일치하지 않습니다."), SAME_PASSWORD(HttpStatus.BAD_REQUEST, "USER_1008", "이전 비밀번호와 동일합니다."), + GITHUB_NOT_ACCESS(HttpStatus.UNAUTHORIZED, "USER_1009", "GitHub 계정 연동이 필요합니다."), // 카테고리 관련 응답 2000 CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_2001", "카테고리가 존재하지 않습니다."), @@ -33,6 +34,9 @@ public enum ErrorStatus implements BaseErrorCode { COMMIT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMIT_3001", "커밋이 존재하지 않습니다."); + + + private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/src/main/java/LogITBackend/LogIT/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java index 8e2bc4e..62b322d 100644 --- a/src/main/java/LogITBackend/LogIT/controller/GithubController.java +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -2,6 +2,7 @@ import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.DTO.RepositoryResponseDTO; import LogITBackend.LogIT.apiPayload.ApiResponse; import LogITBackend.LogIT.service.GithubService; import lombok.RequiredArgsConstructor; @@ -20,10 +21,9 @@ public class GithubController { @GetMapping("/{owners}/{repos}/commits") public ResponseEntity> getCommits( @PathVariable("owners") String owners, - @PathVariable("repos") String repos, - @RequestParam String token + @PathVariable("repos") String repos ) { - List commits = githubService.getInitialCommits(owners, repos, token); + List commits = githubService.getInitialCommits(owners, repos); return ResponseEntity.ok(ApiResponse.onSuccess(commits)); } @@ -31,12 +31,17 @@ public ResponseEntity> getCommits( public ResponseEntity> getCommitDetails( @PathVariable("owners") String owners, @PathVariable("repos") String repos, - @PathVariable("id") String commitId, - @RequestParam String token + @PathVariable("id") String commitId ) { - - CommitDetailResponseDTO commitDetail = githubService.getCommitDetails(owners, repos, commitId, token); + CommitDetailResponseDTO commitDetail = githubService.getCommitDetails(owners, repos, commitId); return ResponseEntity.ok(ApiResponse.onSuccess(commitDetail)); } + @GetMapping("/users/repos") + public ResponseEntity> getUsersRepos() { + List repos = githubService.getUsersRepos(); + return ResponseEntity.ok(ApiResponse.onSuccess(repos)); + } + + } diff --git a/src/main/java/LogITBackend/LogIT/domain/OrgRepos.java b/src/main/java/LogITBackend/LogIT/domain/OrgRepo.java similarity index 93% rename from src/main/java/LogITBackend/LogIT/domain/OrgRepos.java rename to src/main/java/LogITBackend/LogIT/domain/OrgRepo.java index 4c64685..f172184 100644 --- a/src/main/java/LogITBackend/LogIT/domain/OrgRepos.java +++ b/src/main/java/LogITBackend/LogIT/domain/OrgRepo.java @@ -12,7 +12,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class OrgRepos extends BaseEntity { +public class OrgRepo extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java b/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java index 0aad0e8..658e7e5 100644 --- a/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java +++ b/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java @@ -7,12 +7,15 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDate; +import java.time.LocalDateTime; + @Entity @Getter @NoArgsConstructor @AllArgsConstructor @Builder -public class PrivateRepo extends BaseEntity { +public class PrivateRepo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -23,4 +26,9 @@ public class PrivateRepo extends BaseEntity { @Column(length = 40, nullable = false) private String repoName; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + } diff --git a/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java b/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java new file mode 100644 index 0000000..a33ffed --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java @@ -0,0 +1,11 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.PrivateRepo; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PrivateRepoRepository extends JpaRepository { + + List findAllByUserId(Long userId); +} diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index 7daea4b..46fdc52 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -2,12 +2,15 @@ import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.DTO.RepositoryResponseDTO; import java.util.List; public interface GithubService { - List getInitialCommits(String owner, String repo, String accessToken); + List getInitialCommits(String owner, String repo); void getCommitsFromWebhook(String payload); - CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId, String token); + CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); + + List getUsersRepos(); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 29d245b..adec9e5 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -3,29 +3,26 @@ import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; import LogITBackend.LogIT.DTO.FileResponseDTO; +import LogITBackend.LogIT.DTO.RepositoryResponseDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; import LogITBackend.LogIT.apiPayload.exception.GeneralException; -import LogITBackend.LogIT.domain.Commit; -import LogITBackend.LogIT.domain.CommitParent; -import LogITBackend.LogIT.domain.File; -import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.config.security.SecurityUtil; +import LogITBackend.LogIT.domain.*; import LogITBackend.LogIT.domain.enums.Gender; import LogITBackend.LogIT.domain.enums.LoginType; import LogITBackend.LogIT.domain.enums.UserStatus; -import LogITBackend.LogIT.repository.CommitParentRepository; -import LogITBackend.LogIT.repository.CommitRepository; -import LogITBackend.LogIT.repository.FileRepository; +import LogITBackend.LogIT.repository.*; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -35,35 +32,35 @@ @RequiredArgsConstructor public class GithubServiceImpl implements GithubService { + private final UserRepository userRepository; private final CommitRepository commitRepository; private final CommitParentRepository commitParentRepository; private final FileRepository fileRepository; + private final PrivateRepoRepository privateRepoRepository; @Override @Transactional - public List getInitialCommits(String owner, String repo, String accessToken) { + public List getInitialCommits(String owner, String repo) { String url = String.format("https://api.github.com/repos/%s/%s/commits", owner, repo); + Long userId = SecurityUtil.getCurrentUserId(); + + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + String token = user.getGithubAccesstoken(); + if (token == null) { + throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); + } HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(accessToken); + headers.setBearerAuth(token); headers.set("Accept", "application/vnd.github+json"); headers.set("X-GitHub-Api-Version", "2022-11-28"); HttpEntity entity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); - Users dummyUser = Users.builder() - .id(1L) // DB에 실제 저장하지 않아도 됨, 그냥 임의의 ID - .nickname("dummy01") - .username("dummyuser") - .password("dummy1234!") // 사용 안 하므로 무관 - .loginType(LoginType.REGULAR) - .status(UserStatus.ACTIVE) - .githubNickname("dummyGithub") - .email("dummy@example.com") - .gender(Gender.MALE) - .build(); ResponseEntity>> response = restTemplate.exchange( url, @@ -83,11 +80,9 @@ public List getInitialCommits(String owner, String repo, Stri String dateStr = (String) author.get("date"); LocalDateTime date = LocalDateTime.parse(dateStr.replace("Z", "")); - - return new Commit( sha, - dummyUser, + user, message, null, // stats 필드는 이후에 계산할 수 있음 date, @@ -97,7 +92,6 @@ public List getInitialCommits(String owner, String repo, Stri commitRepository.saveAll(savedCommits); - // 커밋을 SHA 기준으로 Map으로 변환 (List 참조 대신 성능 개선함) Map shaToCommitMap = savedCommits.stream() .collect(Collectors.toMap(Commit::getId, c -> c)); @@ -108,7 +102,7 @@ public List getInitialCommits(String owner, String repo, Stri List> parents = (List>) item.get("parents"); return parents.stream() - .map(parentMap -> { + .map( parentMap -> { String parentSha = (String) parentMap.get("sha"); Commit parent = shaToCommitMap.get(parentSha); if (child != null && parent != null) { @@ -129,7 +123,6 @@ public List getInitialCommits(String owner, String repo, Stri return savedCommits.stream() .map(c -> new CommitResponseDTO(c.getId(), c.getUser().getId() ,c.getMessage(), c.getStats(), c.getDate())) .collect(Collectors.toList()); - } @Override @@ -139,11 +132,19 @@ public void getCommitsFromWebhook(String payload) { @Override @Transactional - public CommitDetailResponseDTO getCommitDetails(String owner, String repo, String commitId, String token) { + public CommitDetailResponseDTO getCommitDetails(String owner, String repo, String commitId) { + Long userId = SecurityUtil.getCurrentUserId(); + + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + String token = user.getGithubAccesstoken(); + Commit commit = commitRepository.findById(commitId) .orElseThrow(() -> new GeneralException(COMMIT_NOT_FOUND)); // stats가 null이면 GitHub에서 정보 요청 + // 커밋 세부정보는 거의 바뀌지 않으므로, update x if (commit.getStats() == null) { RestTemplate restTemplate = new RestTemplate(); @@ -192,4 +193,72 @@ public CommitDetailResponseDTO getCommitDetails(String owner, String repo, Strin CommitResponseDTO commitResponseDTO = CommitResponseDTO.fromEntity(commit); return new CommitDetailResponseDTO(commitResponseDTO, fileResponses); } + + @Override + public List getUsersRepos() { + Long userId = SecurityUtil.getCurrentUserId(); + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + String githubAccesstoken = user.getGithubAccesstoken(); + String githubNickname = user.getGithubNickname(); + + if (githubAccesstoken == null || githubNickname == null) { + throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); + } + + List privateRepo = privateRepoRepository.findAllByUserId(userId); + + if (privateRepo.isEmpty()) { + String url = "https://api.github.com/users/" + githubNickname + "/repos"; + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(githubAccesstoken); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity request = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + request, + JsonNode.class + ); + + JsonNode body = response.getBody(); + List repoList = new ArrayList<>(); + + for (JsonNode item : body) { + String name = item.get("name").asText(); + String createdAtStr = item.get("created_at").asText(); + String updatedAtStr = item.get("updated_at").asText(); + + LocalDateTime createdAt = LocalDateTime.parse(createdAtStr, DateTimeFormatter.ISO_DATE_TIME); + LocalDateTime updatedAt = LocalDateTime.parse(updatedAtStr, DateTimeFormatter.ISO_DATE_TIME); + + PrivateRepo repo = PrivateRepo.builder() + .user(user) + .repoName(name) + .createdAt(createdAt) + .updatedAt(updatedAt) + .build(); + + repoList.add(repo); + } + + privateRepoRepository.saveAll(repoList); + + } + + // db에 있는거 그대로 출력하는 로직 + return privateRepoRepository.findAllByUserId(userId) + .stream() + .map(repo -> new RepositoryResponseDTO( + repo.getId(), + userId, + repo.getRepoName(), + repo.getCreatedAt(), + repo.getUpdatedAt() + )) + .collect(Collectors.toList()); + } } From a308c03d5924bb6bc7ed04916f11172e93560cdd Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 14 Apr 2025 00:04:37 +0900 Subject: [PATCH 19/39] =?UTF-8?q?commit=20list=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(=EB=82=A0=EC=A7=9C=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=EB=A7=8C=20=EC=88=98=EC=A0=95=EB=90=98?= =?UTF-8?q?=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/repository/CommitRepository.java | 8 +++++- .../LogIT/service/GithubService.java | 1 - .../LogIT/service/GithubServiceImpl.java | 25 +++++++++++++------ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java index 2d2a21f..3959498 100644 --- a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java @@ -1,8 +1,14 @@ package LogITBackend.LogIT.repository; import LogITBackend.LogIT.domain.Commit; +import LogITBackend.LogIT.domain.Users; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; -public interface CommitRepository extends JpaRepository { +import java.time.LocalDateTime; +import java.util.Optional; +public interface CommitRepository extends JpaRepository { + @Query("SELECT MAX(c.date) FROM Commit c WHERE c.user.id = :userId") + Optional findLatestCommitDateByUserId(Long userId); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index 46fdc52..b9f169f 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -8,7 +8,6 @@ public interface GithubService { List getInitialCommits(String owner, String repo); - void getCommitsFromWebhook(String payload); CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index adec9e5..715eb98 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -53,6 +53,9 @@ public List getInitialCommits(String owner, String repo) { throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); } + LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(user.getId()) + .orElse(LocalDateTime.MIN); + HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); headers.set("Accept", "application/vnd.github+json"); @@ -72,7 +75,17 @@ public List getInitialCommits(String owner, String repo) { List> body = response.getBody(); if (body == null) return Collections.emptyList(); - List savedCommits = body.stream().map(item -> { + List> newCommits = body.stream() + .filter(item -> { + Map commit = (Map) item.get("commit"); + Map author = (Map) commit.get("author"); + String dateStr = (String) author.get("date"); + LocalDateTime date = LocalDateTime.parse(dateStr.replace("Z", "")); + return date.isAfter(latestDate); + }) + .toList(); + + List savedCommits = newCommits.stream().map(item -> { String sha = (String) item.get("sha"); Map commit = (Map) item.get("commit"); String message = (String) commit.get("message"); @@ -95,7 +108,7 @@ public List getInitialCommits(String owner, String repo) { Map shaToCommitMap = savedCommits.stream() .collect(Collectors.toMap(Commit::getId, c -> c)); - List savedParents = body.stream() + List savedParents = newCommits.stream() .flatMap(item -> { String childSha = (String) item.get("sha"); Commit child = shaToCommitMap.get(childSha); @@ -125,13 +138,9 @@ public List getInitialCommits(String owner, String repo) { .collect(Collectors.toList()); } - @Override - public void getCommitsFromWebhook(String payload) { - - } @Override - @Transactional + @Transactional // 커밋 세부정보는 거의 바뀌지 않으므로, update x public CommitDetailResponseDTO getCommitDetails(String owner, String repo, String commitId) { Long userId = SecurityUtil.getCurrentUserId(); @@ -144,7 +153,7 @@ public CommitDetailResponseDTO getCommitDetails(String owner, String repo, Strin .orElseThrow(() -> new GeneralException(COMMIT_NOT_FOUND)); // stats가 null이면 GitHub에서 정보 요청 - // 커밋 세부정보는 거의 바뀌지 않으므로, update x + if (commit.getStats() == null) { RestTemplate restTemplate = new RestTemplate(); From 91ef1ec0adacea5e7083c710e272e59694e26438 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 14 Apr 2025 03:13:51 +0900 Subject: [PATCH 20/39] =?UTF-8?q?entity=20=EC=88=98=EC=A0=95,=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/CommitResponseDTO.java | 6 +- .../LogIT/DTO/GithubRepoResponse.java | 15 +++ .../LogIT/DTO/RepositoryResponseDTO.java | 1 - .../apiPayload/code/status/ErrorStatus.java | 8 +- .../LogIT/controller/GithubController.java | 5 +- .../LogITBackend/LogIT/domain/Commit.java | 8 +- .../LogITBackend/LogIT/domain/OrgRepo.java | 28 ---- .../domain/{Organization.java => Owner.java} | 5 +- .../domain/{PrivateRepo.java => Repo.java} | 11 +- .../java/LogITBackend/LogIT/domain/Users.java | 6 +- .../LogIT/repository/CommitRepository.java | 8 +- .../LogIT/repository/OwnerRepository.java | 18 +++ .../repository/PrivateRepoRepository.java | 11 -- .../LogIT/repository/RepoRepository.java | 20 +++ .../LogIT/repository/UserRepository.java | 2 +- .../LogIT/service/GithubService.java | 5 +- .../LogIT/service/GithubServiceImpl.java | 120 ++++++++++-------- 17 files changed, 159 insertions(+), 118 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/GithubRepoResponse.java delete mode 100644 src/main/java/LogITBackend/LogIT/domain/OrgRepo.java rename src/main/java/LogITBackend/LogIT/domain/{Organization.java => Owner.java} (82%) rename src/main/java/LogITBackend/LogIT/domain/{PrivateRepo.java => Repo.java} (75%) create mode 100644 src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java delete mode 100644 src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/RepoRepository.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java index f0c59fe..1521111 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java @@ -2,20 +2,18 @@ import LogITBackend.LogIT.domain.Commit; -import LogITBackend.LogIT.domain.File; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; -import java.util.List; @Data @AllArgsConstructor @NoArgsConstructor public class CommitResponseDTO { private String id; //Commit id - private Long user_id; + private Long repo_id; private String message; private String stats; private LocalDateTime date; @@ -23,7 +21,7 @@ public class CommitResponseDTO { public static CommitResponseDTO fromEntity(Commit commit) { return new CommitResponseDTO( commit.getId(), - commit.getUser().getId(), + commit.getRepo().getId(), commit.getMessage(), commit.getStats(), commit.getDate() diff --git a/src/main/java/LogITBackend/LogIT/DTO/GithubRepoResponse.java b/src/main/java/LogITBackend/LogIT/DTO/GithubRepoResponse.java new file mode 100644 index 0000000..2337353 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/GithubRepoResponse.java @@ -0,0 +1,15 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GithubRepoResponse { + private String ownerName; + private List repoList; +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java index 30813b5..03398ee 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java @@ -11,7 +11,6 @@ @NoArgsConstructor public class RepositoryResponseDTO { private Long id; - private Long user_id; private String repoName; LocalDateTime createdAt; LocalDateTime updatedAt; diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index 92ecddf..4910360 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -31,11 +31,13 @@ public enum ErrorStatus implements BaseErrorCode { CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_2001", "카테고리가 존재하지 않습니다."), // commit 관련 응답 3000 - COMMIT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMIT_3001", "커밋이 존재하지 않습니다."); - - + COMMIT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMIT_3001", "커밋이 존재하지 않습니다."), + // repo 관련 응답 4000 + REPO_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPO_4001", "레포지토리가 존재하지 않습니다."), + // owner 관련 응답 5000 + OWNER_NOT_FOUND(HttpStatus.BAD_REQUEST, "OWNER_5001", "OWNER이 존재하지 않습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/LogITBackend/LogIT/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java index 62b322d..05a99a9 100644 --- a/src/main/java/LogITBackend/LogIT/controller/GithubController.java +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -2,6 +2,7 @@ import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.DTO.GithubRepoResponse; import LogITBackend.LogIT.DTO.RepositoryResponseDTO; import LogITBackend.LogIT.apiPayload.ApiResponse; import LogITBackend.LogIT.service.GithubService; @@ -23,7 +24,7 @@ public ResponseEntity> getCommits( @PathVariable("owners") String owners, @PathVariable("repos") String repos ) { - List commits = githubService.getInitialCommits(owners, repos); + List commits = githubService.getCommits(owners, repos); return ResponseEntity.ok(ApiResponse.onSuccess(commits)); } @@ -39,7 +40,7 @@ public ResponseEntity> getCommitDetails( @GetMapping("/users/repos") public ResponseEntity> getUsersRepos() { - List repos = githubService.getUsersRepos(); + GithubRepoResponse repos = githubService.getUsersRepos(); return ResponseEntity.ok(ApiResponse.onSuccess(repos)); } diff --git a/src/main/java/LogITBackend/LogIT/domain/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java index ab08502..bb69fe2 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Commit.java +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -26,8 +26,12 @@ public class Commit extends BaseEntity { private String id; // commit SHA @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private Users user; + @JoinColumn(name = "repo_id", nullable = false) + private Repo repo; + +// @ManyToOne(fetch = FetchType.LAZY) +// @JoinColumn(name = "user_id", nullable = false) +// private Users user; @Column(length = 255) private String message; diff --git a/src/main/java/LogITBackend/LogIT/domain/OrgRepo.java b/src/main/java/LogITBackend/LogIT/domain/OrgRepo.java deleted file mode 100644 index f172184..0000000 --- a/src/main/java/LogITBackend/LogIT/domain/OrgRepo.java +++ /dev/null @@ -1,28 +0,0 @@ -package LogITBackend.LogIT.domain; - -import LogITBackend.LogIT.domain.common.BaseEntity; -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class OrgRepo extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - // 🔗 ManyToOne 연관관계 - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "repo_id") - private Organization organization; - - @Column(length = 30, nullable = false) - private String repoName; - -} diff --git a/src/main/java/LogITBackend/LogIT/domain/Organization.java b/src/main/java/LogITBackend/LogIT/domain/Owner.java similarity index 82% rename from src/main/java/LogITBackend/LogIT/domain/Organization.java rename to src/main/java/LogITBackend/LogIT/domain/Owner.java index 7bd2a94..b8e7167 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Organization.java +++ b/src/main/java/LogITBackend/LogIT/domain/Owner.java @@ -12,16 +12,15 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class Organization extends BaseEntity { +public class Owner extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - // 🔗 ManyToOne 연관관계 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private Users user; @Column(length = 30, nullable = false) - private String orgName; + private String ownerName; } diff --git a/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java b/src/main/java/LogITBackend/LogIT/domain/Repo.java similarity index 75% rename from src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java rename to src/main/java/LogITBackend/LogIT/domain/Repo.java index 658e7e5..aa3f0fa 100644 --- a/src/main/java/LogITBackend/LogIT/domain/PrivateRepo.java +++ b/src/main/java/LogITBackend/LogIT/domain/Repo.java @@ -7,26 +7,29 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; @Entity @Getter @NoArgsConstructor @AllArgsConstructor @Builder -public class PrivateRepo { +public class Repo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private Users user; + @JoinColumn(name = "owner_id") + private Owner owner; @Column(length = 40, nullable = false) private String repoName; + @OneToMany(mappedBy = "repo", cascade = CascadeType.ALL) + private List commit; + private LocalDateTime createdAt; private LocalDateTime updatedAt; diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 3318c66..239dc83 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -6,7 +6,6 @@ import LogITBackend.LogIT.domain.enums.UserStatus; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -78,10 +77,7 @@ public class Users extends BaseEntity { private List codeCategoriesList = new ArrayList<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List organizationList = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List privateRepoList = new ArrayList<>(); + private List ownerList = new ArrayList<>(); public void encodePassword(String password) { this.password = password; diff --git a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java index 3959498..d3708e6 100644 --- a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java @@ -6,9 +6,13 @@ import org.springframework.data.jpa.repository.Query; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public interface CommitRepository extends JpaRepository { - @Query("SELECT MAX(c.date) FROM Commit c WHERE c.user.id = :userId") - Optional findLatestCommitDateByUserId(Long userId); + @Query("SELECT MAX(c.date) FROM Commit c WHERE c.repo.id = :repoId") + Optional findLatestCommitDateByUserId(Long repoId); + + @Query("SELECT c FROM Commit c WHERE c.repo.id = :repoId") + List findAllByRepoId(Long repoId); } diff --git a/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java b/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java new file mode 100644 index 0000000..0dccefe --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java @@ -0,0 +1,18 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Owner; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface OwnerRepository extends JpaRepository { + + @Query("SELECT o FROM Owner o WHERE o.user.id = :userId AND o.ownerName = :ownerName") + Optional findByUserIdAndOwnerName(@Param("userId") Long userId, @Param("ownerName") String ownerName); + + + List findAllByUserId(Long userId); +} diff --git a/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java b/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java deleted file mode 100644 index a33ffed..0000000 --- a/src/main/java/LogITBackend/LogIT/repository/PrivateRepoRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package LogITBackend.LogIT.repository; - -import LogITBackend.LogIT.domain.PrivateRepo; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface PrivateRepoRepository extends JpaRepository { - - List findAllByUserId(Long userId); -} diff --git a/src/main/java/LogITBackend/LogIT/repository/RepoRepository.java b/src/main/java/LogITBackend/LogIT/repository/RepoRepository.java new file mode 100644 index 0000000..c78253a --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/RepoRepository.java @@ -0,0 +1,20 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Owner; +import LogITBackend.LogIT.domain.Repo; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface RepoRepository extends JpaRepository { + Optional findByOwnerIdAndRepoName(Long ownerId, String repoName); + + List findAllByOwnerId(Long ownerId); + + @Query("SELECT MAX(r.createdAt) FROM Repo r WHERE r.owner.id = :ownerId") + Optional findLatestRepoCreatedAtByOwnerId(@Param("ownerId") Long ownerId); +} diff --git a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java index 8c873b1..260f800 100644 --- a/src/main/java/LogITBackend/LogIT/repository/UserRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -9,7 +9,7 @@ @Repository public interface UserRepository extends JpaRepository { @Override - Optional findById(Long id); + Optional findById(Long userId); Optional findByUsername(String username); Optional findByProviderId(String providerId); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index b9f169f..b99e2d8 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -2,14 +2,15 @@ import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; import LogITBackend.LogIT.DTO.CommitResponseDTO; +import LogITBackend.LogIT.DTO.GithubRepoResponse; import LogITBackend.LogIT.DTO.RepositoryResponseDTO; import java.util.List; public interface GithubService { - List getInitialCommits(String owner, String repo); + List getCommits(String owner, String repo); CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); - List getUsersRepos(); + GithubRepoResponse getUsersRepos(); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 715eb98..ed8d1c6 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -1,19 +1,12 @@ package LogITBackend.LogIT.service; -import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; -import LogITBackend.LogIT.DTO.CommitResponseDTO; -import LogITBackend.LogIT.DTO.FileResponseDTO; -import LogITBackend.LogIT.DTO.RepositoryResponseDTO; +import LogITBackend.LogIT.DTO.*; import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; import LogITBackend.LogIT.apiPayload.exception.GeneralException; import LogITBackend.LogIT.config.security.SecurityUtil; import LogITBackend.LogIT.domain.*; -import LogITBackend.LogIT.domain.enums.Gender; -import LogITBackend.LogIT.domain.enums.LoginType; -import LogITBackend.LogIT.domain.enums.UserStatus; import LogITBackend.LogIT.repository.*; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; @@ -32,16 +25,17 @@ @RequiredArgsConstructor public class GithubServiceImpl implements GithubService { + private final OwnerRepository ownerRepository; + private final RepoRepository repoRepository; private final UserRepository userRepository; private final CommitRepository commitRepository; private final CommitParentRepository commitParentRepository; private final FileRepository fileRepository; - private final PrivateRepoRepository privateRepoRepository; @Override @Transactional - public List getInitialCommits(String owner, String repo) { - String url = String.format("https://api.github.com/repos/%s/%s/commits", owner, repo); + public List getCommits(String ownerName, String repoName) { + String url = String.format("https://api.github.com/repos/%s/%s/commits", ownerName, repoName); Long userId = SecurityUtil.getCurrentUserId(); @@ -53,7 +47,14 @@ public List getInitialCommits(String owner, String repo) { throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); } - LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(user.getId()) + Owner owner = ownerRepository.findByUserIdAndOwnerName(userId,ownerName) + .orElseThrow(() -> new GeneralException(ErrorStatus.OWNER_NOT_FOUND)); + + Repo repo = repoRepository.findByOwnerIdAndRepoName(owner.getId(), repoName) + .orElseThrow(() -> new GeneralException(ErrorStatus.REPO_NOT_FOUND)); + + + LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(repo.getId()) .orElse(LocalDateTime.MIN); HttpHeaders headers = new HttpHeaders(); @@ -95,7 +96,7 @@ public List getInitialCommits(String owner, String repo) { return new Commit( sha, - user, + repo, message, null, // stats 필드는 이후에 계산할 수 있음 date, @@ -132,9 +133,10 @@ public List getInitialCommits(String owner, String repo) { commitParentRepository.saveAll(savedParents); + List allCommitList = commitRepository.findAllByRepoId(repo.getId()); - return savedCommits.stream() - .map(c -> new CommitResponseDTO(c.getId(), c.getUser().getId() ,c.getMessage(), c.getStats(), c.getDate())) + return allCommitList.stream() + .map(c -> new CommitResponseDTO(c.getId(), c.getRepo().getId() ,c.getMessage(), c.getStats(), c.getDate())) .collect(Collectors.toList()); } @@ -204,10 +206,12 @@ public CommitDetailResponseDTO getCommitDetails(String owner, String repo, Strin } @Override - public List getUsersRepos() { + @Transactional + public GithubRepoResponse getUsersRepos() { Long userId = SecurityUtil.getCurrentUserId(); Users user = userRepository.findById(userId) .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + String githubAccesstoken = user.getGithubAccesstoken(); String githubNickname = user.getGithubNickname(); @@ -215,26 +219,29 @@ public List getUsersRepos() { throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); } - List privateRepo = privateRepoRepository.findAllByUserId(userId); + List owners = ownerRepository.findAllByUserId(userId); - if (privateRepo.isEmpty()) { - String url = "https://api.github.com/users/" + githubNickname + "/repos"; - RestTemplate restTemplate = new RestTemplate(); + Owner owner = getOrCreateOwner(user, owners); - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(githubAccesstoken); - headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - HttpEntity request = new HttpEntity<>(headers); - - ResponseEntity response = restTemplate.exchange( - url, - HttpMethod.GET, - request, - JsonNode.class - ); + String url = "https://api.github.com/users/" + githubNickname + "/repos"; + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(githubAccesstoken); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity request = new HttpEntity<>(headers); - JsonNode body = response.getBody(); - List repoList = new ArrayList<>(); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + request, + JsonNode.class + ); + + LocalDateTime latestDate = repoRepository.findLatestRepoCreatedAtByOwnerId(owner.getId()) + .orElse(LocalDateTime.MIN); + + JsonNode body = response.getBody(); + List repoList = new ArrayList<>(); for (JsonNode item : body) { String name = item.get("name").asText(); @@ -244,30 +251,43 @@ public List getUsersRepos() { LocalDateTime createdAt = LocalDateTime.parse(createdAtStr, DateTimeFormatter.ISO_DATE_TIME); LocalDateTime updatedAt = LocalDateTime.parse(updatedAtStr, DateTimeFormatter.ISO_DATE_TIME); - PrivateRepo repo = PrivateRepo.builder() - .user(user) - .repoName(name) - .createdAt(createdAt) - .updatedAt(updatedAt) - .build(); + if (createdAt.isAfter(latestDate)) { + Repo repo = Repo.builder() + .owner(owner) + .repoName(name) + .createdAt(createdAt) + .updatedAt(updatedAt) + .build(); - repoList.add(repo); + repoList.add(repo); + } } - - privateRepoRepository.saveAll(repoList); - - } + repoRepository.saveAll(repoList); // db에 있는거 그대로 출력하는 로직 - return privateRepoRepository.findAllByUserId(userId) + List repoDTOList = repoRepository.findAllByOwnerId((owner.getId())) .stream() .map(repo -> new RepositoryResponseDTO( - repo.getId(), - userId, - repo.getRepoName(), - repo.getCreatedAt(), - repo.getUpdatedAt() + repo.getId(), + repo.getRepoName(), + repo.getCreatedAt(), + repo.getUpdatedAt() )) .collect(Collectors.toList()); + + return new GithubRepoResponse(owner.getOwnerName(), repoDTOList); + } + + private Owner getOrCreateOwner(Users user, List owners) { + return owners.stream() + .filter(o -> o.getOwnerName().equals(user.getGithubNickname())) + .findFirst() + .orElseGet(() -> { + Owner newOwner = Owner.builder() + .user(user) + .ownerName(user.getGithubNickname()) + .build(); + return ownerRepository.save(newOwner); + }); } } From cdddb3136eaece33700531fd4fdd44c9e545eb72 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 14 Apr 2025 14:54:40 +0900 Subject: [PATCH 21/39] =?UTF-8?q?stash=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/DTO/OrgResponse.java | 12 ++ .../LogIT/DTO/RepositoryResponseDTO.java | 1 - .../LogIT/controller/GithubController.java | 17 +- .../java/LogITBackend/LogIT/domain/Owner.java | 2 +- .../java/LogITBackend/LogIT/domain/Repo.java | 2 +- .../LogIT/service/GithubService.java | 9 +- .../LogIT/service/GithubServiceImpl.java | 152 +++++++++++++++--- 7 files changed, 161 insertions(+), 34 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/OrgResponse.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/OrgResponse.java b/src/main/java/LogITBackend/LogIT/DTO/OrgResponse.java new file mode 100644 index 0000000..739ffa3 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/OrgResponse.java @@ -0,0 +1,12 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OrgResponse { + private String OrgName; +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java index 03398ee..74255e7 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java @@ -10,7 +10,6 @@ @AllArgsConstructor @NoArgsConstructor public class RepositoryResponseDTO { - private Long id; private String repoName; LocalDateTime createdAt; LocalDateTime updatedAt; diff --git a/src/main/java/LogITBackend/LogIT/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java index 05a99a9..01b7d9d 100644 --- a/src/main/java/LogITBackend/LogIT/controller/GithubController.java +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -1,9 +1,6 @@ package LogITBackend.LogIT.controller; -import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; -import LogITBackend.LogIT.DTO.CommitResponseDTO; -import LogITBackend.LogIT.DTO.GithubRepoResponse; -import LogITBackend.LogIT.DTO.RepositoryResponseDTO; +import LogITBackend.LogIT.DTO.*; import LogITBackend.LogIT.apiPayload.ApiResponse; import LogITBackend.LogIT.service.GithubService; import lombok.RequiredArgsConstructor; @@ -44,5 +41,17 @@ public ResponseEntity> getUsersRepos() { return ResponseEntity.ok(ApiResponse.onSuccess(repos)); } + @GetMapping("/users/org") + public ResponseEntity> getUserOrgs() { + List orgs = githubService.getUserOrgs(); + return ResponseEntity.ok(ApiResponse.onSuccess(orgs)); + } + @GetMapping("/users/{owners}/repos") + public ResponseEntity> getUserOrgsRepos( + @PathVariable("owners") String owners + ) { + GithubRepoResponse repos = githubService.getUserOrgsRepos(owners); + return ResponseEntity.ok(ApiResponse.onSuccess(repos)); + } } diff --git a/src/main/java/LogITBackend/LogIT/domain/Owner.java b/src/main/java/LogITBackend/LogIT/domain/Owner.java index b8e7167..81886b0 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Owner.java +++ b/src/main/java/LogITBackend/LogIT/domain/Owner.java @@ -21,6 +21,6 @@ public class Owner extends BaseEntity { @JoinColumn(name = "user_id") private Users user; - @Column(length = 30, nullable = false) + @Column(length = 100, nullable = false) private String ownerName; } diff --git a/src/main/java/LogITBackend/LogIT/domain/Repo.java b/src/main/java/LogITBackend/LogIT/domain/Repo.java index aa3f0fa..5b19d9b 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Repo.java +++ b/src/main/java/LogITBackend/LogIT/domain/Repo.java @@ -24,7 +24,7 @@ public class Repo { @JoinColumn(name = "owner_id") private Owner owner; - @Column(length = 40, nullable = false) + @Column(length = 100, nullable = false) private String repoName; @OneToMany(mappedBy = "repo", cascade = CascadeType.ALL) diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index b99e2d8..bb42701 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -1,9 +1,6 @@ package LogITBackend.LogIT.service; -import LogITBackend.LogIT.DTO.CommitDetailResponseDTO; -import LogITBackend.LogIT.DTO.CommitResponseDTO; -import LogITBackend.LogIT.DTO.GithubRepoResponse; -import LogITBackend.LogIT.DTO.RepositoryResponseDTO; +import LogITBackend.LogIT.DTO.*; import java.util.List; @@ -13,4 +10,8 @@ public interface GithubService { CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); GithubRepoResponse getUsersRepos(); + + List getUserOrgs(); + + GithubRepoResponse getUserOrgsRepos(String owners); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index ed8d1c6..3f4774d 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -243,41 +243,147 @@ public GithubRepoResponse getUsersRepos() { JsonNode body = response.getBody(); List repoList = new ArrayList<>(); - for (JsonNode item : body) { - String name = item.get("name").asText(); - String createdAtStr = item.get("created_at").asText(); - String updatedAtStr = item.get("updated_at").asText(); - - LocalDateTime createdAt = LocalDateTime.parse(createdAtStr, DateTimeFormatter.ISO_DATE_TIME); - LocalDateTime updatedAt = LocalDateTime.parse(updatedAtStr, DateTimeFormatter.ISO_DATE_TIME); - - if (createdAt.isAfter(latestDate)) { - Repo repo = Repo.builder() - .owner(owner) - .repoName(name) - .createdAt(createdAt) - .updatedAt(updatedAt) - .build(); - - repoList.add(repo); - } + for (JsonNode item : body) { + String name = item.get("name").asText(); + String createdAtStr = item.get("created_at").asText(); + String updatedAtStr = item.get("updated_at").asText(); + + LocalDateTime createdAt = LocalDateTime.parse(createdAtStr, DateTimeFormatter.ISO_DATE_TIME); + LocalDateTime updatedAt = LocalDateTime.parse(updatedAtStr, DateTimeFormatter.ISO_DATE_TIME); + + if (createdAt.isAfter(latestDate)) { + Repo repo = Repo.builder() + .owner(owner) + .repoName(name) + .createdAt(createdAt) + .updatedAt(updatedAt) + .build(); + + repoList.add(repo); } - repoRepository.saveAll(repoList); + } + repoRepository.saveAll(repoList); // db에 있는거 그대로 출력하는 로직 List repoDTOList = repoRepository.findAllByOwnerId((owner.getId())) .stream() .map(repo -> new RepositoryResponseDTO( - repo.getId(), - repo.getRepoName(), - repo.getCreatedAt(), - repo.getUpdatedAt() + repo.getRepoName(), + repo.getCreatedAt(), + repo.getUpdatedAt() )) .collect(Collectors.toList()); return new GithubRepoResponse(owner.getOwnerName(), repoDTOList); } + @Override + public List getUserOrgs() { + Long userId = SecurityUtil.getCurrentUserId(); + + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + String githubAccesstoken = user.getGithubAccesstoken(); + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(githubAccesstoken); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity request = new HttpEntity<>(headers); + + RestTemplate restTemplate = new RestTemplate(); + + // Step 1: 조직 목록 조회 + ResponseEntity orgResponse = restTemplate.exchange( + "https://api.github.com/user/orgs", + HttpMethod.GET, + request, + JsonNode.class + ); + + List orgResponses = new ArrayList<>(); + + for (JsonNode org : orgResponse.getBody()) { + String orgName = org.get("login").asText(); // Owner 이름 + + ownerRepository.findByUserIdAndOwnerName(user.getId(), orgName) + .orElseGet(() -> ownerRepository.save( + Owner.builder() + .user(user) + .ownerName(orgName) + .build() + )); + + orgResponses.add(new OrgResponse(orgName)); + } + return orgResponses; + } + + @Override + public GithubRepoResponse getUserOrgsRepos(String owners) { + Long userId = SecurityUtil.getCurrentUserId(); + + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + String githubAccesstoken = user.getGithubAccesstoken(); + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(githubAccesstoken); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + HttpEntity request = new HttpEntity<>(headers); + + RestTemplate restTemplate = new RestTemplate(); + + Owner owner = ownerRepository.findByUserIdAndOwnerName(userId, owners) + .orElseGet(() -> ownerRepository.save( + Owner.builder() + .user(user) + .ownerName(owners) + .build() + )); + + LocalDateTime latestDate = repoRepository.findLatestRepoCreatedAtByOwnerId(owner.getId()) + .orElse(LocalDateTime.MIN); + + String url = "https://api.github.com/orgs/" + owners + "/repos?per_page=100"; + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + request, + JsonNode.class + ); + + List repoDTOList = new ArrayList<>(); + List newRepos = new ArrayList<>(); + + for (JsonNode repoNode : response.getBody()) { + String repoName = repoNode.get("name").asText(); + LocalDateTime createdAt = LocalDateTime.parse(repoNode.get("created_at").asText(), DateTimeFormatter.ISO_DATE_TIME); + LocalDateTime updatedAt = LocalDateTime.parse(repoNode.get("updated_at").asText(), DateTimeFormatter.ISO_DATE_TIME); + + if (createdAt.isAfter(latestDate)) { + Repo repo = Repo.builder() + .owner(owner) + .repoName(repoName) + .createdAt(createdAt) + .updatedAt(updatedAt) + .build(); + newRepos.add(repo); + } + + repoDTOList.add(new RepositoryResponseDTO( + repoName, + createdAt, + updatedAt + )); + } + + repoRepository.saveAll(newRepos); + + return new GithubRepoResponse(owners, repoDTOList); + } + private Owner getOrCreateOwner(Users user, List owners) { return owners.stream() .filter(o -> o.getOwnerName().equals(user.getGithubNickname())) From fde0d0ee6149b912c2883744a3a0dd28c248fe35 Mon Sep 17 00:00:00 2001 From: mino Date: Mon, 14 Apr 2025 20:51:07 +0900 Subject: [PATCH 22/39] =?UTF-8?q?CORS=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/config/WebConfig.java | 23 +++++++++++++++++++ .../LogIT/config/security/SecurityConfig.java | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/LogITBackend/LogIT/config/WebConfig.java diff --git a/src/main/java/LogITBackend/LogIT/config/WebConfig.java b/src/main/java/LogITBackend/LogIT/config/WebConfig.java new file mode 100644 index 0000000..2e056ea --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/WebConfig.java @@ -0,0 +1,23 @@ +package LogITBackend.LogIT.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig { + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173") // 프론트엔드 주소 추가 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } + }; + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java index cb85ba6..750c7dd 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -16,6 +16,11 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; @EnableWebSecurity @Configuration @@ -31,6 +36,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) // 추가해주어야함. // 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) @@ -49,4 +55,17 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ); return http.build(); } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of("http://localhost:5173")); // 프론트엔드 주소 + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setAllowCredentials(true); // 인증 정보 포함 요청 허용 (ex. 쿠키) + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } } From 116a5424cc2471b6a65250375190232f085199dc Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Mon, 14 Apr 2025 23:36:30 +0900 Subject: [PATCH 23/39] =?UTF-8?q?feat:=20#29=20=EA=B9=83=ED=97=88=EB=B8=8C?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=82=AD=EC=A0=9C,=20=EB=8C=80=EC=8B=A0=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=9B=84=20?= =?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=EC=97=90=20=EB=93=B1=EB=A1=9D(?= =?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=20OAuth2=EB=A1=9C=20PAT=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EA=B8=B0)=ED=95=98=EB=8A=94=20=EC=A0=88?= =?UTF-8?q?=EC=B0=A8=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 깃허브 소셜 로그인 기능 삭제 일반 로그인 후 깃허브 OAuth2로 PAT가져오기 db에서 일반 회원 테이블에 깃허브 OAuth2로 가져온 PAT 정보 추가하는 api구현 --- .../LogIT/DTO/UserRequestDTO.java | 8 +++ .../LogITBackend/LogIT/config/WebConfig.java | 18 ++++++ .../LogIT/config/security/SecurityConfig.java | 1 + .../oauth/CustomOAuth2SuccessHandler.java | 60 +++++++++++++------ .../oauth/CustomOAuth2UserService.java | 54 ++++++++--------- .../LogIT/controller/UserController.java | 11 ++++ .../java/LogITBackend/LogIT/domain/Users.java | 12 ++++ .../LogIT/service/UserCommandService.java | 1 + .../LogIT/service/UserCommandServiceImpl.java | 15 +++++ 9 files changed, 136 insertions(+), 44 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/config/WebConfig.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java index 70eb8bb..107d9b1 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java @@ -47,4 +47,12 @@ public static class GithubSignUpRequestDTO { private String nickname; private String githubAccessToken; } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class GithubRegisterRequestDTO { + private String providerId; + } } diff --git a/src/main/java/LogITBackend/LogIT/config/WebConfig.java b/src/main/java/LogITBackend/LogIT/config/WebConfig.java new file mode 100644 index 0000000..248b418 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/WebConfig.java @@ -0,0 +1,18 @@ +package LogITBackend.LogIT.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +// 전체 애플리케이션에 CORS 허용 +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173") + .allowedMethods("*") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java index cb85ba6..f18ad91 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -31,6 +31,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + .cors(Customizer.withDefaults()) // CORS 설정 .csrf(AbstractHttpConfigurer::disable) // 추가해주어야함. // 폼 로그인 비활성화 .formLogin(AbstractHttpConfigurer::disable) diff --git a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java index 69318e2..68c48d6 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java @@ -1,5 +1,7 @@ package LogITBackend.LogIT.config.security.oauth; +import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.converter.UserConverter; import LogITBackend.LogIT.domain.Users; import LogITBackend.LogIT.jwt.JwtUtils; import LogITBackend.LogIT.repository.UserRepository; @@ -16,12 +18,18 @@ import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; +import java.util.Map; @Component @Slf4j @@ -38,36 +46,54 @@ public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler private final UserCommandService userCommandService; private final JwtUtils jwtUtils; private final RedisTemplate redisTemplate; + private final OAuth2AuthorizedClientService authorizedClientService; @Override @Transactional public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; + OAuth2User oauthUser = oauthToken.getPrincipal(); + + // 클라이언트 정보 (깃허브, 구글 등) + String registrationId = oauthToken.getAuthorizedClientRegistrationId(); + + // accessToken 꺼내기 + OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient( + registrationId, + oauthToken.getName() + ); + OAuth2AccessToken accessToken = client.getAccessToken(); + + // 사용자 정보 Map (GitHub의 경우 login, id, name 등 포함) + Map attributes = oauthUser.getAttributes(); + + // GitHub 기준 예시 (플랫폼마다 다름) + String providerId = String.valueOf(attributes.get("id")); + String nickname = (String) attributes.get("login"); + // 여기에 로그인 성공 후 처리할 내용을 작성하기! DefaultOAuth2User oAuth2User = (DefaultOAuth2User) authentication.getPrincipal(); log.info("Suceess!---------------------"); - // 프론트엔드에 보내줄 accessToken 및 refreshToken생성 - Users getUser = userRepository.findByProviderId(oAuth2User.getName()).orElseThrow(null); - String accessToken = userCommandService.generateAccessToken(getUser.getId(), accessExpTime); - String key = "users:" + getUser.getId().toString(); - String refreshToken = userCommandService.generateAndSaveRefreshToken(key, refreshExpTime); - - // db에 인가처리할 accessToken저장 (개발자 테스트용) - getUser.updateAccessToken(accessToken); + Users existUser = userRepository.findByProviderId(providerId).orElse(null); - // 프론트엔드에 accessToken과 refreshToken를 redirect url로 보내주기 - // ✅ refreshToken 쿠키에 저장 - Cookie cookie = new Cookie("refreshToken", refreshToken); - cookie.setHttpOnly(true); - cookie.setPath("/"); - cookie.setMaxAge(7 * 24 * 60 * 60 * 2); // 14일 - response.addCookie(cookie); + // user dto 생성해서 user저장 + if(existUser == null) { + Users newUser = UserConverter.githubDatatoUsers( + UserRequestDTO.GithubSignUpRequestDTO.builder() + .providerId(providerId) + .nickname(nickname) + .githubAccessToken(accessToken.getTokenValue()) + .build() + ); + userRepository.save(newUser); + } - // ✅ accessToken 쿼리 파라미터로 전달 + // ✅ provideId 쿼리 파라미터로 전달 String redirectUrl = UriComponentsBuilder .fromUriString(redirectUri) - .queryParam("accessToken", accessToken) + .queryParam("providerId", providerId) .build() .toUriString(); diff --git a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java index 59afdc7..bf41222 100644 --- a/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2UserService.java @@ -23,36 +23,36 @@ public class CustomOAuth2UserService extends DefaultOAuth2UserService { // 깃허브로 부터 받은 userRequest 데이터에 대한 후처리 되는 함수 @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{ - log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); - log.info("getAccessToken: {}", userRequest.getAccessToken()); - log.info("getAttributes: {}", super.loadUser(userRequest).getAttributes()); - System.out.println("userRequest:"+userRequest); - System.out.println("getClientRegistraion:"+userRequest.getClientRegistration()); //client에 대한 정보들이 받아짐 - System.out.println("getAccessToken:"+userRequest.getAccessToken().getTokenValue()); - System.out.println("getAttributes:"+super.loadUser(userRequest).getAttributes()); //유저 정보를 받아옴 +// log.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); +// log.info("getAccessToken: {}", userRequest.getAccessToken()); +// log.info("getAttributes: {}", super.loadUser(userRequest).getAttributes()); +// System.out.println("userRequest:"+userRequest); +// System.out.println("getClientRegistraion:"+userRequest.getClientRegistration()); //client에 대한 정보들이 받아짐 +// System.out.println("getAccessToken:"+userRequest.getAccessToken().getTokenValue()); +// System.out.println("getAttributes:"+super.loadUser(userRequest).getAttributes()); //유저 정보를 받아옴 OAuth2User oAuth2User = super.loadUser(userRequest); // 실제 사용자 정보 요청 - // 사용자 정보 map - Map attributes = oAuth2User.getAttributes(); - - // 정보 추출 - String providerId = String.valueOf(attributes.get("id")); - String nickname = (String) attributes.get("login"); - - Users existUser = userRepository.findByProviderId(providerId).orElse(null); - - if (existUser == null) { - // user dto 생성해서 user저장 - Users newUser = UserConverter.githubDatatoUsers( - UserRequestDTO.GithubSignUpRequestDTO.builder() - .providerId(providerId) - .nickname(nickname) - .githubAccessToken(userRequest.getAccessToken().getTokenValue()) - .build() - ); - userRepository.save(newUser); - } +// // 사용자 정보 map +// Map attributes = oAuth2User.getAttributes(); +// +// // 정보 추출 +// String providerId = String.valueOf(attributes.get("id")); +// String nickname = (String) attributes.get("login"); +// +// Users existUser = userRepository.findByProviderId(providerId).orElse(null); +// +// if (existUser == null) { +// // user dto 생성해서 user저장 +// Users newUser = UserConverter.githubDatatoUsers( +// UserRequestDTO.GithubSignUpRequestDTO.builder() +// .providerId(providerId) +// .nickname(nickname) +// .githubAccessToken(userRequest.getAccessToken().getTokenValue()) +// .build() +// ); +// userRepository.save(newUser); +// } return oAuth2User; } diff --git a/src/main/java/LogITBackend/LogIT/controller/UserController.java b/src/main/java/LogITBackend/LogIT/controller/UserController.java index a2be584..412e735 100644 --- a/src/main/java/LogITBackend/LogIT/controller/UserController.java +++ b/src/main/java/LogITBackend/LogIT/controller/UserController.java @@ -46,4 +46,15 @@ public ApiResponse signIn( userCommandService.signIn(request) ); } + + @Operation(summary = "깃허브 연동", description = + "# 깃허브 연동 API 입니다. providerId를 입력해주세요." + ) + @PostMapping("/register/github") + public Object register( + @RequestBody @Valid UserRequestDTO.GithubRegisterRequestDTO request + ) { + userCommandService.register(request); + return ApiResponse.onSuccess(null); + } } diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 239dc83..42a4d49 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -86,4 +86,16 @@ public void encodePassword(String password) { public void updateAccessToken(String accessToken) { this.accesstoken = accessToken; } + + public void updateGithubAccessToken(String githubAccessToken) { + this.githubAccesstoken = githubAccessToken; + } + + public void updateProviderId(String providerId) { + this.providerId = providerId; + } + + public void updateGithubNickname(String nickname) { + this.githubNickname = nickname; + } } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java index 81047d6..9d871e9 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandService.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -9,4 +9,5 @@ public interface UserCommandService { UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDTO request); String generateAccessToken(Long userId, int accessExpTime); String generateAndSaveRefreshToken(String key, int refreshExpTime); + void register(UserRequestDTO.GithubRegisterRequestDTO request); } diff --git a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java index b8faed5..50e0f95 100644 --- a/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -3,6 +3,8 @@ import LogITBackend.LogIT.DTO.UserResponseDTO; import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; import LogITBackend.LogIT.apiPayload.exception.handler.ExceptionHandler; +import LogITBackend.LogIT.config.security.SecurityConfig; +import LogITBackend.LogIT.config.security.SecurityUtil; import LogITBackend.LogIT.converter.UserConverter; import LogITBackend.LogIT.jwt.JwtUtils; import LogITBackend.LogIT.repository.UserRepository; @@ -60,6 +62,19 @@ public UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDT return UserConverter.toUserSignInResultDTO(getUser, accessToken, refreshToken); } + @Override + public void register(UserRequestDTO.GithubRegisterRequestDTO request) { + Users getGithubInfo = userRepository.findByProviderId(request.getProviderId()) + .orElseThrow(() -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); // 후에 errorstatus바꾸기 + Long userId = SecurityUtil.getCurrentUserId(); + Users getUser = userRepository.findById(userId) + .orElseThrow(() -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); + getUser.updateGithubAccessToken(getGithubInfo.getGithubAccesstoken()); + getUser.updateProviderId(getGithubInfo.getProviderId()); + getUser.updateGithubNickname(getGithubInfo.getNickname()); + userRepository.delete(getGithubInfo); + } + public String generateAccessToken(Long userId, int accessExpTime) { // 인증 완료 후 jwt토큰(accessToken) 생성 Map valueMap = Map.of( From 87a63726a2102a60d1e89ebeacb72b02704cfed8 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Tue, 15 Apr 2025 14:49:46 +0900 Subject: [PATCH 24/39] =?UTF-8?q?feat:=20#34=20=EA=B8=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20CRUD=20API=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/RecordRequestDTO.java | 28 +++++ .../LogIT/DTO/RecordResponseDTO.java | 41 +++++++ .../apiPayload/code/status/ErrorStatus.java | 5 +- .../LogIT/controller/RecordController.java | 102 ++++++++++++++++++ .../LogIT/converter/RecordConverter.java | 48 +++++++++ .../LogITBackend/LogIT/domain/Records.java | 52 +++++++++ .../java/LogITBackend/LogIT/domain/Users.java | 3 + .../LogIT/repository/RecordRepository.java | 13 +++ .../LogIT/service/RecordCommandService.java | 10 ++ .../service/RecordCommandServiceImpl.java | 49 +++++++++ .../LogIT/service/RecordQueryService.java | 10 ++ .../LogIT/service/RecordQueryServiceImpl.java | 36 +++++++ 12 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/RecordRequestDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/controller/RecordController.java create mode 100644 src/main/java/LogITBackend/LogIT/converter/RecordConverter.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/Records.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/RecordRepository.java create mode 100644 src/main/java/LogITBackend/LogIT/service/RecordCommandService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/RecordCommandServiceImpl.java create mode 100644 src/main/java/LogITBackend/LogIT/service/RecordQueryService.java create mode 100644 src/main/java/LogITBackend/LogIT/service/RecordQueryServiceImpl.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/RecordRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RecordRequestDTO.java new file mode 100644 index 0000000..06baf5d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/RecordRequestDTO.java @@ -0,0 +1,28 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Optional; + +public class RecordRequestDTO { + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class CreateRecordRequestDTO { + private String title; + private String content; + } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class EditRecordRequestDTO { + private Optional title; + private Optional content; + } +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java new file mode 100644 index 0000000..65119b2 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java @@ -0,0 +1,41 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +public class RecordResponseDTO { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class RecordResultDTO { + Long recordId; + String title; + String content; + LocalDateTime createdAt; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GetRecordListResultDTO { + List getRecordResultDTOList; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GetRecordResultDTO { + Long recordId; + String title; + String content; + LocalDateTime createdAt; + } +} diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index 4910360..fd990f0 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -37,7 +37,10 @@ public enum ErrorStatus implements BaseErrorCode { REPO_NOT_FOUND(HttpStatus.BAD_REQUEST, "REPO_4001", "레포지토리가 존재하지 않습니다."), // owner 관련 응답 5000 - OWNER_NOT_FOUND(HttpStatus.BAD_REQUEST, "OWNER_5001", "OWNER이 존재하지 않습니다."); + OWNER_NOT_FOUND(HttpStatus.BAD_REQUEST, "OWNER_5001", "OWNER이 존재하지 않습니다."), + + // record 관련 응답 6000 + RECORD_NOT_FOUND(HttpStatus.BAD_REQUEST, "RECORD_6001", "기록이 존재하지 않습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/LogITBackend/LogIT/controller/RecordController.java b/src/main/java/LogITBackend/LogIT/controller/RecordController.java new file mode 100644 index 0000000..78836ca --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/RecordController.java @@ -0,0 +1,102 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.DTO.RecordRequestDTO; +import LogITBackend.LogIT.DTO.RecordResponseDTO; +import LogITBackend.LogIT.apiPayload.ApiResponse; +import LogITBackend.LogIT.converter.RecordConverter; +import LogITBackend.LogIT.domain.Records; +import LogITBackend.LogIT.service.RecordCommandService; +import LogITBackend.LogIT.service.RecordQueryService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/records") +@RequiredArgsConstructor +public class RecordController { + private final RecordCommandService recordCommandService; + private final RecordQueryService recordQueryService; + + @Operation(summary = "글 작성", + description = """ + 글 제목과 글 내용을 작성해주세요. + 저장된 글의 ID, 글 제목, 글 내용 그리고 생성 날짜가 반환됩니다. + """ + ) + + @PostMapping(value = "/") + public ApiResponse createRecord( + @RequestBody RecordRequestDTO.CreateRecordRequestDTO request + ){ + Records records = recordCommandService.createRecord(request); + + return ApiResponse.onSuccess( + RecordConverter.toRecordResultDTO(records) + ); + } + + @Operation(summary = "글 삭제", + description = """ + 삭제할 글 id를 작성해주세요. + 해당 글이 삭제됩니다. + """ + ) + @DeleteMapping("/delete/{recordId}") + public ApiResponse deleteRecord( + @PathVariable Long recordId + ) { + recordCommandService.deleteRecord(recordId); + return ApiResponse.onSuccess(null); + } + + @Operation(summary = "글 수정", + description = """ + 글 id -> Path Variable \n + 수정할 글의 제목, 내용 -> body 를 작성해주세요.\n + ⭐️ 수정 하는 항목만 보내주세요. ⭐️\n + """ + ) + @PatchMapping(value = "/edit/{recordId}") + public ApiResponse editRecord( + @PathVariable Long recordId, + @RequestBody RecordRequestDTO.EditRecordRequestDTO request + ) { + Records records = recordCommandService.editRecord(recordId, request); + return ApiResponse.onSuccess( + RecordConverter.toRecordResultDTO(records) + ); + } + + @Operation(summary = "글 전체 조회", + description = """ + 글 전체 조회 api입니다.\n + """ + ) + @GetMapping("/list") + public ApiResponse getRecordList() { + List recordsList = recordQueryService.getRecordList(); + + return ApiResponse.onSuccess( + RecordConverter.toGetRecordListResultDTO(recordsList) + ); + } + + @Operation(summary = "특정 글 조회", + description = """ + 특정 글 조회 api입니다.\n + """ + ) + @GetMapping("/{recordId}") + public ApiResponse getRecordDetail( + @PathVariable Long recordId + ) { + Records recordDetail = recordQueryService.getRecordDetail(recordId); + + return ApiResponse.onSuccess( + RecordConverter.toRecordResultDTO(recordDetail) + ); + } +} diff --git a/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java new file mode 100644 index 0000000..0241eed --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java @@ -0,0 +1,48 @@ +package LogITBackend.LogIT.converter; + +import LogITBackend.LogIT.DTO.RecordRequestDTO; +import LogITBackend.LogIT.DTO.RecordResponseDTO; +import LogITBackend.LogIT.domain.Records; + +import java.util.List; +import java.util.stream.Collectors; + +public class RecordConverter { + // Convert RecordRequestDTO to Records entity + public static Records toRecords(RecordRequestDTO.CreateRecordRequestDTO request) { + return Records.builder() + .title(request.getTitle()) + .content(request.getContent()) + .build(); + } + + // Convert Records entity to RecordResponseDTO + public static RecordResponseDTO.RecordResultDTO toRecordResultDTO(Records record) { + return RecordResponseDTO.RecordResultDTO.builder() + .recordId(record.getId()) + .title(record.getTitle()) + .content(record.getContent()) + .createdAt(record.getCreatedAt()) + .build(); + } + + public static RecordResponseDTO.GetRecordListResultDTO toGetRecordListResultDTO(List recordList) { + List getRecordResultDTOList = recordList.stream() + .map(RecordConverter::toGetRecordResultDTO).collect(Collectors.toList()); + + return RecordResponseDTO.GetRecordListResultDTO.builder() + .getRecordResultDTOList(getRecordResultDTOList) + .build(); + } + + public static RecordResponseDTO.GetRecordResultDTO toGetRecordResultDTO(Records record) { + String contentPreview = record.getContent().length() > 20 ? record.getContent().substring(0, 20) + "..." : record.getContent(); + + return RecordResponseDTO.GetRecordResultDTO.builder() + .recordId(record.getId()) + .title(record.getTitle()) + .content(contentPreview) + .createdAt(record.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Records.java b/src/main/java/LogITBackend/LogIT/domain/Records.java new file mode 100644 index 0000000..7383fd1 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Records.java @@ -0,0 +1,52 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Records extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 100) + private String title; + + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users users; + + public void setUsers(Users users) { + // 기존에 이미 등록되어 있던 관계를 제거 + if (this.users != null) { + this.users.getRecordsList().remove(this); + } + + this.users = users; + + // 양방향 관계를 설정 + if (users != null) { + users.getRecordsList().add(this); + } + } + + public void updateTitle(String title) { + this.title = title; + } + + public void updateContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Users.java b/src/main/java/LogITBackend/LogIT/domain/Users.java index 42a4d49..23ad4dc 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Users.java +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -79,6 +79,9 @@ public class Users extends BaseEntity { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List ownerList = new ArrayList<>(); + @OneToMany(mappedBy = "users", cascade = CascadeType.ALL) + private List recordsList = new ArrayList<>(); + public void encodePassword(String password) { this.password = password; } diff --git a/src/main/java/LogITBackend/LogIT/repository/RecordRepository.java b/src/main/java/LogITBackend/LogIT/repository/RecordRepository.java new file mode 100644 index 0000000..7f5f5e4 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/RecordRepository.java @@ -0,0 +1,13 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Records; +import LogITBackend.LogIT.domain.Users; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface RecordRepository extends JpaRepository { + List findAllByUsersOrderByCreatedAtDesc(Users user); +} diff --git a/src/main/java/LogITBackend/LogIT/service/RecordCommandService.java b/src/main/java/LogITBackend/LogIT/service/RecordCommandService.java new file mode 100644 index 0000000..731f733 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/RecordCommandService.java @@ -0,0 +1,10 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.RecordRequestDTO; +import LogITBackend.LogIT.domain.Records; + +public interface RecordCommandService { + Records createRecord(RecordRequestDTO.CreateRecordRequestDTO request); + Records editRecord(Long recordId, RecordRequestDTO.EditRecordRequestDTO request); + void deleteRecord(Long recordId); +} diff --git a/src/main/java/LogITBackend/LogIT/service/RecordCommandServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/RecordCommandServiceImpl.java new file mode 100644 index 0000000..20e7dd4 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/RecordCommandServiceImpl.java @@ -0,0 +1,49 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.RecordRequestDTO; +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import LogITBackend.LogIT.apiPayload.exception.handler.ExceptionHandler; +import LogITBackend.LogIT.config.security.SecurityUtil; +import LogITBackend.LogIT.converter.RecordConverter; +import LogITBackend.LogIT.domain.Records; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.RecordRepository; +import LogITBackend.LogIT.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RecordCommandServiceImpl implements RecordCommandService{ + private final RecordRepository recordRepository; + private final UserRepository userRepository; + + @Override + public Records createRecord(RecordRequestDTO.CreateRecordRequestDTO request) { + Records newRecord = RecordConverter.toRecords(request); + Long userId = SecurityUtil.getCurrentUserId(); + Users user = userRepository.findById(userId).orElseThrow(() -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); + + newRecord.setUsers(user); + + return recordRepository.save(newRecord); + } + + @Override + @Transactional + public Records editRecord(Long recordId, RecordRequestDTO.EditRecordRequestDTO request) { + // 해당유저의 기록이 맞는지 확인하기 <- 추후에 + Records getRecord = recordRepository.findById(recordId).orElseThrow(() -> new ExceptionHandler(ErrorStatus.RECORD_NOT_FOUND)); + request.getTitle().ifPresent(getRecord::updateTitle); + request.getContent().ifPresent(getRecord::updateContent); + return getRecord; + } + + @Override + public void deleteRecord(Long recordId) { + // 해당유저의 기록이 맞는지 확인하기 <- 추후에 + Records getRecord = recordRepository.findById(recordId).orElseThrow(() -> new ExceptionHandler(ErrorStatus.RECORD_NOT_FOUND)); + recordRepository.delete(getRecord); + } +} diff --git a/src/main/java/LogITBackend/LogIT/service/RecordQueryService.java b/src/main/java/LogITBackend/LogIT/service/RecordQueryService.java new file mode 100644 index 0000000..ba7c181 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/RecordQueryService.java @@ -0,0 +1,10 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.domain.Records; + +import java.util.List; + +public interface RecordQueryService { + List getRecordList(); + Records getRecordDetail(Long recordId); +} diff --git a/src/main/java/LogITBackend/LogIT/service/RecordQueryServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/RecordQueryServiceImpl.java new file mode 100644 index 0000000..2e68c14 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/RecordQueryServiceImpl.java @@ -0,0 +1,36 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.apiPayload.code.status.ErrorStatus; +import LogITBackend.LogIT.apiPayload.exception.handler.ExceptionHandler; +import LogITBackend.LogIT.config.security.SecurityUtil; +import LogITBackend.LogIT.domain.Records; +import LogITBackend.LogIT.domain.Users; +import LogITBackend.LogIT.repository.RecordRepository; +import LogITBackend.LogIT.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RecordQueryServiceImpl implements RecordQueryService{ + + private final UserRepository userRepository; + private final RecordRepository recordRepository; + + @Override + public List getRecordList() { + Long userId = SecurityUtil.getCurrentUserId(); + Users getUser = userRepository.findById(userId).orElseThrow(() -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); + List recordsList = recordRepository.findAllByUsersOrderByCreatedAtDesc(getUser); + return recordsList; + } + + @Override + public Records getRecordDetail(Long recordId) { + // 해당유저의 기록이 맞는지 확인하기 <- 추후에 + Records getRecord = recordRepository.findById(recordId).orElseThrow(() -> new ExceptionHandler(ErrorStatus.RECORD_NOT_FOUND)); + return getRecord; + } +} From 149250b9d6f80ece7c17a5f9bdfffaa27ef6a0b5 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Wed, 16 Apr 2025 20:15:41 +0900 Subject: [PATCH 25/39] =?UTF-8?q?feat:=20#36=20=EA=B8=80=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20api=EC=99=80=20?= =?UTF-8?q?=EA=B8=80=20=EC=9E=91=EC=84=B1=20api=EC=9D=98=20response?= =?UTF-8?q?=EC=97=90=20author=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/DTO/RecordResponseDTO.java | 1 + .../LogIT/controller/RecordController.java | 8 ++++---- .../LogIT/converter/RecordConverter.java | 14 +++++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java index 65119b2..48002be 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java @@ -34,6 +34,7 @@ public static class GetRecordListResultDTO { @AllArgsConstructor public static class GetRecordResultDTO { Long recordId; + String author; String title; String content; LocalDateTime createdAt; diff --git a/src/main/java/LogITBackend/LogIT/controller/RecordController.java b/src/main/java/LogITBackend/LogIT/controller/RecordController.java index 78836ca..f91cec6 100644 --- a/src/main/java/LogITBackend/LogIT/controller/RecordController.java +++ b/src/main/java/LogITBackend/LogIT/controller/RecordController.java @@ -28,13 +28,13 @@ public class RecordController { ) @PostMapping(value = "/") - public ApiResponse createRecord( + public ApiResponse createRecord( @RequestBody RecordRequestDTO.CreateRecordRequestDTO request ){ Records records = recordCommandService.createRecord(request); return ApiResponse.onSuccess( - RecordConverter.toRecordResultDTO(records) + RecordConverter.toGetRecordDetailResultDTO(records) ); } @@ -90,13 +90,13 @@ public ApiResponse getRecordList() { """ ) @GetMapping("/{recordId}") - public ApiResponse getRecordDetail( + public ApiResponse getRecordDetail( @PathVariable Long recordId ) { Records recordDetail = recordQueryService.getRecordDetail(recordId); return ApiResponse.onSuccess( - RecordConverter.toRecordResultDTO(recordDetail) + RecordConverter.toGetRecordDetailResultDTO(recordDetail) ); } } diff --git a/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java index 0241eed..e5e4fee 100644 --- a/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java +++ b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java @@ -36,13 +36,25 @@ public static RecordResponseDTO.GetRecordListResultDTO toGetRecordListResultDTO( } public static RecordResponseDTO.GetRecordResultDTO toGetRecordResultDTO(Records record) { - String contentPreview = record.getContent().length() > 20 ? record.getContent().substring(0, 20) + "..." : record.getContent(); + String contentPreview = record.getContent().length() > 70 ? record.getContent().substring(0, 70) + "..." : record.getContent(); return RecordResponseDTO.GetRecordResultDTO.builder() .recordId(record.getId()) + .author(record.getUsers().getNickname()) .title(record.getTitle()) .content(contentPreview) .createdAt(record.getCreatedAt()) .build(); } + + // Convert Records entity to RecordResponseDTO + public static RecordResponseDTO.GetRecordResultDTO toGetRecordDetailResultDTO(Records record) { + return RecordResponseDTO.GetRecordResultDTO.builder() + .recordId(record.getId()) + .author(record.getUsers().getNickname()) + .title(record.getTitle()) + .content(record.getContent()) + .createdAt(record.getCreatedAt()) + .build(); + } } From 613a8dfca87d2ce39dcb8e52fcba5b20c501a53b Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Wed, 16 Apr 2025 20:41:03 +0900 Subject: [PATCH 26/39] =?UTF-8?q?feat:=20#38=20=EA=B8=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20api=EC=9D=98=20response=EC=9D=98=20content=EA=B0=80?= =?UTF-8?q?=2070=EC=9E=90=20=EC=9D=B4=EC=83=81=EC=9D=B4=EB=9D=BC=EB=A9=B4?= =?UTF-8?q?=20=EC=9E=90=EB=A5=B4=EA=B3=A0=20...=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogITBackend/LogIT/DTO/RecordResponseDTO.java | 12 ++++++++++++ .../LogIT/controller/RecordController.java | 4 ++-- .../LogIT/converter/RecordConverter.java | 12 ++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java index 48002be..9983718 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java @@ -39,4 +39,16 @@ public static class GetRecordResultDTO { String content; LocalDateTime createdAt; } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class CreateRecordResultDTO { + Long recordId; + String author; + String title; + String content; + LocalDateTime createdAt; + } } diff --git a/src/main/java/LogITBackend/LogIT/controller/RecordController.java b/src/main/java/LogITBackend/LogIT/controller/RecordController.java index f91cec6..2ba9716 100644 --- a/src/main/java/LogITBackend/LogIT/controller/RecordController.java +++ b/src/main/java/LogITBackend/LogIT/controller/RecordController.java @@ -28,13 +28,13 @@ public class RecordController { ) @PostMapping(value = "/") - public ApiResponse createRecord( + public ApiResponse createRecord( @RequestBody RecordRequestDTO.CreateRecordRequestDTO request ){ Records records = recordCommandService.createRecord(request); return ApiResponse.onSuccess( - RecordConverter.toGetRecordDetailResultDTO(records) + RecordConverter.toCreateRecordResultDTO(records) ); } diff --git a/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java index e5e4fee..13ff4ad 100644 --- a/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java +++ b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java @@ -57,4 +57,16 @@ public static RecordResponseDTO.GetRecordResultDTO toGetRecordDetailResultDTO(Re .createdAt(record.getCreatedAt()) .build(); } + + // Convert Records entity to RecordResponseDTO + public static RecordResponseDTO.CreateRecordResultDTO toCreateRecordResultDTO(Records record) { + String contentPreview = record.getContent().length() > 70 ? record.getContent().substring(0, 70) + "..." : record.getContent(); + return RecordResponseDTO.CreateRecordResultDTO.builder() + .recordId(record.getId()) + .author(record.getUsers().getNickname()) + .title(record.getTitle()) + .content(contentPreview) + .createdAt(record.getCreatedAt()) + .build(); + } } From e8021df5705ccd79437f9e068243f8f065a2d2cf Mon Sep 17 00:00:00 2001 From: mino Date: Thu, 17 Apr 2025 15:32:07 +0900 Subject: [PATCH 27/39] =?UTF-8?q?refactor/-=EC=BB=A4=EB=B0=8B=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EC=A0=9C=ED=95=9C=20100=EA=B0=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 3f4774d..7c67d34 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -35,7 +35,7 @@ public class GithubServiceImpl implements GithubService { @Override @Transactional public List getCommits(String ownerName, String repoName) { - String url = String.format("https://api.github.com/repos/%s/%s/commits", ownerName, repoName); + String url = String.format("https://api.github.com/repos/%s/%s/commits?per_page=100", ownerName, repoName); Long userId = SecurityUtil.getCurrentUserId(); From a70215cb492ac6320d4d58d48c4c7e7dea30b50d Mon Sep 17 00:00:00 2001 From: mino Date: Thu, 15 May 2025 19:53:26 +0900 Subject: [PATCH 28/39] =?UTF-8?q?branch=20entity=20,=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LogIT/DTO/BranchResponseDTO.java | 10 +++ .../LogIT/DTO/CommitResponseDTO.java | 2 +- .../apiPayload/code/status/ErrorStatus.java | 5 +- .../LogIT/controller/GithubController.java | 29 ++++-- .../LogITBackend/LogIT/domain/Branch.java | 33 +++++++ .../LogITBackend/LogIT/domain/Commit.java | 13 +-- .../java/LogITBackend/LogIT/domain/Repo.java | 3 +- .../LogIT/repository/BranchRepository.java | 11 +++ .../LogIT/repository/CommitRepository.java | 8 +- .../LogIT/repository/OwnerRepository.java | 2 +- .../LogIT/service/GithubService.java | 4 +- .../LogIT/service/GithubServiceImpl.java | 90 +++++++++++++++++-- 12 files changed, 172 insertions(+), 38 deletions(-) create mode 100644 src/main/java/LogITBackend/LogIT/DTO/BranchResponseDTO.java create mode 100644 src/main/java/LogITBackend/LogIT/domain/Branch.java create mode 100644 src/main/java/LogITBackend/LogIT/repository/BranchRepository.java diff --git a/src/main/java/LogITBackend/LogIT/DTO/BranchResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/BranchResponseDTO.java new file mode 100644 index 0000000..0834c7c --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/BranchResponseDTO.java @@ -0,0 +1,10 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BranchResponseDTO { + private String branchName; +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java index 1521111..70d3468 100644 --- a/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java @@ -21,7 +21,7 @@ public class CommitResponseDTO { public static CommitResponseDTO fromEntity(Commit commit) { return new CommitResponseDTO( commit.getId(), - commit.getRepo().getId(), + commit.getBranch().getId(), commit.getMessage(), commit.getStats(), commit.getDate() diff --git a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java index fd990f0..0d24627 100644 --- a/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -40,7 +40,10 @@ public enum ErrorStatus implements BaseErrorCode { OWNER_NOT_FOUND(HttpStatus.BAD_REQUEST, "OWNER_5001", "OWNER이 존재하지 않습니다."), // record 관련 응답 6000 - RECORD_NOT_FOUND(HttpStatus.BAD_REQUEST, "RECORD_6001", "기록이 존재하지 않습니다."); + RECORD_NOT_FOUND(HttpStatus.BAD_REQUEST, "RECORD_6001", "기록이 존재하지 않습니다."), + + // branch 관련 응답 7000 + BRANCH_NOT_FOUND(HttpStatus.BAD_REQUEST, "BRANCH_7001", "브랜치가 존재하지 않습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/LogITBackend/LogIT/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java index 01b7d9d..d61da8b 100644 --- a/src/main/java/LogITBackend/LogIT/controller/GithubController.java +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -16,12 +16,13 @@ public class GithubController { private final GithubService githubService; - @GetMapping("/{owners}/{repos}/commits") + @GetMapping("/{owners}/{repos}/{branches}/commits") public ResponseEntity> getCommits( @PathVariable("owners") String owners, - @PathVariable("repos") String repos + @PathVariable("repos") String repos, + @PathVariable("branches") String branches ) { - List commits = githubService.getCommits(owners, repos); + List commits = githubService.getCommits(owners, repos, branches); return ResponseEntity.ok(ApiResponse.onSuccess(commits)); } @@ -35,12 +36,6 @@ public ResponseEntity> getCommitDetails( return ResponseEntity.ok(ApiResponse.onSuccess(commitDetail)); } - @GetMapping("/users/repos") - public ResponseEntity> getUsersRepos() { - GithubRepoResponse repos = githubService.getUsersRepos(); - return ResponseEntity.ok(ApiResponse.onSuccess(repos)); - } - @GetMapping("/users/org") public ResponseEntity> getUserOrgs() { List orgs = githubService.getUserOrgs(); @@ -54,4 +49,20 @@ public ResponseEntity> getUserOrgsRepos( GithubRepoResponse repos = githubService.getUserOrgsRepos(owners); return ResponseEntity.ok(ApiResponse.onSuccess(repos)); } + + @GetMapping("/users/repos") + public ResponseEntity> getUsersRepos() { + GithubRepoResponse repos = githubService.getUsersRepos(); + return ResponseEntity.ok(ApiResponse.onSuccess(repos)); + } + + @GetMapping("/{owners}/{repos}/branches") + public ResponseEntity> getUserBranches( + @PathVariable("owners") String owners, + @PathVariable("repos") String repos + ) { + List branches = githubService.getUserBranches(owners, repos); + return ResponseEntity.ok(ApiResponse.onSuccess(branches)); + + } } diff --git a/src/main/java/LogITBackend/LogIT/domain/Branch.java b/src/main/java/LogITBackend/LogIT/domain/Branch.java new file mode 100644 index 0000000..6a0c844 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Branch.java @@ -0,0 +1,33 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Entity +@Table(name = "branches") +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class Branch extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "branch_name", nullable = false, length = 50) + private String name; + + @ManyToOne + @JoinColumn(name = "repo_id") + private Repo repo; + + @OneToMany(mappedBy = "branch", cascade = CascadeType.ALL) + private List commits; + +} diff --git a/src/main/java/LogITBackend/LogIT/domain/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java index bb69fe2..285341a 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Commit.java +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -2,7 +2,6 @@ import LogITBackend.LogIT.domain.common.BaseEntity; import com.fasterxml.jackson.annotation.JsonManagedReference; -import jakarta.annotation.Nonnull; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -20,19 +19,10 @@ @AllArgsConstructor @NoArgsConstructor public class Commit extends BaseEntity { - @Id @Column(length = 40) private String id; // commit SHA - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "repo_id", nullable = false) - private Repo repo; - -// @ManyToOne(fetch = FetchType.LAZY) -// @JoinColumn(name = "user_id", nullable = false) -// private Users user; - @Column(length = 255) private String message; @@ -45,4 +35,7 @@ public class Commit extends BaseEntity { @JsonManagedReference private List files = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "branch_id", nullable = false) + private Branch branch; } diff --git a/src/main/java/LogITBackend/LogIT/domain/Repo.java b/src/main/java/LogITBackend/LogIT/domain/Repo.java index 5b19d9b..2277f72 100644 --- a/src/main/java/LogITBackend/LogIT/domain/Repo.java +++ b/src/main/java/LogITBackend/LogIT/domain/Repo.java @@ -1,6 +1,5 @@ package LogITBackend.LogIT.domain; -import LogITBackend.LogIT.domain.common.BaseEntity; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -28,7 +27,7 @@ public class Repo { private String repoName; @OneToMany(mappedBy = "repo", cascade = CascadeType.ALL) - private List commit; + private List branches; private LocalDateTime createdAt; diff --git a/src/main/java/LogITBackend/LogIT/repository/BranchRepository.java b/src/main/java/LogITBackend/LogIT/repository/BranchRepository.java new file mode 100644 index 0000000..afd8a21 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/BranchRepository.java @@ -0,0 +1,11 @@ +package LogITBackend.LogIT.repository; + +import LogITBackend.LogIT.domain.Branch; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface BranchRepository extends JpaRepository { + + Optional findByRepoIdAndName(Long RepoId, String branchName); +} diff --git a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java index d3708e6..1ae8fa1 100644 --- a/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java @@ -10,9 +10,9 @@ import java.util.Optional; public interface CommitRepository extends JpaRepository { - @Query("SELECT MAX(c.date) FROM Commit c WHERE c.repo.id = :repoId") - Optional findLatestCommitDateByUserId(Long repoId); + @Query("SELECT MAX(c.date) FROM Commit c WHERE c.branch.id = :branchId") + Optional findLatestCommitDateByUserId(Long branchId); - @Query("SELECT c FROM Commit c WHERE c.repo.id = :repoId") - List findAllByRepoId(Long repoId); + @Query("SELECT c FROM Commit c WHERE c.branch.id = :branchId") + List findAllByBranchId(Long branchId); } diff --git a/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java b/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java index 0dccefe..018267e 100644 --- a/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java +++ b/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java @@ -11,7 +11,7 @@ public interface OwnerRepository extends JpaRepository { @Query("SELECT o FROM Owner o WHERE o.user.id = :userId AND o.ownerName = :ownerName") - Optional findByUserIdAndOwnerName(@Param("userId") Long userId, @Param("ownerName") String ownerName); + Optional findByUserIdAndOwnerName(@Param("userId") Long userId, @Param("ownerName") String ownerName); List findAllByUserId(Long userId); diff --git a/src/main/java/LogITBackend/LogIT/service/GithubService.java b/src/main/java/LogITBackend/LogIT/service/GithubService.java index bb42701..88f0618 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubService.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -5,7 +5,7 @@ import java.util.List; public interface GithubService { - List getCommits(String owner, String repo); + List getCommits(String owner, String repo, String branch); CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); @@ -14,4 +14,6 @@ public interface GithubService { List getUserOrgs(); GithubRepoResponse getUserOrgsRepos(String owners); + + List getUserBranches(String owner, String repo); } diff --git a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java index 7c67d34..7ff2a8d 100644 --- a/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -31,11 +31,12 @@ public class GithubServiceImpl implements GithubService { private final CommitRepository commitRepository; private final CommitParentRepository commitParentRepository; private final FileRepository fileRepository; + private final BranchRepository branchRepository; @Override @Transactional - public List getCommits(String ownerName, String repoName) { - String url = String.format("https://api.github.com/repos/%s/%s/commits?per_page=100", ownerName, repoName); + public List getCommits(String ownerName, String repoName, String branchName) { + String url = String.format("https://api.github.com/repos/%s/%s/commits?per_page=100&sha=%s", ownerName, repoName, branchName); Long userId = SecurityUtil.getCurrentUserId(); @@ -47,14 +48,18 @@ public List getCommits(String ownerName, String repoName) { throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); } - Owner owner = ownerRepository.findByUserIdAndOwnerName(userId,ownerName) + Owner owner = ownerRepository.findByUserIdAndOwnerName(userId, ownerName) .orElseThrow(() -> new GeneralException(ErrorStatus.OWNER_NOT_FOUND)); Repo repo = repoRepository.findByOwnerIdAndRepoName(owner.getId(), repoName) .orElseThrow(() -> new GeneralException(ErrorStatus.REPO_NOT_FOUND)); + Branch branch = branchRepository.findByRepoIdAndName(repo.getId(), branchName) + .orElseThrow(() -> new GeneralException(ErrorStatus.BRANCH_NOT_FOUND)); - LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(repo.getId()) + System.out.println("branch.getId() = " + branch.getId()); + + LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(branch.getId()) .orElse(LocalDateTime.MIN); HttpHeaders headers = new HttpHeaders(); @@ -86,7 +91,8 @@ public List getCommits(String ownerName, String repoName) { }) .toList(); - List savedCommits = newCommits.stream().map(item -> { + List savedCommits = newCommits.stream() + .map(item -> { String sha = (String) item.get("sha"); Map commit = (Map) item.get("commit"); String message = (String) commit.get("message"); @@ -96,11 +102,11 @@ public List getCommits(String ownerName, String repoName) { return new Commit( sha, - repo, message, null, // stats 필드는 이후에 계산할 수 있음 date, - null + null, + branch ); }).collect(Collectors.toList()); @@ -133,10 +139,10 @@ public List getCommits(String ownerName, String repoName) { commitParentRepository.saveAll(savedParents); - List allCommitList = commitRepository.findAllByRepoId(repo.getId()); + List allCommitList = commitRepository.findAllByBranchId(branch.getId()); return allCommitList.stream() - .map(c -> new CommitResponseDTO(c.getId(), c.getRepo().getId() ,c.getMessage(), c.getStats(), c.getDate())) + .map(c -> new CommitResponseDTO(c.getId(), c.getBranch().getId() ,c.getMessage(), c.getStats(), c.getDate())) .collect(Collectors.toList()); } @@ -396,4 +402,70 @@ private Owner getOrCreateOwner(Users user, List owners) { return ownerRepository.save(newOwner); }); } + + @Override + public List getUserBranches(String ownerName, String repoName) { + Long userId = SecurityUtil.getCurrentUserId(); + + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + Owner owner = ownerRepository.findByUserIdAndOwnerName(userId, ownerName) + .orElseThrow(() -> new GeneralException(ErrorStatus.OWNER_NOT_FOUND)); + + Repo repo = repoRepository.findByOwnerIdAndRepoName(owner.getId(), repoName) + .orElseThrow(() -> new GeneralException(ErrorStatus.REPO_NOT_FOUND)); + + String githubAccesstoken = user.getGithubAccesstoken(); + + String url = "https://api.github.com/repos/" + ownerName + "/" + repoName + "/branches"; + + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/vnd.github+json"); + headers.set("Authorization", "Bearer " + githubAccesstoken); + headers.set("X-GitHub-Api-Version", "2022-11-28"); + + HttpEntity entity = new HttpEntity<>(headers); + + ParameterizedTypeReference>> responseType = + new ParameterizedTypeReference<>() {}; + + ResponseEntity>> response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + responseType + ); + + + List result = new ArrayList<>(); + List branches = new ArrayList<>(); + + if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { + for (Map branch : response.getBody()) { + String name = (String) branch.get("name"); + + // 중복 저장 방지 (이미 DB에 있는 브랜치 필터링) + boolean exists = branchRepository.findByRepoIdAndName(repo.getId(), name).isPresent(); + if (!exists) { + Branch branchEntity = new Branch( + null, // id (auto-generated) + name, + repo, + new ArrayList<>() // commits 비워두기 + ); + branches.add(branchEntity); + + } + branchRepository.saveAll(branches); + result.add(new BranchResponseDTO(name)); + + } + } + + return result; + + } } From 76bf6402ad0b6d74391aa41fa20738752e800978 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 00:43:29 +0900 Subject: [PATCH 29/39] =?UTF-8?q?feat:=20#44=20Dockerfile,=20workflow?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC(main.yml)=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dockerfile: 스프링 프로젝트를 Docker Image로 빌드하기 위한 파일 workflow파일(main.yml): application.yml생성, jar파일로 빌드, Dockerfile을 이용하여 jar파일을 도커이미지로 빌드, 도커 레포지토리(도커 허브)에 push, 도커 레포지토리(도커 허브)에서 pull, docker compose up 명령어를 통해 ec2의 docker-compose.yml에 정의된 서비스 실행 --- .github/workflows/main.yml | 68 ++++++++++++++++++++++++++++++++++++++ Dockerfile | 11 ++++++ 2 files changed, 79 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 Dockerfile diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5e78132 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,68 @@ +# cicd.yml +# github repository Actions 페이지에 나타낼 이름 +name: LogIT A CI/CD with Gradle + +# event trigger +# main, develop 브랜치에 push, pr 생성시 실행되는 트리거 +on: + pull_request: + branches: [ "main" ] + + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + ## jdk setting + - uses: actions/checkout@v3 + - name: 🐧Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + ## gradle caching + - name: 🐧Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: 🐧application.yml 파일을 생성 합니다. + run: | + cd ./src/main/resources + touch ./application.yml + echo "${{ secrets.PROPERTIES }}" > ./application.yml + shell: bash + + - name: 🐧gradle build를 위한 권한을 부여합니다. + run: chmod +x gradlew + + - name: 🐧gradle build 중입니다. + run: ./gradlew build + shell: bash # ci는 여기까지 + + - name: 🐧docker image build 후 docker hub에 push합니다. + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} . + docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} + + ## deploy to production + - name: docker hub에서 pull 후 deploy합니다. + uses: appleboy/ssh-action@master + # id: deploy-prod + with: + username: ${{ secrets.EC2_USERNAME }} + host: ${{ secrets.EC2_HOST }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + envs: GITHUB_SHA + script: | + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} + sudo docker rm -f $(docker ps -qa) + docker compose up -d + docker image prune -f \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eac2b07 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# 도커 이미지 지정 +FROM openjdk:21 + +# 빌드된 파일의 위치를 argument로 지정 +ARG JAR_FILE=./build/libs/*.jar + +# 위의 경로의 파일을 이미지 내부의 app.jar로 복사 +COPY ${JAR_FILE} app.jar + +# 컨테이너 시작시 명령어. 즉 해당 jar파일을 실행하겠다는 것 +ENTRYPOINT [ "java", "-jar", "LogIT.jar" ] \ No newline at end of file From 679c70ff04cfc1d7e5a64cbfab668ede87723183 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 01:02:56 +0900 Subject: [PATCH 30/39] =?UTF-8?q?refactor:=20#46=20workflow=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC(main.yml)=EC=9D=98=20ubuntu=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9D=84=20ec2=EC=9D=98=20ubuntu=EB=B2=84=EC=A0=84=EC=9D=B8=20?= =?UTF-8?q?22.04=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e78132..8f8c6e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: ## jdk setting - uses: actions/checkout@v3 From 0bd4c4d5d3d6e05ec6538cfe775a3c5a835c4240 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 01:28:38 +0900 Subject: [PATCH 31/39] =?UTF-8?q?refactor:=20#49=20Workflow=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC(main.yml)=EC=97=90=EC=84=9C=20application.yml?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A7=80=EC=A0=95=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=20=EA=B2=BD=EB=A1=9C=20'cd=20../../src/main/?= =?UTF-8?q?resources'=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8f8c6e4..8a138bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,7 +34,7 @@ jobs: - name: 🐧application.yml 파일을 생성 합니다. run: | - cd ./src/main/resources + cd ../../src/main/resources touch ./application.yml echo "${{ secrets.PROPERTIES }}" > ./application.yml shell: bash From 126380d35647f4958bc0a598ef13ab70b2799670 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 01:47:22 +0900 Subject: [PATCH 32/39] =?UTF-8?q?refactor:=20#51=20Workflow=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC(main.yml)=EC=97=90=EC=84=9C=20application.yml?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=EA=B2=BD=EB=A1=9C=20'./src/main/resources'?= =?UTF-8?q?=EB=A1=9C=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8a138bb..6164c35 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,8 +34,7 @@ jobs: - name: 🐧application.yml 파일을 생성 합니다. run: | - cd ../../src/main/resources - touch ./application.yml + touch ./src/main/resources/application.yml # application.yml 파일 생성 echo "${{ secrets.PROPERTIES }}" > ./application.yml shell: bash From 77ffe0a91883fa63d67ece5b6a4120c51ec35140 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 01:54:17 +0900 Subject: [PATCH 33/39] =?UTF-8?q?refactor:=20#53=20Workflow=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC(main.yml)=EC=97=90=EC=84=9C=20application.yml?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=83=9D=EC=84=B1=ED=95=A0=EB=95=8C=20'./src?= =?UTF-8?q?/main/resources'=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=EA=B0=80?= =?UTF-8?q?=20=EC=97=86=EC=9C=BC=EB=A9=B4=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6164c35..b21982f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,8 +34,9 @@ jobs: - name: 🐧application.yml 파일을 생성 합니다. run: | + mkdir -p ./src/main/resources # 디렉토리가 없으면 생성 touch ./src/main/resources/application.yml # application.yml 파일 생성 - echo "${{ secrets.PROPERTIES }}" > ./application.yml + echo "${{ secrets.PROPERTIES }}" > ./src/main/resources/application.yml shell: bash - name: 🐧gradle build를 위한 권한을 부여합니다. From d3169c1bebed5d5e920eb2ec8f6b8ef88b2ef320 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 02:10:00 +0900 Subject: [PATCH 34/39] =?UTF-8?q?fix:=20#55=20Workflow=ED=8C=8C=EC=9D=BC(m?= =?UTF-8?q?ain.yml)=EC=97=90=EC=84=9C=20github=20action=EC=97=90=EC=84=9C?= =?UTF-8?q?=20ec2=EC=A0=91=EC=86=8D=20=ED=9B=84=20docker=20hub=EC=97=90?= =?UTF-8?q?=EC=84=9C=20pull=EB=B0=9B=EB=8A=94=20=EB=AA=85=EB=A0=B9?= =?UTF-8?q?=EC=96=B4=EC=97=90=20sudo=20=ED=8F=AC=ED=95=A8=EC=8B=9C?= =?UTF-8?q?=ED=82=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b21982f..94b7b49 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,5 +64,5 @@ jobs: script: | sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} sudo docker rm -f $(docker ps -qa) - docker compose up -d - docker image prune -f \ No newline at end of file + sudo docker compose up -d + sudo docker image prune -f \ No newline at end of file From 06c0b6b6318944d5488da5a743d41449fbdd99eb Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 02:24:09 +0900 Subject: [PATCH 35/39] =?UTF-8?q?feat:=20#57=20Workflow=ED=8C=8C=EC=9D=BC(?= =?UTF-8?q?main.yml)=EC=97=90=EC=84=9C=20ec2=EC=97=90=20docker=20=EB=AA=85?= =?UTF-8?q?=EB=A0=B9=EC=96=B4=20=EC=8B=A4=ED=96=89=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=84=20'cd=20logit-server'=EB=A1=9C=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20=EB=A8=BC=EC=A0=80=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94b7b49..e1e1ec1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,6 +62,7 @@ jobs: key: ${{ secrets.EC2_PRIVATE_KEY }} envs: GITHUB_SHA script: | + cd logit-server sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} sudo docker rm -f $(docker ps -qa) sudo docker compose up -d From 11cc9e09be0b8b64961506b7d1f9a2bfcc26decf Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 17:06:08 +0900 Subject: [PATCH 36/39] =?UTF-8?q?refactor:=20github=20action=EC=9D=98=20do?= =?UTF-8?q?cker=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정전 명령어: sudo docker image prune -f 수정후 명령어: docker image prune -f --- .github/workflows/main.yml | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..bec7674 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,69 @@ +# cicd.yml +# github repository Actions 페이지에 나타낼 이름 +name: LogIT A CI/CD with Gradle + +# event trigger +# main, develop 브랜치에 push, pr 생성시 실행되는 트리거 +on: + pull_request: + branches: [ "main" ] + + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + ## jdk setting + - uses: actions/checkout@v3 + - name: 🐧Set up JDK 21 + uses: actions/setup-java@v3 + with: + java-version: '21' + distribution: 'temurin' + + ## gradle caching + - name: 🐧Gradle Caching + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: 🐧application.yml 파일을 생성 합니다. + run: | + mkdir -p ./src/main/resources # 디렉토리가 없으면 생성 + touch ./src/main/resources/application.yml # application.yml 파일 생성 + echo "${{ secrets.PROPERTIES }}" > ./src/main/resources/application.yml + shell: bash + + - name: 🐧gradle build를 위한 권한을 부여합니다. + run: chmod +x gradlew + + - name: 🐧gradle build 중입니다. + run: ./gradlew build + shell: bash # ci는 여기까지 + + - name: 🐧docker image build 후 docker hub에 push합니다. + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} . + docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} + + ## deploy to production + - name: docker hub에서 pull 후 deploy합니다. + uses: appleboy/ssh-action@master + # id: deploy-prod + with: + username: ${{ secrets.EC2_USERNAME }} + host: ${{ secrets.EC2_HOST }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + envs: GITHUB_SHA + script: | + cd logit-server + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} + sudo docker rm -f $(docker ps -qa) + sudo docker compose up -d + docker image prune -f \ No newline at end of file From 3e948eaf71285907382e23960afbe9572849cb19 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Thu, 22 May 2025 17:37:41 +0900 Subject: [PATCH 37/39] =?UTF-8?q?workflow=20file(main.yml)=EC=97=90?= =?UTF-8?q?=EC=84=9C=20ec2=EC=97=90=EC=84=9C=20=EC=8B=A4=ED=96=89=ED=95=98?= =?UTF-8?q?=EB=8A=94=20docker=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 수정전 명령어: docker image prune -f 수정후 명령어: sudo docker image prune -f --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bec7674..e1e1ec1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -66,4 +66,4 @@ jobs: sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} sudo docker rm -f $(docker ps -qa) sudo docker compose up -d - docker image prune -f \ No newline at end of file + sudo docker image prune -f \ No newline at end of file From 2626c562c4c77536cbf7dde64c95b9e581bb496c Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 23 May 2025 01:06:21 +0900 Subject: [PATCH 38/39] =?UTF-8?q?jar=ED=8C=8C=EC=9D=BC=EC=9D=84=20?= =?UTF-8?q?=EB=8F=84=EC=BB=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=ED=95=98=EB=8A=94=20Dockerfile=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index eac2b07..c2e4d43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,14 @@ # 도커 이미지 지정 FROM openjdk:21 +WORKDIR /app/logit + # 빌드된 파일의 위치를 argument로 지정 -ARG JAR_FILE=./build/libs/*.jar +ARG JAR_PATH=../build/libs +ARG RESOURCE_PATH=../build/resources/main/ # 위의 경로의 파일을 이미지 내부의 app.jar로 복사 -COPY ${JAR_FILE} app.jar +COPY ${JAR_PATH}/*.jar /app/logit/LogIT.jar # 컨테이너 시작시 명령어. 즉 해당 jar파일을 실행하겠다는 것 ENTRYPOINT [ "java", "-jar", "LogIT.jar" ] \ No newline at end of file From f4d329f0c3805f2d3b53d65b9847e54ebbb07793 Mon Sep 17 00:00:00 2001 From: Jinho622 Date: Fri, 23 May 2025 12:15:50 +0900 Subject: [PATCH 39/39] =?UTF-8?q?A=20Workflow=ED=8C=8C=EC=9D=BC(main.yml)?= =?UTF-8?q?=EC=97=90=EC=84=9C=20ec2=EC=97=90=EC=84=9C=20=EC=BB=A8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=A2=85=EB=A3=8C=ED=95=98=EA=B3=A0=20doc?= =?UTF-8?q?ker-compose=EB=A1=9C=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 컨테이너 종료 명령어 수정 전: sudo docker rm -f $(docker ps -qa), 수정 후 :sudo docker rm -f $(sudo docker ps -aq) || true docker-compose로 컨테이너 업데이트하는 명령어 수정 전: sudo docker compose up -d, 수정 후: sudo docker-compose up -d --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e1e1ec1..03ba8f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,6 +64,6 @@ jobs: script: | cd logit-server sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE }} - sudo docker rm -f $(docker ps -qa) - sudo docker compose up -d + sudo docker rm -f $(sudo docker ps -aq) || true + sudo docker-compose up -d sudo docker image prune -f \ No newline at end of file