Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -29,86 +29,86 @@ public class GitHubService {
public Mono<Void> updateRepoContribution(String owner, String repo) {

return getContributors(owner, repo)
.flatMapMany(contributors -> {
String contributorKey = buildContributorsKey(owner, repo);
List<String> logins = contributors.stream()
.map(GitHubContributor::login)
.collect(Collectors.toList());

return reactiveRedisTemplate.delete(contributorKey)
.thenMany(Flux.fromIterable(logins))
.flatMap(login -> reactiveRedisTemplate.opsForList()
.rightPush(contributorKey, login))
.then(reactiveRedisTemplate.expire(contributorKey, TTL))
.thenMany(Flux.fromIterable(contributors));
})
.flatMap(contributor -> updateContributorScore(owner, repo, contributor))
.then();
.flatMapMany(contributors -> {
String contributorKey = buildContributorsKey(owner, repo);
List<String> logins = contributors.stream()
.map(GitHubContributor::login)
.collect(Collectors.toList());

return reactiveRedisTemplate.delete(contributorKey)
.thenMany(Flux.fromIterable(logins))
.flatMap(login -> reactiveRedisTemplate.opsForList()
.rightPush(contributorKey, login))
.then(reactiveRedisTemplate.expire(contributorKey, TTL))
.thenMany(Flux.fromIterable(contributors));
})
.flatMap(contributor -> updateContributorScore(owner, repo, contributor))
.then();
}

private Mono<List<GitHubContributor>> getContributors(String owner, String repo) {

return githubWebClient.get()
.uri("/repos/{owner}/{repo}/contributors", owner, repo)
.retrieve()
.bodyToFlux(GitHubContributor.class)
.collectList();
.uri("/repos/{owner}/{repo}/contributors", owner, repo)
.retrieve()
.bodyToFlux(GitHubContributor.class)
.collectList();
}

private Mono<Void> updateContributorScore(String owner, String repo,
GitHubContributor contributor) {
GitHubContributor contributor) {

String login = contributor.login();

return Mono.zip(
getCommitCount(owner, repo, login),
getPullRequestCount(owner, repo, login),
getMergedPullRequestCount(owner, repo, login),
getIssueCount(owner, repo, login)
getCommitCount(owner, repo, login),
getPullRequestCount(owner, repo, login),
getMergedPullRequestCount(owner, repo, login),
getIssueCount(owner, repo, login)
).flatMap(tuple -> {
long commit = tuple.getT1();
long pr = tuple.getT2();
long mpr = tuple.getT3();
long issue = tuple.getT4();
long score = commit + pr + mpr + issue;
double score = commit * 0.1 + pr * 0.2 + mpr + issue * 0.2;

String detailKey = buildDetailKey(owner, repo, login);

return reactiveRedisTemplate.opsForHash().putAll(detailKey, Map.of(
"commit", String.valueOf(commit),
"pr", String.valueOf(pr),
"mpr", String.valueOf(mpr),
"issue", String.valueOf(issue),
"score", String.valueOf(score)
))
.then(reactiveRedisTemplate.expire(detailKey, TTL));
"commit", String.valueOf(commit),
"pr", String.valueOf(pr),
"mpr", String.valueOf(mpr),
"issue", String.valueOf(issue),
"score", String.valueOf(score)
))
.then(reactiveRedisTemplate.expire(detailKey, TTL));
}).then();
}

private Mono<Long> getCommitCount(String owner, String repo, String login) {

return getAllCommits(owner, repo, 1)
.filter(commit -> {
Map author = (Map) commit.get("author");
.filter(commit -> {
Map author = (Map) commit.get("author");

return author != null && login.equalsIgnoreCase((String) author.get("login"));
})
.count();
return author != null && login.equalsIgnoreCase((String) author.get("login"));
})
.count();
}

private Flux<Map> getAllCommits(String owner, String repo, int page) {

return githubWebClient.get()
.uri(uriBuilder -> uriBuilder
.path("/repos/{owner}/{repo}/commits")
.queryParam("per_page", PER_PAGE)
.queryParam("page", page)
.build(owner, repo))
.retrieve()
.bodyToFlux(Map.class)
.collectList()
.flatMapMany(list -> list.size() < PER_PAGE ? Flux.fromIterable(list)
: Flux.fromIterable(list).concatWith(getAllCommits(owner, repo, page + 1)));
.uri(uriBuilder -> uriBuilder
.path("/repos/{owner}/{repo}/commits")
.queryParam("per_page", PER_PAGE)
.queryParam("page", page)
.build(owner, repo))
.retrieve()
.bodyToFlux(Map.class)
.collectList()
.flatMapMany(list -> list.size() < PER_PAGE ? Flux.fromIterable(list)
: Flux.fromIterable(list).concatWith(getAllCommits(owner, repo, page + 1)));
}

