Skip to content

Commit def638f

Browse files
authored
Merge branch 'dev' into feat/#20
2 parents cf4a2f8 + 57c390f commit def638f

21 files changed

+501
-26
lines changed

โ€Ž.gitignoreโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ docker-compose.yaml
7575

7676
### dbํŒŒ์ผ
7777
db/
78+
.docker
79+
data/
7880

7981
### secret ํ”„๋กœํ•„
8082
application-secret.yml

โ€Žbuild.gradle.ktsโ€Ž

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,13 @@ dependencies {
4040
compileOnly("org.projectlombok:lombok")
4141
annotationProcessor("org.projectlombok:lombok")
4242

43-
// DB
43+
//DB
4444
runtimeOnly("com.h2database:h2")
4545
runtimeOnly("com.mysql:mysql-connector-j")
4646
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
4747

48-
// redis
48+
//redis
4949
implementation("org.springframework.boot:spring-boot-starter-data-redis")
50-
implementation("org.springframework.session:spring-session-data-redis")
51-
52-
// actuator
53-
implementation("org.springframework.boot:spring-boot-starter-actuator")
5450

5551
// Security
5652
implementation("org.springframework.boot:spring-boot-starter-security")
@@ -59,10 +55,7 @@ dependencies {
5955

6056
//Swagger
6157
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
62-
63-
//Web Socket
6458
implementation("org.java-websocket:Java-WebSocket:1.5.2")
65-
implementation ("org.springframework:spring-messaging")
6659

6760
// JWT
6861
implementation("io.jsonwebtoken:jjwt-api:0.12.6")
@@ -81,6 +74,17 @@ dependencies {
8174
implementation ("org.springframework.security:spring-security-oauth2-client:6.4.2") // Or the version you're using
8275
implementation ("org.springframework.security:spring-security-oauth2-core:6.4.2") // Or the version you're using
8376

77+
// Spring Kafka
78+
implementation("org.springframework.kafka:spring-kafka")
79+
80+
// Kafka ํด๋ผ์ด์–ธํŠธ (Redpanda์™€ ํ˜ธํ™˜)
81+
implementation("org.apache.kafka:kafka-clients:3.6.0")
82+
83+
// JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” (Kafka ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ)
84+
implementation("com.fasterxml.jackson.core:jackson-databind")
85+
86+
// ํ…Œ์ŠคํŠธ์šฉ Kafka ์ž„๋ฒ ๋””๋“œ ์„œ๋ฒ„ (์„ ํƒ ์‚ฌํ•ญ)
87+
testImplementation("org.springframework.kafka:spring-kafka-test")
8488
}
8589

8690
tasks.withType<Test> {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmf.commitField.domain.commit.scheduler;
2+
3+
import cmf.commitField.domain.commit.sinceCommit.service.CommitCacheService;
4+
import cmf.commitField.domain.commit.sinceCommit.service.GithubService;
5+
import cmf.commitField.domain.redpanda.RedpandaProducer;
6+
import cmf.commitField.domain.user.entity.User;
7+
import cmf.commitField.domain.user.repository.UserRepository;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.List;
14+
15+
@Slf4j
16+
@Service
17+
@RequiredArgsConstructor
18+
public class CommitScheduler {
19+
private final GithubService githubService;
20+
private final CommitCacheService commitCacheService;
21+
private final RedpandaProducer redpandaProducer;
22+
private final UserRepository userRepository;
23+
24+
@Scheduled(fixedRate = 60000) // 1๋ถ„๋งˆ๋‹ค ์‹คํ–‰
25+
public void updateUserCommits() {
26+
List<User> activeUsers = userRepository.findAll(); // ๐Ÿ’ซ ๋ณ€๊ฒฝ ํ•„์š”, ์ฐจํ›„ active ์ƒํƒœ์ธ user๋งŒ ์ฐพ๊ฒŒ๋” ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ.
27+
28+
for (User user : activeUsers) {
29+
Integer cachedCount = commitCacheService.getCachedCommitCount(user.getUsername());
30+
int newCommitCount = githubService.getUserCommitCount(user.getUsername());
31+
32+
if (cachedCount == null || cachedCount != newCommitCount) { // ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์ฒ˜๋ฆฌ
33+
commitCacheService.updateCachedCommitCount(user.getUsername(), newCommitCount);
34+
redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount);
35+
}
36+
}
37+
}
38+
}

โ€Žsrc/main/java/cmf/commitField/domain/commit/sinceCommit/controller/SinceCommitController.javaโ€Ž

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import cmf.commitField.domain.commit.sinceCommit.dto.CommitAnalysisResponseDto;
44
import cmf.commitField.domain.commit.sinceCommit.dto.SinceCommitResponseDto;
5+
import cmf.commitField.domain.commit.sinceCommit.service.GithubService;
56
import cmf.commitField.domain.commit.sinceCommit.service.SinceCommitService;
67
import lombok.RequiredArgsConstructor;
78
import org.springframework.format.annotation.DateTimeFormat;
89
import org.springframework.http.ResponseEntity;
910
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
1012
import org.springframework.web.bind.annotation.RequestParam;
1113
import org.springframework.web.bind.annotation.RestController;
1214

@@ -18,6 +20,7 @@
1820
@RequiredArgsConstructor
1921
public class SinceCommitController {
2022
private final SinceCommitService sinceCommitService;
23+
private final GithubService githubService;
2124

2225
@GetMapping("/api/github/commits-since")
2326
public ResponseEntity<List<SinceCommitResponseDto>> getCommits(
@@ -100,4 +103,12 @@ public ResponseEntity<CommitAnalysisResponseDto> getWinterSeasonCommits(
100103
CommitAnalysisResponseDto analysis = sinceCommitService.getCommitAnalysis(owner, repo, since, until);
101104
return ResponseEntity.ok(analysis);
102105
}
106+
107+
// api ํ…Œ์ŠคํŠธ ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€
108+
@GetMapping("/api/commit-count/{username}")
109+
public ResponseEntity<Integer> getCommitCount(@PathVariable String username) {
110+
System.out.println("โšก API ์—”๋“œํฌ์ธํŠธ ํ˜ธ์ถœ: " + username);
111+
int commitCount = githubService.getUserCommitCount(username);
112+
return ResponseEntity.ok(commitCount);
113+
}
103114
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmf.commitField.domain.commit.sinceCommit.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
import lombok.Setter;
7+
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
@Getter
11+
@Setter
12+
public class CommitData {
13+
String user;
14+
int commits;
15+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cmf.commitField.domain.commit.sinceCommit.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.data.redis.core.StringRedisTemplate;
6+
import org.springframework.stereotype.Service;
7+
8+
import java.time.Duration;
9+
10+
@Slf4j
11+
@Service
12+
@RequiredArgsConstructor
13+
public class CommitCacheService {
14+
private final StringRedisTemplate redisTemplate;
15+
16+
public Integer getCachedCommitCount(String username) {
17+
String key = "commit:" + username;
18+
String value = redisTemplate.opsForValue().get(key);
19+
return value != null ? Integer.parseInt(value) : null;
20+
}
21+
22+
public void updateCachedCommitCount(String username, int count) {
23+
String key = "commit:" + username;
24+
redisTemplate.opsForValue().set(key, String.valueOf(count), Duration.ofHours(1)); // 1์‹œ๊ฐ„ ์บ์‹ฑ
25+
}
26+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cmf.commitField.domain.commit.sinceCommit.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.core.ParameterizedTypeReference;
6+
import org.springframework.http.*;
7+
import org.springframework.stereotype.Service;
8+
import org.springframework.web.client.HttpClientErrorException;
9+
import org.springframework.web.client.RestTemplate;
10+
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
public class GithubService {
17+
private final RestTemplate restTemplate;
18+
private final String GITHUB_API_URL = "https://api.github.com";
19+
20+
@Value("${github.token}")
21+
private String GITHUB_TOKEN;
22+
23+
public int getUserCommitCount(String username) {
24+
HttpHeaders headers = new HttpHeaders();
25+
headers.set("Authorization", "Bearer " + GITHUB_TOKEN);
26+
headers.set("Accept", "application/vnd.github.v3+json"); // ์ตœ์‹  GitHub API ๋ฒ„์ „ ์ง€์ •
27+
28+
HttpEntity<String> entity = new HttpEntity<>(headers);
29+
String url = String.format("%s/users/%s/events", GITHUB_API_URL, username);
30+
31+
// ๐Ÿ“Œ API ํ˜ธ์ถœ ํšŸ์ˆ˜ ํ™•์ธ์šฉ ๋กœ๊ทธ ์ถ”๊ฐ€
32+
System.out.println("GitHub API ํ˜ธ์ถœ: " + url);
33+
34+
try {
35+
ResponseEntity<List<Map<String, Object>>> response =
36+
restTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<>() {});
37+
38+
// GitHub API Rate Limit ํ™•์ธ (๋‚จ์€ ์š”์ฒญ ํšŸ์ˆ˜ ๋กœ๊น…)
39+
HttpHeaders responseHeaders = response.getHeaders();
40+
String remainingRequests = responseHeaders.getFirst("X-RateLimit-Remaining");
41+
System.out.println("GitHub API ๋‚จ์€ ์š”์ฒญ ํšŸ์ˆ˜: " + remainingRequests);
42+
43+
int commitCount = 0;
44+
if (response.getBody() != null) {
45+
for (Map<String, Object> event : response.getBody()) {
46+
if ("PushEvent".equals(event.get("type"))) {
47+
Map<String, Object> payload = (Map<String, Object>) event.get("payload");
48+
if (payload != null && payload.containsKey("commits")) {
49+
List<?> commits = (List<?>) payload.get("commits");
50+
commitCount += (commits != null) ? commits.size() : 0;
51+
}
52+
}
53+
}
54+
}
55+
return commitCount;
56+
57+
} catch (HttpClientErrorException e) {
58+
System.err.println("GitHub API ์š”์ฒญ ์‹คํŒจ: " + e.getStatusCode() + " - " + e.getResponseBodyAsString());
59+
if (e.getStatusCode() == HttpStatus.UNAUTHORIZED) {
60+
throw new RuntimeException("GitHub API ์ธ์ฆ ์‹คํŒจ: ์˜ฌ๋ฐ”๋ฅธ ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.");
61+
} else if (e.getStatusCode() == HttpStatus.FORBIDDEN) {
62+
throw new RuntimeException("GitHub API ์š”์ฒญ ์ œํ•œ ์ดˆ๊ณผ (Rate Limit ์ดˆ๊ณผ). ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”.");
63+
}
64+
return 0; // ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
65+
} catch (Exception e) {
66+
System.err.println("์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: " + e.getMessage());
67+
return 0; // ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
68+
}
69+
}
70+
}

โ€Žsrc/main/java/cmf/commitField/domain/commit/totalCommit/controller/TotalCommitController.javaโ€Ž

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import org.springframework.web.bind.annotation.PathVariable;
88
import org.springframework.web.bind.annotation.RestController;
99

10+
import java.time.LocalDateTime;
11+
import java.time.temporal.TemporalAdjusters;
12+
1013
@RestController
1114
@RequiredArgsConstructor
1215
public class TotalCommitController {
@@ -16,4 +19,41 @@ public class TotalCommitController {
1619
public TotalCommitResponseDto getTotalCommits(@PathVariable String username) {
1720
return totalCommitService.getTotalCommitCount(username);
1821
}
22+
23+
// ๋ด„ ์‹œ์ฆŒ(3/1 - 5/31)
24+
@GetMapping("/api/commits/{username}/spring")
25+
public TotalCommitResponseDto getSpringSeasonCommits(@PathVariable String username) {
26+
int currentYear = LocalDateTime.now().getYear(); // ํ˜„์žฌ๋Š” ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ 2024 ๋Œ€์ž…
27+
LocalDateTime since = LocalDateTime.of(2024, 3, 1, 0, 0);
28+
LocalDateTime until = LocalDateTime.of(2024, 5, 31, 23, 59, 59);
29+
return totalCommitService.getSeasonCommits(username, since, until);
30+
}
31+
32+
// ์—ฌ๋ฆ„ ์‹œ์ฆŒ(6/1 - 8/31)
33+
@GetMapping("/api/commits/{username}/summer")
34+
public TotalCommitResponseDto getSummerSeasonCommits(@PathVariable String username) {
35+
int currentYear = LocalDateTime.now().getYear(); // ํ˜„์žฌ๋Š” ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ 2024 ๋Œ€์ž…
36+
LocalDateTime since = LocalDateTime.of(2024, 6, 1, 0, 0);
37+
LocalDateTime until = LocalDateTime.of(2024, 8, 31, 23, 59, 59);
38+
return totalCommitService.getSeasonCommits(username, since, until);
39+
}
40+
41+
// ๊ฐ€์„ ์‹œ์ฆŒ(9/1 - 11/30)
42+
@GetMapping("/api/commits/{username}/fall")
43+
public TotalCommitResponseDto getFallSeasonCommits(@PathVariable String username) {
44+
int currentYear = LocalDateTime.now().getYear(); // ํ˜„์žฌ๋Š” ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ 2024 ๋Œ€์ž…
45+
LocalDateTime since = LocalDateTime.of(2024, 9, 1, 0, 0);
46+
LocalDateTime until = LocalDateTime.of(2024, 11, 30, 23, 59, 59);
47+
return totalCommitService.getSeasonCommits(username, since, until);
48+
}
49+
50+
// ๊ฒจ์šธ ์‹œ์ฆŒ(์ด์ „ ๋…„๋„ 12/1 - ๋‹ค์Œ ๋…„๋„ 2/28)
51+
@GetMapping("/api/commits/{username}/winter")
52+
public TotalCommitResponseDto getWinterSeasonCommits(@PathVariable String username) {
53+
int currentYear = LocalDateTime.now().getYear(); // ํ˜„์žฌ๋Š” ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ 2024 ๋Œ€์ž…
54+
LocalDateTime since = LocalDateTime.of(2024 - 1, 12, 1, 0, 0);
55+
LocalDateTime until = LocalDateTime.of(2024, 2, 1, 23, 59, 59)
56+
.with(TemporalAdjusters.lastDayOfMonth());
57+
return totalCommitService.getSeasonCommits(username, since, until);
58+
}
1959
}

โ€Žsrc/main/java/cmf/commitField/domain/commit/totalCommit/dto/TotalCommitGraphQLResponse.javaโ€Ž

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import lombok.Getter;
44
import lombok.NoArgsConstructor;
55

6+
import java.util.List;
7+
68
/*
79
์‘๋‹ต๊ตฌ์กฐ ํ† ๋Œ€๋กœ ๊ตฌํ˜„
810
{
@@ -37,7 +39,28 @@ public static class CommitUser {
3739
@Getter
3840
@NoArgsConstructor
3941
public static class ContributionsCollection {
40-
private long totalCommitContributions;
41-
private long restrictedContributionsCount;
42+
private long totalCommitContributions; // ๊ณต๊ฐœ ๋ ˆํฌ ์ปค๋ฐ‹
43+
private long restrictedContributionsCount; // ๋น„๊ณต๊ฐœ ๋ ˆํฌ ์ปค๋ฐ‹
44+
private ContributionCalendar contributionCalendar;
45+
}
46+
47+
@Getter
48+
@NoArgsConstructor
49+
public static class ContributionCalendar {
50+
private int totalContributions;
51+
private List<Week> weeks;
52+
}
53+
54+
@Getter
55+
@NoArgsConstructor
56+
public static class Week {
57+
private List<ContributionDay> contributionDays;
58+
}
59+
60+
@Getter
61+
@NoArgsConstructor
62+
public static class ContributionDay {
63+
private int contributionCount;
64+
private String date;
4265
}
4366
}

โ€Žsrc/main/java/cmf/commitField/domain/commit/totalCommit/dto/TotalCommitResponseDto.javaโ€Ž

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@
66

77
@Getter
88
@NoArgsConstructor
9-
@AllArgsConstructor
109
public class TotalCommitResponseDto {
1110
private long totalCommitContributions;
1211
private long restrictedContributionsCount;
12+
private int currentStreakDays;
13+
private int maxStreakDays;
14+
15+
public TotalCommitResponseDto(long totalCommitContributions, long restrictedContributionsCount, int currentStreakDays, int maxStreakDays) {
16+
this.totalCommitContributions = totalCommitContributions;
17+
this.restrictedContributionsCount = restrictedContributionsCount;
18+
this.currentStreakDays = currentStreakDays;
19+
this.maxStreakDays = maxStreakDays;
20+
}
21+
22+
public TotalCommitResponseDto(long totalCommitContributions, long restrictedContributionsCount) {
23+
this.totalCommitContributions = totalCommitContributions;
24+
this.restrictedContributionsCount = restrictedContributionsCount;
25+
}
1326
}

0 commit comments

Comments
ย (0)