-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from Leets-Official/6-fetch-from-Github
- Loading branch information
Showing
13 changed files
with
378 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 7 additions & 2 deletions
9
src/main/java/com/leets/commitatobe/domain/commit/domain/repository/CommitRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
package com.leets.commitatobe.domain.commit.domain.repository; | ||
|
||
import com.leets.commitatobe.domain.commit.domain.Commit; | ||
import com.leets.commitatobe.domain.user.domain.User; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
|
||
@Repository | ||
public interface CommitRepository extends JpaRepository<Commit, UUID> { | ||
List<Commit> findAllByUser(User user); | ||
|
||
Optional<Commit> findByCommitDateAndUser(LocalDateTime commitDate, User user); | ||
} |
17 changes: 15 additions & 2 deletions
17
src/main/java/com/leets/commitatobe/domain/commit/presentation/CommitController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,24 @@ | ||
package com.leets.commitatobe.domain.commit.presentation; | ||
import com.leets.commitatobe.domain.commit.presentation.dto.response.CommitResponse; | ||
import com.leets.commitatobe.domain.commit.usecase.FetchCommits; | ||
import com.leets.commitatobe.global.response.ApiResponse; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/commit") | ||
public class CommitController { | ||
private final FetchCommits fetchCommits; | ||
|
||
@Operation( | ||
summary = "์ปค๋ฐ ๊ธฐ๋ก ๋ถ๋ฌ์ค๊ธฐ", | ||
description = "GitHub์์ ์ปค๋ฐ ๊ธฐ๋ก์ ๊ฐ์ ธ์ DB์ ์ ์ฅํฉ๋๋ค.") | ||
@PostMapping("/fetch") | ||
public ApiResponse<CommitResponse> fetchCommits(HttpServletRequest request) { | ||
return ApiResponse.onSuccess(fetchCommits.execute(request)); | ||
} | ||
} |
4 changes: 3 additions & 1 deletion
4
...in/java/com/leets/commitatobe/domain/commit/presentation/dto/response/CommitResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
package com.leets.commitatobe.domain.commit.presentation.dto.response; | ||
|
||
public record CommitResponse() { | ||
public record CommitResponse( | ||
String message | ||
) { | ||
} |
93 changes: 93 additions & 0 deletions
93
src/main/java/com/leets/commitatobe/domain/commit/usecase/FetchCommits.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package com.leets.commitatobe.domain.commit.usecase; | ||
|
||
import com.leets.commitatobe.domain.commit.domain.Commit; | ||
import com.leets.commitatobe.domain.commit.domain.repository.CommitRepository; | ||
import com.leets.commitatobe.domain.commit.presentation.dto.response.CommitResponse; | ||
import com.leets.commitatobe.domain.login.usecase.LoginCommandServiceImpl; | ||
import com.leets.commitatobe.domain.login.usecase.LoginQueryService; | ||
import com.leets.commitatobe.domain.user.domain.User; | ||
import com.leets.commitatobe.domain.user.domain.repository.UserRepository; | ||
import com.leets.commitatobe.global.exception.ApiException; | ||
import com.leets.commitatobe.global.response.code.status.ErrorStatus; | ||
import com.leets.commitatobe.global.response.code.status.SuccessStatus; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.io.IOException; | ||
import java.time.LocalDateTime; | ||
import java.util.*; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class FetchCommits { | ||
private final CommitRepository commitRepository; | ||
private final UserRepository userRepository; | ||
private final GitHubService gitHubService; // GitHub API ํต์ | ||
private final LoginCommandServiceImpl loginCommandService; | ||
private final LoginQueryService loginQueryService; | ||
|
||
public CommitResponse execute(HttpServletRequest request) { | ||
String gitHubId = loginQueryService.getGitHubId(request); | ||
User user = userRepository.findByGithubId(gitHubId) | ||
.orElseThrow(() -> new UsernameNotFoundException("ํด๋นํ๋ ๊นํ๋ธ ๋๋ค์๊ณผ ์ผ์นํ๋ ์ ์ ๋ฅผ ์ฐพ์ ์ ์์: " + gitHubId)); | ||
|
||
LocalDateTime dateTime; | ||
try { | ||
dateTime = commitRepository.findAllByUser(user).stream() | ||
.max(Comparator.comparing(Commit::getUpdatedAt)) | ||
.orElseThrow(() -> new ApiException(ErrorStatus._COMMIT_NOT_FOUND)) | ||
.getUpdatedAt(); | ||
} catch (ApiException e) { | ||
dateTime = LocalDateTime.now(); | ||
// dateTime = LocalDateTime.of(2024, 07, 01, 0, 0, 0); // test ์ฉ | ||
} | ||
|
||
try { | ||
// Github API Access Token ์ ์ฅ | ||
gitHubService.updateToken(loginCommandService.gitHubLogin(gitHubId)); | ||
|
||
List<String> repos = gitHubService.fetchRepos(gitHubId); | ||
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); | ||
List<CompletableFuture<Void>> futures = new ArrayList<>(); | ||
LocalDateTime finalDateTime = dateTime; // ์ค๋ฅ ๋ฐฉ์ง | ||
|
||
for (String fullName : repos) { | ||
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> { | ||
try { | ||
gitHubService.countCommits(fullName, gitHubId, finalDateTime); | ||
} catch (IOException e) { | ||
throw new ApiException(ErrorStatus._GIT_URL_INCORRECT); | ||
} | ||
}, executor); | ||
futures.add(voidCompletableFuture); | ||
} | ||
|
||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); | ||
allFutures.join(); | ||
|
||
executor.shutdown(); | ||
|
||
saveCommits(user); | ||
|
||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
return new CommitResponse(SuccessStatus._OK.getMessage()); | ||
} | ||
|
||
private void saveCommits(User user) { | ||
// ๋ ์ง๋ณ ์ปค๋ฐ ์ DB์ ์ ์ฅ | ||
for (Map.Entry<LocalDateTime, Integer> entry : gitHubService.getCommitsByDate().entrySet()) { | ||
Commit commit = commitRepository.findByCommitDateAndUser(entry.getKey(), user) | ||
.orElse(Commit.create(entry.getKey(), 0, user)); | ||
commit.updateCnt(entry.getValue()); | ||
commitRepository.save(commit); | ||
} | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
src/main/java/com/leets/commitatobe/domain/commit/usecase/GitHubService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package com.leets.commitatobe.domain.commit.usecase; | ||
|
||
import com.google.gson.JsonArray; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParser; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.net.HttpURLConnection; | ||
import java.net.URL; | ||
import java.time.LocalDate; | ||
import java.time.LocalDateTime; | ||
import java.time.ZoneOffset; | ||
import java.time.ZonedDateTime; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.*; | ||
import java.util.concurrent.ForkJoinPool; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
@Getter | ||
public class GitHubService { | ||
private final String GITHUB_API_URL = "https://api.github.com"; | ||
private String AUTH_TOKEN; | ||
private final Map<LocalDateTime, Integer> commitsByDate = new HashMap<>(); | ||
|
||
// GitHub repository ์ด๋ฆ ์ ์ฅ | ||
public List<String> fetchRepos(String gitHubUsername) throws IOException { | ||
URL url = new URL(GITHUB_API_URL + "/user/repos?type=all&sort=pushed&per_page=100"); | ||
HttpURLConnection connection = getConnection(url); | ||
JsonArray repos = fetchJsonArray(connection); | ||
|
||
if (repos == null) { | ||
return new ArrayList<>(); | ||
} | ||
|
||
List<String> repoFullNames = new ArrayList<>(); | ||
for (int i = 0; i < repos.size(); i++) { | ||
String fullName = repos.get(i).getAsJsonObject().get("full_name").getAsString(); | ||
repoFullNames.add(fullName); | ||
} | ||
|
||
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors()); | ||
return forkJoinPool.submit(() -> | ||
repoFullNames.parallelStream() | ||
.filter(fullName -> { | ||
try { | ||
return isContributor(fullName, gitHubUsername); | ||
} catch (IOException e) { | ||
return false; | ||
} | ||
}) | ||
.collect(Collectors.toList()) | ||
).join(); | ||
} | ||
|
||
// ์์ ์ด ํด๋น repository์ ๊ธฐ์ฌ์ ์ธ์ง ํ์ธ | ||
private boolean isContributor(String fullName, String gitHubUsername) throws IOException { | ||
if (fullName.contains(gitHubUsername)) { | ||
return true; | ||
} | ||
|
||
URL url = new URL(GITHUB_API_URL + "/repos/" + fullName + "/contributors"); | ||
HttpURLConnection connection = getConnection(url); | ||
JsonArray contributors = fetchJsonArray(connection); | ||
|
||
if (contributors == null) { | ||
return false; | ||
} | ||
|
||
for (int i = 0; i < contributors.size(); i++) { | ||
JsonObject contributor = contributors.get(i).getAsJsonObject(); | ||
String contributorLogin = contributor.get("login").getAsString(); | ||
|
||
if (contributorLogin.equals(gitHubUsername)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
// commit์ ์ผ๋ณ๋ก ์ ๋ฆฌ | ||
public void countCommits(String fullName, String gitHubUsername, LocalDateTime date) throws IOException { | ||
int page = 1; | ||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); | ||
|
||
while (true) { | ||
URL url = new URL(GITHUB_API_URL + "/repos/" + fullName + "/commits?page=" + page + "&per_page=100"); | ||
HttpURLConnection connection = getConnection(url); | ||
JsonArray commits = fetchJsonArray(connection); | ||
|
||
if (commits == null || commits.isEmpty()) { | ||
return; | ||
} | ||
|
||
for (int i = 0; i < commits.size(); i++) { | ||
String commitDateTime = getCommitDateTime(commits.get(i).getAsJsonObject()); | ||
|
||
int comparisonResult = commitDateTime.compareTo(formatToISO8601(date)); | ||
|
||
if (comparisonResult < 0 || !commits.get(i).getAsJsonObject().get("author").getAsJsonObject().get("login").getAsString().equals(gitHubUsername)) { | ||
continue; | ||
} | ||
|
||
LocalDateTime commitDate = LocalDate.parse(commitDateTime.substring(0, 10), formatter).atStartOfDay(); | ||
|
||
synchronized (commitsByDate) { | ||
commitsByDate.put(commitDate, commitsByDate.getOrDefault(commitDate, 0) + 1); | ||
} | ||
} | ||
|
||
page++; | ||
} | ||
} | ||
|
||
// http ์ฐ๊ฒฐ | ||
private HttpURLConnection getConnection(URL url) throws IOException { | ||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | ||
connection.setRequestMethod("GET"); | ||
connection.setRequestProperty("Authorization", "token " + AUTH_TOKEN); | ||
connection.setRequestProperty("Accept", "application/vnd.github.v3+json"); | ||
return connection; | ||
} | ||
|
||
// ์๋ต์ jsonObject๋ก ๋ฐํ | ||
private JsonObject fetchJsonObject(HttpURLConnection connection) throws IOException { | ||
int responseCode = connection.getResponseCode(); | ||
if (responseCode == HttpURLConnection.HTTP_OK) { | ||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { | ||
return JsonParser.parseReader(in).getAsJsonObject(); | ||
} | ||
} else { | ||
System.err.println(responseCode); | ||
return null; | ||
} | ||
} | ||
|
||
// ์๋ต์ JsonArray๋ก ๋ฐํ | ||
private JsonArray fetchJsonArray(HttpURLConnection connection) throws IOException { | ||
int responseCode = connection.getResponseCode(); | ||
if (responseCode == HttpURLConnection.HTTP_OK) { | ||
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { | ||
return JsonParser.parseReader(in).getAsJsonArray(); | ||
} | ||
} else { | ||
System.err.println(responseCode); | ||
return null; | ||
} | ||
} | ||
|
||
// commit ์๊ฐ ์ถ์ถ | ||
private String getCommitDateTime(JsonObject commit) { | ||
String originCommitDateTime = commit.get("commit").getAsJsonObject() // UTC+0 | ||
.get("author").getAsJsonObject() | ||
.get("date").getAsString(); | ||
|
||
// ์ ๋ ฅ๋ ์๊ฐ ๋ฌธ์์ด์ LocalDateTime์ผ๋ก ๋ณํ | ||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); | ||
LocalDateTime dateTime = LocalDateTime.parse(originCommitDateTime, formatter); | ||
|
||
// 9์๊ฐ ์ถ๊ฐ -> UTC+9(ํ๊ตญ ํ์ค ์) | ||
return dateTime.plusHours(9).format(formatter); | ||
} | ||
|
||
// GitHub API์์ ์ ๊ณตํ๋ ์๊ฐ ํํ๋ฒ์ผ๋ก ๋ณํ | ||
private String formatToISO8601(LocalDateTime dateTime) { | ||
ZonedDateTime zonedDateTime = dateTime.atZone(ZoneOffset.UTC); | ||
return DateTimeFormatter.ISO_INSTANT.format(zonedDateTime); | ||
} | ||
|
||
// GitHub Access Token ์ ์ฅ | ||
public void updateToken(String accessToken) { | ||
this.AUTH_TOKEN = accessToken; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.