diff --git a/src/main/java/com/server/domain/project/repository/ProjectRepository.java b/src/main/java/com/server/domain/project/repository/ProjectRepository.java index 050fcd1..2a605e0 100644 --- a/src/main/java/com/server/domain/project/repository/ProjectRepository.java +++ b/src/main/java/com/server/domain/project/repository/ProjectRepository.java @@ -4,9 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import java.util.Optional; public interface ProjectRepository extends JpaRepository { List findByOwnerId(Long ownerId); + Optional findByUrl(String url); + + Optional findByName(String name); Project findByOwnerIdAndName(Long ownerId, String name); } diff --git a/src/main/java/com/server/domain/project/service/ProjectService.java b/src/main/java/com/server/domain/project/service/ProjectService.java index 32b1f57..987bf5d 100644 --- a/src/main/java/com/server/domain/project/service/ProjectService.java +++ b/src/main/java/com/server/domain/project/service/ProjectService.java @@ -3,6 +3,8 @@ import com.server.domain.project.dto.ProjectDto; import com.server.domain.project.entity.Project; import com.server.domain.project.repository.ProjectRepository; +import com.server.domain.pull.enums.PullRequestState; +import com.server.domain.pull.service.PullRequestService; import com.server.domain.user.entity.User; import com.server.domain.user.service.UserService; import com.server.global.error.code.AuthErrorCode; @@ -23,11 +25,14 @@ public class ProjectService { private final UserService userService; private final JwtService jwtService; private final ProjectRepository projectRepository; + private final PullRequestService pullRequestService; public ProjectDto createProject(HttpServletRequest request, String repoUrl, String name) { String username = jwtService.extractUsernameFromToken(request) .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); User user = userService.getUserWithPersonalInfo(username); + String afterCom = repoUrl.substring(repoUrl.indexOf(".com/") + 5); + String[] parts = afterCom.split("/"); Project p = projectRepository.save(Project.builder() .name(name) @@ -35,6 +40,7 @@ public ProjectDto createProject(HttpServletRequest request, String repoUrl, Stri .s3Path("") .ownerId(user.getId()) .build()); + pullRequestService.getPullRequestsWithProject(user, parts[0], parts[1], PullRequestState.ALL); return ProjectDto.from(p); } diff --git a/src/main/java/com/server/domain/pull/controller/PullRequestController.java b/src/main/java/com/server/domain/pull/controller/PullRequestController.java index 7acebde..d7a9c26 100644 --- a/src/main/java/com/server/domain/pull/controller/PullRequestController.java +++ b/src/main/java/com/server/domain/pull/controller/PullRequestController.java @@ -2,6 +2,7 @@ import java.util.List; +import com.server.domain.pull.dto.PullRequestWithProjectDto; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -45,4 +46,13 @@ public ApiResponseDto getPullRequests(HttpServletReque return ApiResponseDto.success(HttpStatus.OK.value(), pullRequestService.getPullRequest(request, userOrOrgName, repositoryName, pullNo)); } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{projectName}") + @Operation(summary = "프로젝트 이름으로 pull request 목록 조회", description = "프로젝트 이름을 입력 받아 해당 프로젝트와 연관되어있는 풀리퀘 목록 반환해줍니다.") + public ApiResponseDto> getPullRequestsByProjectName(HttpServletRequest request, + @PathVariable String projectName) { + return ApiResponseDto.success(HttpStatus.OK.value(), + pullRequestService.getPullRequestByProjectName(request, projectName)); + } } diff --git a/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java b/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java index 6f27248..be8f856 100644 --- a/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java +++ b/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import com.server.domain.pull.entity.PullRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -34,4 +37,6 @@ public class GetPullRequestInfoOutDto extends PullRequestDto { @JsonProperty("changed_files") int changedFiles; + + } diff --git a/src/main/java/com/server/domain/pull/dto/GetPullRequestOutDto.java b/src/main/java/com/server/domain/pull/dto/GetPullRequestOutDto.java index 9cfd942..663ffe9 100644 --- a/src/main/java/com/server/domain/pull/dto/GetPullRequestOutDto.java +++ b/src/main/java/com/server/domain/pull/dto/GetPullRequestOutDto.java @@ -1,9 +1,15 @@ package com.server.domain.pull.dto; +import com.server.domain.pull.enums.PullRequestState; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +import java.time.LocalDateTime; + @Getter @Setter +@Builder public class GetPullRequestOutDto extends PullRequestDto { + } diff --git a/src/main/java/com/server/domain/pull/dto/PullRequestDto.java b/src/main/java/com/server/domain/pull/dto/PullRequestDto.java index eb9233a..fafb50d 100644 --- a/src/main/java/com/server/domain/pull/dto/PullRequestDto.java +++ b/src/main/java/com/server/domain/pull/dto/PullRequestDto.java @@ -4,8 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; +import com.server.domain.pull.entity.PullRequest; import com.server.domain.pull.enums.PullRequestState; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; @Getter @@ -45,4 +48,24 @@ private void unpackHead(JsonNode head) { @JsonProperty("merged_at") private LocalDateTime mergedAt; + + public static PullRequest toEntity(PullRequestDto pullRequestDto){ + + return PullRequest.builder() + .prNumber(pullRequestDto.getPrId()) + .url(pullRequestDto.getUrl()) + .title(pullRequestDto.getTitle()) + .ownerId(pullRequestDto.getOwnerId()) + .branchName(pullRequestDto.getBranchName()) + .status(pullRequestDto.getStatus()) + .createdAt(pullRequestDto.getCreatedAt()) + .closedAt(pullRequestDto.getClosedAt()) + .mergedAt(pullRequestDto.getMergedAt()) + .build(); + } + + + + } + diff --git a/src/main/java/com/server/domain/pull/dto/PullRequestWithProjectDto.java b/src/main/java/com/server/domain/pull/dto/PullRequestWithProjectDto.java new file mode 100644 index 0000000..fc0b1e5 --- /dev/null +++ b/src/main/java/com/server/domain/pull/dto/PullRequestWithProjectDto.java @@ -0,0 +1,46 @@ +package com.server.domain.pull.dto; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.server.domain.pull.entity.PullRequest; +import com.server.domain.pull.enums.PullRequestState; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Builder +@Getter +public class PullRequestWithProjectDto { + private Integer prId; + + private String url; + + private String ownerId; + + private String title; + + private String branchName; + + private PullRequestState status; + + private LocalDateTime createdAt; + + private LocalDateTime closedAt; + + private LocalDateTime mergedAt; + + public static PullRequestWithProjectDto toDto(PullRequest pullRequest){ + return PullRequestWithProjectDto.builder() + .prId(pullRequest.getPrNumber()) + .url(pullRequest.getUrl()) + .ownerId(pullRequest.getOwnerId()) + .title(pullRequest.getTitle()) + .branchName(pullRequest.getBranchName()) + .status(pullRequest.getStatus()) + .createdAt(pullRequest.getCreatedAt()) + .mergedAt(pullRequest.getMergedAt()) + .closedAt(pullRequest.getClosedAt()) + .build(); + } +} diff --git a/src/main/java/com/server/domain/pull/entity/PullRequest.java b/src/main/java/com/server/domain/pull/entity/PullRequest.java new file mode 100644 index 0000000..55c2061 --- /dev/null +++ b/src/main/java/com/server/domain/pull/entity/PullRequest.java @@ -0,0 +1,65 @@ +package com.server.domain.pull.entity; + +import com.server.domain.project.entity.Project; +import com.server.domain.pull.enums.PullRequestState; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@EntityListeners(AuditingEntityListener.class) +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "pull_requests") +@Builder +public class PullRequest { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "pull_request_id") + private Long id; + + @Column(name = "pr_number") + private Integer prNumber; + + @Column(name = "pull_request_url") + private String url; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "project_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Project project; + + @Column(name = "pull_request_title") + private String title; + + @Column(name = "pull_request_user_id") + private String ownerId; + + @Column(name = "branch_name") + private String branchName; + + @Column(name = "state") + private PullRequestState status; + + @Column(name = "created_at") + private LocalDateTime createdAt; + + @Column(name = "closed_at") + private LocalDateTime closedAt; + + @Column(name = "merged_at") + private LocalDateTime mergedAt; + + + public void setProject(Project project){ + this.project = project; + } +} diff --git a/src/main/java/com/server/domain/pull/repository/PullRequestRepository.java b/src/main/java/com/server/domain/pull/repository/PullRequestRepository.java new file mode 100644 index 0000000..307932a --- /dev/null +++ b/src/main/java/com/server/domain/pull/repository/PullRequestRepository.java @@ -0,0 +1,11 @@ +package com.server.domain.pull.repository; + +import com.server.domain.project.entity.Project; +import com.server.domain.pull.entity.PullRequest; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface PullRequestRepository extends JpaRepository { + List findByProject(Project project); +} diff --git a/src/main/java/com/server/domain/pull/service/PullRequestService.java b/src/main/java/com/server/domain/pull/service/PullRequestService.java index 8a77441..1ddcc56 100644 --- a/src/main/java/com/server/domain/pull/service/PullRequestService.java +++ b/src/main/java/com/server/domain/pull/service/PullRequestService.java @@ -1,7 +1,16 @@ package com.server.domain.pull.service; import java.util.List; +import java.util.stream.Collectors; +import com.server.domain.project.entity.Project; +import com.server.domain.project.repository.ProjectRepository; +import com.server.domain.pull.dto.PullRequestDto; +import com.server.domain.pull.dto.PullRequestWithProjectDto; +import com.server.domain.pull.entity.PullRequest; +import com.server.domain.pull.repository.PullRequestRepository; +import com.server.global.error.code.ProjectErrorCode; +import com.server.global.error.exception.BusinessException; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -32,9 +41,48 @@ public class PullRequestService { private final UserService userService; private final JwtService jwtService; + private final PullRequestRepository pullRequestRepository; + private final ProjectRepository projectRepository; - public List getPullRequests(HttpServletRequest request, String userOrOrgName, + public List getPullRequestsWithProject(User user, String userOrOrgName, String repositoryName, PullRequestState state) { + + RestTemplate restTemplate = new RestTemplate(); + String url = String.format("https://api.github.com/repos/%s/%s/pulls", userOrOrgName, repositoryName); + if (state != null) { + url = String.format("%s?state=%s", url, state.getValue()); + } + ResponseEntity> githubPullRequests = restTemplate.exchange( + url, + HttpMethod.GET, + buildGithubHttpEntity(user.getGithubToken()), + new ParameterizedTypeReference>() { + }); + if (githubPullRequests.getStatusCode().value() != 200) { + throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR); + } + + List pullRequests = githubPullRequests.getBody().stream() + .map(PullRequestDto::toEntity) + .toList(); + + String repoUrl = String.format("https://github.com/%s/%s", userOrOrgName, repositoryName); + + Project project = projectRepository.findByUrl(repoUrl).orElseThrow( + ()->new BusinessException(ProjectErrorCode.NOT_FOUND) + ); + + pullRequests.forEach(pullRequest -> { + pullRequest.setProject(project); + }); + + pullRequestRepository.saveAll(pullRequests); + + return githubPullRequests.getBody(); + } + + public List getPullRequests(HttpServletRequest request, String userOrOrgName, + String repositoryName, PullRequestState state) { String username = jwtService.extractUsernameFromToken(request) .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); User user = userService.getUserWithPersonalInfo(username); @@ -53,9 +101,11 @@ public List getPullRequests(HttpServletRequest request, St if (githubPullRequests.getStatusCode().value() != 200) { throw new AuthException(AuthErrorCode.OAUTH_PROCESS_ERROR); } + return githubPullRequests.getBody(); } + public GetPullRequestInfoOutDto getPullRequest(HttpServletRequest request, String userOrOrgName, String repositoryName, int pullNo) { String username = jwtService.extractUsernameFromToken(request) @@ -87,4 +137,20 @@ private HttpEntity> buildGithubHttpEntity(String a headers.setBearerAuth(accessToken); return new HttpEntity<>(params, headers); } + + public List getPullRequestByProjectName(HttpServletRequest request, String projectName) { + String username = jwtService.extractUsernameFromToken(request) + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); + User user = userService.getUserWithPersonalInfo(username); + + Project p = projectRepository.findByName(projectName).orElseThrow(()-> new BusinessException(ProjectErrorCode.NOT_FOUND)); + + + List pullRequests = pullRequestRepository.findByProject(p); + + return pullRequests.stream() + .map(PullRequestWithProjectDto::toDto) + .toList(); + + } } diff --git a/src/main/java/com/server/global/config/WebConfig.java b/src/main/java/com/server/global/config/WebConfig.java index 7e917ad..faebf65 100644 --- a/src/main/java/com/server/global/config/WebConfig.java +++ b/src/main/java/com/server/global/config/WebConfig.java @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // FIXME: 우선은 CORS 전체 허용 - registry.addMapping("/api/**") + registry.addMapping("/**") .allowedOrigins("http://localhost:3000") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") diff --git a/src/main/java/com/server/global/error/code/ProjectErrorCode.java b/src/main/java/com/server/global/error/code/ProjectErrorCode.java new file mode 100644 index 0000000..57145da --- /dev/null +++ b/src/main/java/com/server/global/error/code/ProjectErrorCode.java @@ -0,0 +1,14 @@ +package com.server.global.error.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum ProjectErrorCode implements ErrorCode { + NOT_FOUND(HttpStatus.NOT_FOUND.value(), "프로젝트가 존재하지 않습니다."); + + private final int status; + private final String message; +}