private Mono<Long> getPullRequestCount(String owner, String repo, String login) {
Expand All @@ -127,14 +127,14 @@ private Mono<Long> getIssueCount(String owner, String repo, String login) {
}

private Mono<Long> queryGithubIssueCount(String owner, String repo, String login,
String queryType) {
String queryType) {
String query = String.format("repo:%s/%s+%s+author:%s", owner, repo, queryType, login);

return githubWebClient.get()
.uri(uriBuilder -> uriBuilder.path("/search/issues").queryParam("q", query).build())
.retrieve()
.bodyToMono(Map.class)
.map(map -> ((Number) map.get("total_count")).longValue());
.uri(uriBuilder -> uriBuilder.path("/search/issues").queryParam("q", query).build())
.retrieve()
.bodyToMono(Map.class)
.map(map -> ((Number) map.get("total_count")).longValue());
}

private String buildDetailKey(String owner, String repo, String login) {
Expand All @@ -154,23 +154,23 @@ public Mono<List<String>> getContributorsFromRedis(String owner, String repo) {
String key = "contributors:" + owner + ":" + repo;

return reactiveRedisTemplate.opsForList()
.range(key, 0, -1)
.collectList();
.range(key, 0, -1)
.collectList();
}

/**
* 특정 contributor에 대한 상세 점수 정보 가져오기
*/
public Mono<Map<String, String>> getContributorDetailFromRedis(String owner, String repo,
String contributor) {
String contributor) {
String detailKey = "detail:" + owner + ":" + repo + ":" + contributor;

return reactiveRedisTemplate.opsForHash()
.entries(detailKey)
.collectMap(
e -> e.getKey().toString(),
e -> e.getValue().toString()
);
.entries(detailKey)
.collectMap(
e -> e.getKey().toString(),
e -> e.getValue().toString()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -39,7 +40,7 @@ public String showCreateForm(@SessionAttribute Long userId, Model model) {
// 저장 (POST)
@PostMapping
public String createGitHubRepo(@SessionAttribute Long userId,
@ModelAttribute GithubRepoCreateRequest request) {
@ModelAttribute GithubRepoCreateRequest request) {

GithubRepoCreateResponse response = githubRepoService.createGithubRepo(userId, request);
gitHubService.updateRepoContribution(response.owner(), response.repo()).subscribe();
Expand All @@ -50,7 +51,7 @@ public String createGitHubRepo(@SessionAttribute Long userId,
// 목록 조회 (GET)
@GetMapping
public String getGithubRepos(@SessionAttribute Long userId,
@RequestParam(defaultValue = "0") int page, Model model) {
@RequestParam(defaultValue = "0") int page, Model model) {

GithubRepoPageResponse response = githubRepoService.getGithubRepoInfos(userId, page);
model.addAttribute("repositories", response);
Expand All @@ -62,17 +63,17 @@ public String getGithubRepos(@SessionAttribute Long userId,
// 단일 조회 (GET)
@GetMapping("/{repositoryId}")
public String getRepositoryId(@SessionAttribute Long userId,
@PathVariable long repositoryId, Model model) {
@PathVariable long repositoryId, Model model) {

GithubRepoGetResponse response = githubRepoService.getGithubRepoInfo(userId, repositoryId);
model.addAttribute("repoInfo", response);

return "repo/detail";
}

@PostMapping("/{repositoryId}")
@DeleteMapping("/{repositoryId}")
public String deleteRepository(@SessionAttribute Long userId,
@PathVariable long repositoryId) {
@PathVariable long repositoryId) {

githubRepoService.deleteRepo(userId, repositoryId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.specialwarriors.conal.user.domain.User;
import com.specialwarriors.conal.user.service.UserQuery;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -75,25 +76,40 @@ private void validateCreateRequest(GithubRepoCreateRequest request) {
if (request.name().isEmpty()) {
throw new GeneralException(GithubRepoException.NOT_FOUND_GITHUBREPO_NAME);
}

if (!GITHUB_URL_PATTERN.matcher(request.url()).matches()) {
throw new GeneralException(GithubRepoException.INVALID_GITHUBREPO_URL);
}
if (request.emails().isEmpty()) {

long validEmailCount = request.emails().stream()
.filter(Objects::nonNull)
.filter(email -> !email.trim().isEmpty())
.count();

if (validEmailCount == 0) {
throw new GeneralException(GithubRepoException.NOT_FOUND_GITHUBREPO_EMAIL);
}
if (request.emails().size() > 5) {
if (validEmailCount > 5) {
throw new GeneralException(GithubRepoException.EXCEED_GITHUBREPO_EMAIL);
}

for (String email : request.emails()) {
if (!EMAIL_PATTERN.matcher(email).matches()) {
throw new GeneralException(GithubRepoException.INVALID_GITHUBREPO_EMAIL);

if (Objects.nonNull(email)) {
String trimmed = email.trim();
if (!trimmed.isEmpty() && !EMAIL_PATTERN.matcher(trimmed).matches()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약에 이메일 패턴은 통과했는데 해당 이메일이 존재하지 않으면 어디서 에러를 잡는지 궁금합니다~

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 코드에서 EMAIL_PATTERN은 문자열이 이메일 형식에 맞는지만 검증합니다. 즉, 정규식 검증을 통과한다고 해서 실제 존재하는 이메일이라는 보장은 없습니다.

그래서 SMTP ping 또는 인증 메일 전송 등으로 유효성을 확인해야 합니다.

SMTP 핑은 DNS 조회로 메일 서버 존재 확인 후 RCPT TO 명령으로 존재 여부를 탐색하지만 대부분 메일 서버가 거부하고
가장확실한 방법은 인증 메일 전송으로 유효성을 체크하는 것인데 아직 구현하지 않았습니다.

throw new GeneralException(GithubRepoException.INVALID_GITHUBREPO_EMAIL);
}
}

}

if (request.endDate() == null) {
throw new GeneralException(GithubRepoException.INVALID_GITHUBREPO_DURATION);
}
}


private List<Contributor> createAndSaveContributors(Set<String> emails) {

List<Contributor> contributors = emails.stream()
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/templates/main/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
</a>
<form th:action="@{|/users/repositories/${repository.id}|}" method="post"
onsubmit="return confirm('정말 레포지토리를 삭제하시겠습니까?');">
<input type="hidden" name="_method" value="delete"/>
<button class="repo-delete" type="submit">x</button>
</form>
</div>
Expand Down
44 changes: 0 additions & 44 deletions src/main/resources/templates/repo/list.html

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ class ConalApplicationTests {
@Test
void contextLoads() {
}
}
}