diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..03ba8f2 --- /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 $(sudo docker ps -aq) || true + sudo docker-compose up -d + sudo docker image prune -f \ No newline at end of file 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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c2e4d43 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# 도커 이미지 지정 +FROM openjdk:21 + +WORKDIR /app/logit + +# 빌드된 파일의 위치를 argument로 지정 +ARG JAR_PATH=../build/libs +ARG RESOURCE_PATH=../build/resources/main/ + +# 위의 경로의 파일을 이미지 내부의 app.jar로 복사 +COPY ${JAR_PATH}/*.jar /app/logit/LogIT.jar + +# 컨테이너 시작시 명령어. 즉 해당 jar파일을 실행하겠다는 것 +ENTRYPOINT [ "java", "-jar", "LogIT.jar" ] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5363bc3..edbe9ce 100644 --- a/build.gradle +++ b/build.gradle @@ -24,12 +24,44 @@ 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' + + // jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // 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' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + + //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/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/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/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/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 new file mode 100644 index 0000000..70d3468 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/CommitResponseDTO.java @@ -0,0 +1,30 @@ +package LogITBackend.LogIT.DTO; + + +import LogITBackend.LogIT.domain.Commit; +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 repo_id; + private String message; + private String stats; + private LocalDateTime date; + + public static CommitResponseDTO fromEntity(Commit commit) { + return new CommitResponseDTO( + commit.getId(), + commit.getBranch().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/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/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/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..9983718 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/RecordResponseDTO.java @@ -0,0 +1,54 @@ +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 author; + String title; + 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/DTO/RepositoryResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java new file mode 100644 index 0000000..74255e7 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/RepositoryResponseDTO.java @@ -0,0 +1,16 @@ +package LogITBackend.LogIT.DTO; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RepositoryResponseDTO { + private String repoName; + LocalDateTime createdAt; + LocalDateTime updatedAt; +} diff --git a/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java new file mode 100644 index 0000000..107d9b1 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/UserRequestDTO.java @@ -0,0 +1,58 @@ +package LogITBackend.LogIT.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; + } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class SignInRequestDTO { + @NotBlank(message = "아이디는 빈값일 수 없습니다.") + private String username; + @NotBlank(message = "비밀번호는 빈값일 수 없습니다.") + private String password; + } + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class GithubSignUpRequestDTO { + private String providerId; + 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/DTO/UserResponseDTO.java b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java new file mode 100644 index 0000000..7e0a025 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/DTO/UserResponseDTO.java @@ -0,0 +1,33 @@ +package LogITBackend.LogIT.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; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class UserSignInResultDTO { + String accessToken; + String refreshToken; + String nickname; + LocalDateTime createdAt; + } +} 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/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..0d24627 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/apiPayload/code/status/ErrorStatus.java @@ -0,0 +1,70 @@ +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", "이전 비밀번호와 동일합니다."), + GITHUB_NOT_ACCESS(HttpStatus.UNAUTHORIZED, "USER_1009", "GitHub 계정 연동이 필요합니다."), + + // 카테고리 관련 응답 2000 + CATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "CATEGORY_2001", "카테고리가 존재하지 않습니다."), + + // commit 관련 응답 3000 + 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이 존재하지 않습니다."), + + // record 관련 응답 6000 + 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; + 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(); + } +} 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); + } +} 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/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); + } +} 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/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..33d00d9 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/JwtAuthenticationFilter.java @@ -0,0 +1,159 @@ +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) { + log.info("-------------------#@@@@@------------------"); + 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(); + log.info("---------------$$$$$$------------------"); + log.info("boolean: {}", PatternMatchUtils.simpleMatch(whitelist, requestURI)); + log.info("requestURI: {}", requestURI); + 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); // 🚨 블랙리스트 확인 + log.info("------------------------------------------------------"); + } 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/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 new file mode 100644 index 0000000..750c7dd --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/SecurityConfig.java @@ -0,0 +1,71 @@ +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; +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; +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 +@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 { + http + .cors(Customizer.withDefaults()) + .csrf(AbstractHttpConfigurer::disable) // 추가해주어야함. + // 폼 로그인 비활성화 + .formLogin(AbstractHttpConfigurer::disable) + // HTTP Basic 인증 비활성화 + .httpBasic(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 Stateless로 설정 + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/**").permitAll()) + .oauth2Login(oauth -> oauth + .userInfoEndpoint(userInfo -> userInfo + .userService(customOAuth2UserService) + ) + .successHandler(customOAuth2SuccessHandler) + .failureHandler(customAuthExceptionHandler) + ); + 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; + } +} 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()); + } +} 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..68c48d6 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/config/security/oauth/CustomOAuth2SuccessHandler.java @@ -0,0 +1,108 @@ +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; +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.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 +@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; + 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!---------------------"); + + Users existUser = userRepository.findByProviderId(providerId).orElse(null); + + // user dto 생성해서 user저장 + if(existUser == null) { + Users newUser = UserConverter.githubDatatoUsers( + UserRequestDTO.GithubSignUpRequestDTO.builder() + .providerId(providerId) + .nickname(nickname) + .githubAccessToken(accessToken.getTokenValue()) + .build() + ); + userRepository.save(newUser); + } + + // ✅ provideId 쿼리 파라미터로 전달 + String redirectUrl = UriComponentsBuilder + .fromUriString(redirectUri) + .queryParam("providerId", providerId) + .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..bf41222 --- /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/controller/CategoryController.java b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java new file mode 100644 index 0000000..14a176d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/CategoryController.java @@ -0,0 +1,32 @@ +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.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class CategoryController { + + private final CategoryService categoryService; + + + @GetMapping("") + public ResponseEntity>> getCategories() { + List categories = categoryService.getCategories(); + return ResponseEntity.ok(ApiResponse.onSuccess(categories)); + } + + @PostMapping("") + public ResponseEntity> createCategory(@RequestBody CategoryRequestDTO request) { + CategoryResponseDTO category = categoryService.createCategory(request); + return ResponseEntity.ok(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..d406636 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/CodeController.java @@ -0,0 +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.*; + +@RestController +@RequestMapping("/codes") +@RequiredArgsConstructor +public class CodeController { + + 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/controller/GithubController.java b/src/main/java/LogITBackend/LogIT/controller/GithubController.java new file mode 100644 index 0000000..d61da8b --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/GithubController.java @@ -0,0 +1,68 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.DTO.*; +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}/{branches}/commits") + public ResponseEntity> getCommits( + @PathVariable("owners") String owners, + @PathVariable("repos") String repos, + @PathVariable("branches") String branches + ) { + List commits = githubService.getCommits(owners, repos, branches); + 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 + ) { + CommitDetailResponseDTO commitDetail = githubService.getCommitDetails(owners, repos, commitId); + return ResponseEntity.ok(ApiResponse.onSuccess(commitDetail)); + } + + @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)); + } + + @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/controller/RecordController.java b/src/main/java/LogITBackend/LogIT/controller/RecordController.java new file mode 100644 index 0000000..2ba9716 --- /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.toCreateRecordResultDTO(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.toGetRecordDetailResultDTO(recordDetail) + ); + } +} diff --git a/src/main/java/LogITBackend/LogIT/controller/UserController.java b/src/main/java/LogITBackend/LogIT/controller/UserController.java new file mode 100644 index 0000000..412e735 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/controller/UserController.java @@ -0,0 +1,60 @@ +package LogITBackend.LogIT.controller; + +import LogITBackend.LogIT.converter.UserConverter; +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; +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 + ) + ); + } + + @Operation(summary = "로그인", description = + "# 로그인 API 입니다. 아이디와 패스워드를 body에 입력해주세요." + ) + @PostMapping("/signin") + public ApiResponse signIn( + @RequestBody @Valid UserRequestDTO.SignInRequestDTO request + ) { + return ApiResponse.onSuccess( + 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/converter/RecordConverter.java b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java new file mode 100644 index 0000000..13ff4ad --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/converter/RecordConverter.java @@ -0,0 +1,72 @@ +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() > 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(); + } + + // 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(); + } +} 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..edb3c68 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/converter/UserConverter.java @@ -0,0 +1,41 @@ +package LogITBackend.LogIT.converter; + +import LogITBackend.LogIT.DTO.UserRequestDTO; +import LogITBackend.LogIT.DTO.UserResponseDTO; +import LogITBackend.LogIT.domain.Users; + +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 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()) + .nickname(users.getNickname()) + .loginType(users.getLoginType()) + .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/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/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..3669d7c --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Codes.java @@ -0,0 +1,79 @@ +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; + + @Column(columnDefinition = "TEXT") + private String code; + + private LocalDateTime date; + + @Column(nullable = false) + private Integer line; + + @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/Commit.java b/src/main/java/LogITBackend/LogIT/domain/Commit.java new file mode 100644 index 0000000..285341a --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Commit.java @@ -0,0 +1,41 @@ +package LogITBackend.LogIT.domain; + +import LogITBackend.LogIT.domain.common.BaseEntity; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "Commits") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Commit extends BaseEntity { + @Id + @Column(length = 40) + private String id; // commit SHA + + @Column(length = 255) + private String message; + + @Column(length = 100) + private String stats; + + private LocalDateTime date; + + @OneToMany(mappedBy = "commit", cascade = CascadeType.ALL) + @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/CommitParent.java b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java new file mode 100644 index 0000000..e8e5f5b --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/CommitParent.java @@ -0,0 +1,31 @@ +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; + +@Entity +@Table(name = "commit_parents", + uniqueConstraints = @UniqueConstraint(columnNames = {"commit_id", "parent_id"})) +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +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..8befa2a --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/File.java @@ -0,0 +1,38 @@ +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) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "commit_id", nullable = false) + @JsonBackReference + 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/Owner.java b/src/main/java/LogITBackend/LogIT/domain/Owner.java new file mode 100644 index 0000000..81886b0 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Owner.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 Owner extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private Users user; + + @Column(length = 100, nullable = false) + private String ownerName; +} 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/Repo.java b/src/main/java/LogITBackend/LogIT/domain/Repo.java new file mode 100644 index 0000000..2277f72 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Repo.java @@ -0,0 +1,36 @@ +package LogITBackend.LogIT.domain; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Repo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "owner_id") + private Owner owner; + + @Column(length = 100, nullable = false) + private String repoName; + + @OneToMany(mappedBy = "repo", cascade = CascadeType.ALL) + private List branches; + + 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 new file mode 100644 index 0000000..23ad4dc --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/domain/Users.java @@ -0,0 +1,104 @@ +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.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; + + @Column(length = 50) + private String githubNickname; + + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(10)") + private Gender gender; + + @Enumerated(EnumType.STRING) + @Column + private UserStatus status; + + private LocalDateTime inactiveDate; + + private String providerId; + + private String githubAccesstoken; + + private String accesstoken; + + // 로그인 관련 +// 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<>(); + + @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; + } + + 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/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/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/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/CategoryRepository.java b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java new file mode 100644 index 0000000..7974560 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CategoryRepository.java @@ -0,0 +1,15 @@ +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; +import java.util.Optional; + +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/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/repository/CommitRepository.java b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java new file mode 100644 index 0000000..1ae8fa1 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/CommitRepository.java @@ -0,0 +1,18 @@ +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; + +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.branch.id = :branchId") + Optional findLatestCommitDateByUserId(Long branchId); + + @Query("SELECT c FROM Commit c WHERE c.branch.id = :branchId") + List findAllByBranchId(Long branchId); +} 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/repository/OwnerRepository.java b/src/main/java/LogITBackend/LogIT/repository/OwnerRepository.java new file mode 100644 index 0000000..018267e --- /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/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/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 new file mode 100644 index 0000000..260f800 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/repository/UserRepository.java @@ -0,0 +1,15 @@ +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 { + @Override + Optional findById(Long userId); + Optional findByUsername(String username); + Optional findByProviderId(String providerId); +} 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..535f308 --- /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..aeacddf --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/CategoryServiceImpl.java @@ -0,0 +1,47 @@ +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.config.security.SecurityUtil; +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() { + Long userId = SecurityUtil.getCurrentUserId(); + + List categories = categoryRepository.findAllByUsersId(userId); + if (categories.isEmpty()) { + throw new GeneralException(ErrorStatus.CATEGORY_NOT_FOUND); + } + return categories.stream() + .map(CodeCategories::getName) + .toList(); + } + + @Override + public CategoryResponseDTO createCategory(CategoryRequestDTO request) { + Long userId = SecurityUtil.getCurrentUserId(); + Users user = userRepository.findById(userId) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + 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)); + } +} 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..88f0618 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/GithubService.java @@ -0,0 +1,19 @@ +package LogITBackend.LogIT.service; + +import LogITBackend.LogIT.DTO.*; + +import java.util.List; + +public interface GithubService { + List getCommits(String owner, String repo, String branch); + + CommitDetailResponseDTO getCommitDetails(String owners, String repos, String commitId); + + GithubRepoResponse getUsersRepos(); + + 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 new file mode 100644 index 0000000..7ff2a8d --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/GithubServiceImpl.java @@ -0,0 +1,471 @@ +package LogITBackend.LogIT.service; + +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.repository.*; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.springframework.core.ParameterizedTypeReference; +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; + +import static LogITBackend.LogIT.apiPayload.code.status.ErrorStatus.COMMIT_NOT_FOUND; + +@Service +@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 BranchRepository branchRepository; + + @Override + @Transactional + 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(); + + 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); + } + + 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)); + + System.out.println("branch.getId() = " + branch.getId()); + + LocalDateTime latestDate = commitRepository.findLatestCommitDateByUserId(branch.getId()) + .orElse(LocalDateTime.MIN); + + HttpHeaders headers = new HttpHeaders(); + 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(); + + + ResponseEntity>> response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + new ParameterizedTypeReference>>() {} + ); + + List> body = response.getBody(); + if (body == null) return Collections.emptyList(); + + 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"); + Map author = (Map) commit.get("author"); + String dateStr = (String) author.get("date"); + LocalDateTime date = LocalDateTime.parse(dateStr.replace("Z", "")); + + return new Commit( + sha, + message, + null, // stats 필드는 이후에 계산할 수 있음 + date, + null, + branch + ); + }).collect(Collectors.toList()); + + commitRepository.saveAll(savedCommits); + + Map shaToCommitMap = savedCommits.stream() + .collect(Collectors.toMap(Commit::getId, c -> c)); + + List savedParents = newCommits.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); + + List allCommitList = commitRepository.findAllByBranchId(branch.getId()); + + return allCommitList.stream() + .map(c -> new CommitResponseDTO(c.getId(), c.getBranch().getId() ,c.getMessage(), c.getStats(), c.getDate())) + .collect(Collectors.toList()); + } + + + @Override + @Transactional // 커밋 세부정보는 거의 바뀌지 않으므로, update x + 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에서 정보 요청 + + 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); + } + + @Override + @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(); + + if (githubAccesstoken == null || githubNickname == null) { + throw new GeneralException(ErrorStatus.GITHUB_NOT_ACCESS); + } + + List owners = ownerRepository.findAllByUserId(userId); + + Owner owner = getOrCreateOwner(user, owners); + + 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 + ); + + 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(); + 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); + + // db에 있는거 그대로 출력하는 로직 + List repoDTOList = repoRepository.findAllByOwnerId((owner.getId())) + .stream() + .map(repo -> new RepositoryResponseDTO( + 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())) + .findFirst() + .orElseGet(() -> { + Owner newOwner = Owner.builder() + .user(user) + .ownerName(user.getGithubNickname()) + .build(); + 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; + + } +} 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; + } +} 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..9d871e9 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandService.java @@ -0,0 +1,13 @@ +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); + 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 new file mode 100644 index 0000000..50e0f95 --- /dev/null +++ b/src/main/java/LogITBackend/LogIT/service/UserCommandServiceImpl.java @@ -0,0 +1,92 @@ +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.config.security.SecurityConfig; +import LogITBackend.LogIT.config.security.SecurityUtil; +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 // ??? + public Users signUp(UserRequestDTO.SignUpRequestDTO request) { + Users newUser = UserConverter.toUsers(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); + } + + @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( + "userId", userId // String으로 저장??? 그래서 SecurityUtil에서 Long으로 타입변환 해주나? + ); + return jwtUtils.generateToken(valueMap, accessExpTime); + } + + public 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; + } +} 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