diff --git a/src/main/java/com/server/domain/pull/controller/PullRequestController.java b/src/main/java/com/server/domain/pull/controller/PullRequestController.java new file mode 100644 index 0000000..7acebde --- /dev/null +++ b/src/main/java/com/server/domain/pull/controller/PullRequestController.java @@ -0,0 +1,48 @@ +package com.server.domain.pull.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.server.domain.pull.dto.GetPullRequestInfoOutDto; +import com.server.domain.pull.dto.GetPullRequestOutDto; +import com.server.domain.pull.enums.PullRequestState; +import com.server.domain.pull.service.PullRequestService; +import com.server.global.dto.ApiResponseDto; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/pulls") +public class PullRequestController { + + private final PullRequestService pullRequestService; + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{userOrOrgName}/{repositoryName}") + @Operation(summary = "레포지토리에 따른 pull requests 조회", description = "- all, open, closed의 상태로 filtering 가능
- Merge 완료된 pull request는 merged_at 시간 존재") + public ApiResponseDto> getPullRequests(HttpServletRequest request, + @PathVariable String userOrOrgName, @PathVariable String repositoryName, + @RequestParam(required = false) PullRequestState state) { + return ApiResponseDto.success(HttpStatus.OK.value(), + pullRequestService.getPullRequests(request, userOrOrgName, repositoryName, state)); + } + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{userOrOrgName}/{repositoryName}/{pullNo}") + @Operation(summary = "", description = "") + public ApiResponseDto getPullRequests(HttpServletRequest request, + @PathVariable String userOrOrgName, @PathVariable String repositoryName, @PathVariable int pullNo) { + return ApiResponseDto.success(HttpStatus.OK.value(), + pullRequestService.getPullRequest(request, userOrOrgName, repositoryName, pullNo)); + } +} diff --git a/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java b/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java new file mode 100644 index 0000000..6f27248 --- /dev/null +++ b/src/main/java/com/server/domain/pull/dto/GetPullRequestInfoOutDto.java @@ -0,0 +1,37 @@ +package com.server.domain.pull.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class GetPullRequestInfoOutDto extends PullRequestDto { + + @JsonProperty("merged") + Boolean merged; + + @JsonProperty("mergeable") + Boolean mergeable; + + @JsonProperty("mergeable_state") + String mergeableState; + + // NOTE: 일반 댓글 (comments)는 고려하지 않고 리뷰 댓글 (review_comments)만 고려 + @JsonProperty("review_comments") + int comments; + + @JsonProperty("commits") + int commits; + + @JsonProperty("additions") + int additions; + + @JsonProperty("deletions") + int deletions; + + @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 new file mode 100644 index 0000000..9cfd942 --- /dev/null +++ b/src/main/java/com/server/domain/pull/dto/GetPullRequestOutDto.java @@ -0,0 +1,9 @@ +package com.server.domain.pull.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +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 new file mode 100644 index 0000000..eb9233a --- /dev/null +++ b/src/main/java/com/server/domain/pull/dto/PullRequestDto.java @@ -0,0 +1,48 @@ +package com.server.domain.pull.dto; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.server.domain.pull.enums.PullRequestState; + +import lombok.Getter; + +@Getter +public class PullRequestDto { + + @JsonProperty("number") + private Integer prId; + + @JsonProperty("html_url") + private String url; + + @JsonProperty("user") + private void unpackUser(JsonNode user) { + this.ownerId = user.get("login").asText(); + } + + private String ownerId; + + @JsonProperty("title") + private String title; + + @JsonProperty("head") + private void unpackHead(JsonNode head) { + this.branchName = head.get("ref").asText(); + } + + private String branchName; + + @JsonProperty("state") + private PullRequestState status; + + @JsonProperty("created_at") + private LocalDateTime createdAt; + + @JsonProperty("closed_at") + private LocalDateTime closedAt; + + @JsonProperty("merged_at") + private LocalDateTime mergedAt; +} diff --git a/src/main/java/com/server/domain/pull/enums/PullRequestState.java b/src/main/java/com/server/domain/pull/enums/PullRequestState.java new file mode 100644 index 0000000..55d2f6c --- /dev/null +++ b/src/main/java/com/server/domain/pull/enums/PullRequestState.java @@ -0,0 +1,33 @@ +package com.server.domain.pull.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonValue; + +@JsonFormat(shape = JsonFormat.Shape.STRING) +public enum PullRequestState { + ALL("all"), + OPEN("open"), + CLOSED("closed"); + + private final String value; + + PullRequestState(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static PullRequestState fromValue(String value) { + for (PullRequestState status : values()) { + if (status.value.equalsIgnoreCase(value)) { + return status; + } + } + throw new IllegalArgumentException("Invalid status: " + value); + } +} diff --git a/src/main/java/com/server/domain/pull/enums/PullRequestStateEditor.java b/src/main/java/com/server/domain/pull/enums/PullRequestStateEditor.java new file mode 100644 index 0000000..bad1d43 --- /dev/null +++ b/src/main/java/com/server/domain/pull/enums/PullRequestStateEditor.java @@ -0,0 +1,11 @@ +package com.server.domain.pull.enums; + +import java.beans.PropertyEditorSupport; + +// NOTE: Controller에서 소문자로 입력되어도 enum type으로 인식하기 위하여 필요 +public class PullRequestStateEditor extends PropertyEditorSupport { + @Override + public void setAsText(String text) throws IllegalArgumentException { + setValue(PullRequestState.fromValue(text)); + } +} diff --git a/src/main/java/com/server/domain/pull/service/PullRequestService.java b/src/main/java/com/server/domain/pull/service/PullRequestService.java new file mode 100644 index 0000000..8a77441 --- /dev/null +++ b/src/main/java/com/server/domain/pull/service/PullRequestService.java @@ -0,0 +1,90 @@ +package com.server.domain.pull.service; + +import java.util.List; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.server.domain.pull.dto.GetPullRequestInfoOutDto; +import com.server.domain.pull.dto.GetPullRequestOutDto; +import com.server.domain.pull.enums.PullRequestState; +import com.server.domain.user.entity.User; +import com.server.domain.user.service.UserService; +import com.server.global.error.code.AuthErrorCode; +import com.server.global.error.exception.AuthException; +import com.server.global.jwt.JwtService; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class PullRequestService { + + private final UserService userService; + private final JwtService jwtService; + + 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); + + 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); + } + return githubPullRequests.getBody(); + } + + public GetPullRequestInfoOutDto getPullRequest(HttpServletRequest request, String userOrOrgName, + String repositoryName, int pullNo) { + String username = jwtService.extractUsernameFromToken(request) + .orElseThrow(() -> new AuthException(AuthErrorCode.INVALID_ACCESS_TOKEN)); + User user = userService.getUserWithPersonalInfo(username); + + RestTemplate restTemplate = new RestTemplate(); + String url = String.format("https://api.github.com/repos/%s/%s/pulls/%s", userOrOrgName, repositoryName, + pullNo); + ResponseEntity githubPullRequest = restTemplate.exchange( + url, + HttpMethod.GET, + buildGithubHttpEntity(user.getGithubToken()), + GetPullRequestInfoOutDto.class); + return githubPullRequest.getBody(); + } + + // TODO: 아래 endpoint에서 어떤 파일들이 어떻게 변경 되었는지 파악할 수 있음 + // String url = + // String.format("https://api.github.com/repos/%s/%s/pulls/%s/files", + // userOrOrgName, repositoryName, + // pullNo); + + private HttpEntity> buildGithubHttpEntity(String accessToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Accept", "application/vnd.github+json"); + headers.add("X-GitHub-Api-Version", "2022-11-28"); + headers.setBearerAuth(accessToken); + return new HttpEntity<>(params, headers); + } +} diff --git a/src/main/java/com/server/domain/pullrequest/dto/PullRequestDto.java b/src/main/java/com/server/domain/pullrequest/dto/PullRequestDto.java deleted file mode 100644 index 5cfb83f..0000000 --- a/src/main/java/com/server/domain/pullrequest/dto/PullRequestDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.server.domain.pullrequest.dto; - -import java.time.LocalDateTime; -import java.util.UUID; - -import com.server.domain.pullrequest.enums.PullRequestStatus; - -import lombok.Getter; - -@Getter -public class PullRequestDto { - private UUID id; - private Integer prId; - private String url; - private String ownerId; - private String title; - private String description; - private String branchName; - private PullRequestStatus status; - private LocalDateTime createdAt; -} diff --git a/src/main/java/com/server/domain/pullrequest/enums/PullRequestStatus.java b/src/main/java/com/server/domain/pullrequest/enums/PullRequestStatus.java deleted file mode 100644 index 745640e..0000000 --- a/src/main/java/com/server/domain/pullrequest/enums/PullRequestStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.server.domain.pullrequest.enums; - -public enum PullRequestStatus { - OPEN, - CLOSED, - MERGED -}