Skip to content

Commit f112367

Browse files
authored
Merge pull request #122 from Capstone-OpenStep/feature/#121-return-achievement
FEAT: 업적 반환 로직 추가 및 PR 인코딩 방식 변경
2 parents 03ce8d0 + ddbf227 commit f112367

File tree

13 files changed

+334
-46
lines changed

13 files changed

+334
-46
lines changed

src/main/java/com/chungang/capstone/openstep/domain/Github/service/GithubRestServiceImpl.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.chungang.capstone.openstep.domain.Github.service;
22

3+
import java.net.URI;
4+
import java.net.URLEncoder;
5+
import java.nio.charset.StandardCharsets;
36
import java.util.List;
47

58
import org.springframework.http.*;
@@ -92,9 +95,17 @@ public boolean doesForkExist(String user, String repo, String accessToken) {
9295
// }
9396

9497
@Override
95-
public PullRequestResponse.PullRequestRes findPullRequest(String owner, String repo, String headRefName, String githubId,String accessToken) {
96-
String url = "https://api.github.com/repos/" + owner + "/" + repo + "/pulls?state=all&head=" + headRefName;
98+
public PullRequestResponse.PullRequestRes findPullRequest(String owner, String repo, String branchName, String githubId,String accessToken) {
99+
// String url = "https://api.github.com/repos/" + owner + "/" + repo + "/pulls?state=all&head=" + branchName;
97100

101+
String encodedBranch = URLEncoder.encode(branchName, StandardCharsets.UTF_8);
102+
String urlStr = String.format(
103+
"https://api.github.com/repos/%s/%s/pulls?state=all&head=%s:%s",
104+
owner, repo, githubId, encodedBranch
105+
);
106+
107+
URI uri = URI.create(urlStr);
108+
log.info("Direct URI creation: {}", uri.toString());
98109
try {
99110
RestTemplate restTemplate = new RestTemplate();
100111
HttpHeaders headers = new HttpHeaders();
@@ -103,17 +114,22 @@ public PullRequestResponse.PullRequestRes findPullRequest(String owner, String r
103114
headers.setAccept(List.of(MediaType.APPLICATION_JSON));
104115
HttpEntity<Void> entity = new HttpEntity<>(headers);
105116

117+
log.info("Calling GitHub API: {}", uri);
118+
106119
ResponseEntity<PullRequestResponse.PullRequestRes[]> response = restTemplate.exchange(
107-
url,
120+
uri,
108121
HttpMethod.GET,
109122
entity,
110123
PullRequestResponse.PullRequestRes[].class
111124
);
112125

113126
PullRequestResponse.PullRequestRes[] pullRequests = response.getBody();
114127
if (pullRequests != null && pullRequests.length > 0) {
128+
log.info("Found {} PR(s) for branch: {}", pullRequests.length, branchName);
115129
return pullRequests[0]; // PR은 보통 1개면 충분하므로 첫 번째 것 반환
116-
}
130+
} else {
131+
log.info("No PR found for branch: {}", branchName);
132+
}
117133

118134
} catch (Exception e) {
119135
log.error("GitHub PR 조회 중 예외 발생", e);
@@ -156,11 +172,19 @@ public boolean hasReview(String owner, String repo, int prNumber,String accessTo
156172
return false;// 리뷰가 없거나 오류 발생 시 false 반환
157173
}
158174

159-
private String buildPullRequestUrl(String originalOwner, String originalRepo, String myGithubId, String branchName) {
160-
return String.format(
161-
"https://api.github.com/repos/%s/%s/pulls?state=all&head=%s:%s&sort=updated&direction=desc",
162-
originalOwner, originalRepo, myGithubId, branchName
163-
);
175+
private String buildPullRequestUrl(String owner, String repo, String githubId, String branchName) {
176+
try {
177+
//브랜치명 URL 인코딩
178+
String encodedBranch = URLEncoder.encode(branchName, StandardCharsets.UTF_8);
179+
180+
return String.format(
181+
"https://api.github.com/repos/%s/%s/pulls?state=all&head=%s:%s",
182+
owner, repo, githubId, encodedBranch
183+
);
184+
} catch (Exception e) {
185+
log.error("Error encoding branch name: {}", branchName, e);
186+
return null;
187+
}
164188
}
165189

166190
}

src/main/java/com/chungang/capstone/openstep/domain/Member/controller/MemberController.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import com.chungang.capstone.openstep.domain.Member.dto.MemberResponseDTO;
1717
import com.chungang.capstone.openstep.domain.Member.service.MemberCommandService;
1818
import com.chungang.capstone.openstep.domain.Member.service.MemberQueryService;
19+
import com.chungang.capstone.openstep.domain.achievement.dto.AchievementDTO;
20+
import com.chungang.capstone.openstep.domain.achievement.entity.MemberAchievement;
21+
import com.chungang.capstone.openstep.domain.achievement.service.AchievementService;
1922
import com.chungang.capstone.openstep.global.apiPayload.ApiResponse;
2023
import com.chungang.capstone.openstep.global.apiPayload.code.status.SuccessStatus;
2124
import com.chungang.capstone.openstep.global.security.util.SecurityUtils;
@@ -36,6 +39,7 @@ public class MemberController {
3639
private final MemberQueryService memberQueryService;
3740
private final MemberCommandService memberCommandService;
3841
private final GitHubGraphQLService gitHubGraphQLService;
42+
private final AchievementService achievementService;
3943

4044
@PostMapping("/sign_up")
4145
@Operation(summary = "회원가입", description = "회원가입을 진행합니다.")
@@ -150,6 +154,13 @@ public ApiResponse<MemberResponseDTO.GitHubProfileDTO> getGitHubProfileRealtime(
150154
return ApiResponse.onSuccess(SuccessStatus.MEMBER_GET_GITHUB_PROFILE_OK, dto);
151155
}
152156

153-
157+
//사용자의 모든 업적 조회
158+
@GetMapping("/achievements")
159+
@Operation(summary = "사용자의 모든 업적 조회", description = "사용자가 달성한 모든 업적을 조회합니다.")
160+
public ApiResponse<List<AchievementDTO>> getMemberAchievements() {
161+
Long memberId= SecurityUtils.getCurrentMemberId();
162+
List<MemberAchievement> achievements = achievementService.getMemberUnlockedAchievements(memberId);
163+
return ApiResponse.onSuccess(SuccessStatus.ACHIEVEMENT_GET_ALL_OK,achievements.stream().map(AchievementDTO::from).toList());
164+
}
154165

155166
}

src/main/java/com/chungang/capstone/openstep/domain/Member/entity/Member.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ public class Member extends BaseEntity {
7474
private List<Rank> ranks = new ArrayList<>();
7575

7676
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
77-
private List<MemberAchievement> achievements = new ArrayList<>();
77+
private List<MemberAchievement> memberAchievements = new ArrayList<>();
7878

7979
public List<MemberAchievement> getUnLockedAchievements() {
80-
return achievements.stream()
80+
return memberAchievements.stream()
8181
.filter(MemberAchievement::isUnlocked)
8282
.toList();
8383
}
8484

8585
public Optional<MemberAchievement> getAchievement(AchievementType type) {
86-
return achievements.stream()
86+
return memberAchievements.stream()
8787
.filter(achievement -> achievement.getType() == type)
8888
.findFirst();
8989
}
@@ -95,7 +95,7 @@ public boolean hasUnlockedAchievement(AchievementType type) {
9595
}
9696

9797
public int getUnlockedAchievementCount() {
98-
return (int)achievements.stream()
98+
return (int)memberAchievements.stream()
9999
.filter(MemberAchievement::isUnlocked)
100100
.count();
101101
}

src/main/java/com/chungang/capstone/openstep/domain/Task/converter/TaskConverter.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
package com.chungang.capstone.openstep.domain.Task.converter;
22

3+
import java.util.ArrayList;
34
import java.util.List;
45

56
import com.chungang.capstone.openstep.domain.Task.dto.TaskResponseDTO;
67
import com.chungang.capstone.openstep.domain.Task.entity.Task;
78

89
public class TaskConverter {
910
public static TaskResponseDTO.TaskDetail toTaskDetail(Task task){
10-
return TaskResponseDTO.TaskDetail.builder().
11-
taskId(task.getTaskId()).
12-
status(task.getStatus()).
13-
forkedUrl(task.getForkedUrl()).
14-
createdAt(task.getCreatedAt().toString()).
15-
branchName(task.getBranchName()).
16-
updatedAt(task.getUpdatedAt().toString()).
17-
issueId(task.getIssue().getIssueId()).
18-
issueUrl(task.getIssue().getGithubUrl()).
19-
build();
11+
return TaskResponseDTO.TaskDetail.of(
12+
task.getTaskId(),
13+
task.getIssue().getTitle(),
14+
task.getForkedUrl(),
15+
task.getStatus(),
16+
task.getBranchName(),
17+
task.getCreatedAt().toString(),
18+
task.getUpdatedAt().toString(),
19+
task.getIssue().getIssueId(),
20+
task.getIssue().getGithubUrl()
21+
);
2022
}
2123
public static TaskResponseDTO.TaskBranchName toTaskBranchName(Task task){
2224
return TaskResponseDTO.TaskBranchName.builder().

src/main/java/com/chungang/capstone/openstep/domain/Task/dto/TaskResponseDTO.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.chungang.capstone.openstep.domain.Task.dto;
22

33
import java.time.LocalDateTime;
4+
import java.util.ArrayList;
45
import java.util.List;
56

67
import com.chungang.capstone.openstep.domain.Task.entity.TaskStatus;
78

89
import lombok.Builder;
10+
import lombok.Getter;
911

1012
public class TaskResponseDTO {
1113

@@ -18,7 +20,7 @@ public record Status(
1820
) {
1921
}
2022

21-
@Builder
23+
@Builder(toBuilder = true)
2224
public record TaskDetail(
2325
Long taskId,
2426
String title,
@@ -28,8 +30,51 @@ public record TaskDetail(
2830
String createdAt,
2931
String updatedAt,
3032
Long issueId,
31-
String issueUrl
33+
String issueUrl,
34+
List<AchievementDTO> achievements
3235
) {
36+
public static TaskDetail of(
37+
Long taskId,
38+
String title,
39+
String forkedUrl,
40+
TaskStatus status,
41+
String branchName,
42+
String createdAt,
43+
String updatedAt,
44+
Long issueId,
45+
String issueUrl
46+
) {
47+
return new TaskDetail(
48+
taskId, title, forkedUrl, status, branchName,
49+
createdAt, updatedAt, issueId, issueUrl,
50+
new ArrayList<>()
51+
);
52+
}
53+
54+
// 업적 정보와 함께 새로운 인스턴스 생성하는 메소드
55+
public TaskDetail withAchievements(List<AchievementDTO> achievements) {
56+
return new TaskDetail(
57+
taskId, title, forkedUrl, status, branchName,
58+
createdAt, updatedAt, issueId, issueUrl,
59+
achievements != null ? achievements : new ArrayList<>()
60+
);
61+
}
62+
}
63+
// 업적 정보 DTO
64+
@Getter
65+
@Builder(toBuilder = true)
66+
public static class AchievementDTO {
67+
private Long id;
68+
private String type;
69+
private String title;
70+
private String description;
71+
private boolean unlocked;
72+
private LocalDateTime unlockedAt;
73+
private int currentProgress;
74+
private int targetCount;
75+
76+
@Builder.Default
77+
private final boolean isNewlyUnlocked = false;
3378
}
3479

3580
@Builder

src/main/java/com/chungang/capstone/openstep/domain/Task/service/TaskQueryService.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import com.chungang.capstone.openstep.domain.Task.entity.Task;
2121
import com.chungang.capstone.openstep.domain.Task.entity.TaskStatus;
2222
import com.chungang.capstone.openstep.domain.Task.repository.TaskRepository;
23+
import com.chungang.capstone.openstep.domain.achievement.entity.MemberAchievement;
2324
import com.chungang.capstone.openstep.domain.achievement.event.PrCreatedEvent;
2425
import com.chungang.capstone.openstep.domain.achievement.event.TaskActivityEvent;
2526
import com.chungang.capstone.openstep.domain.achievement.event.TaskCompletedEvent;
27+
import com.chungang.capstone.openstep.domain.achievement.service.AchievementService;
2628
import com.chungang.capstone.openstep.global.apiPayload.code.status.ErrorStatus;
2729
import com.chungang.capstone.openstep.global.apiPayload.exception.TaskException;
2830

@@ -38,19 +40,30 @@ public class TaskQueryService {
3840
private final RankCommandService rankCommandService;
3941
private final TaskXpLogRepository taskXpLogRepository;
4042
private final GitHubStatusResolverService githubStatusResolver;
43+
private final AchievementService achievementService;
4144

4245
public TaskResponseDTO.TaskDetail getTaskDetailById(Long taskId, Member member) {
4346
Task task = taskRepository.findById(taskId).orElseThrow(() ->
4447
new TaskException(ErrorStatus.TASK_NOT_FOUND));
48+
49+
TaskStatus oldStatus = task.getStatus();
4550
TaskStatus resolvedStatus = githubStatusResolver.resolveStatus(task, member);
4651

4752
// DB 캐시 상태가 다르면 update
48-
if (task.getStatus() != resolvedStatus) {
53+
if (oldStatus != resolvedStatus) {
4954
task.updateStatus(resolvedStatus);
5055
taskRepository.save(task);
5156
}
5257

53-
return TaskConverter.toTaskDetail(task);
58+
TaskResponseDTO.TaskDetail taskDetail = TaskConverter.toTaskDetail(task);
59+
60+
// 이 Task와 관련된 업적 정보 조회
61+
List<TaskResponseDTO.AchievementDTO> relatedAchievements = achievementService.getRelatedAchievements(
62+
member.getMemberId(),
63+
taskId
64+
);
65+
66+
return taskDetail.withAchievements(relatedAchievements);
5467
}
5568

5669
public TaskResponseDTO.TaskBranchName getBranchNameByTask(Long taskId, Member member) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.chungang.capstone.openstep.domain.achievement.dto;
2+
3+
import java.time.LocalDateTime;
4+
5+
import com.chungang.capstone.openstep.domain.achievement.entity.MemberAchievement;
6+
import com.chungang.capstone.openstep.domain.achievement.enums.AchievementType;
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
9+
import lombok.Builder;
10+
import lombok.Getter;
11+
12+
@Getter
13+
@Builder
14+
@JsonInclude(JsonInclude.Include.NON_NULL)
15+
public class AchievementDTO {
16+
private Long id;
17+
private AchievementType type;
18+
private String title;
19+
private String description;
20+
private int currentProgress;
21+
private int targetCount;
22+
private boolean unlocked;
23+
private LocalDateTime unlockedAt;
24+
25+
// 트리거 Task 정보 추가
26+
private Long triggerTaskId;
27+
private String triggerTaskTitle;
28+
private String triggerRepoName;
29+
private String triggerTaskUrl; // GitHub Issue URL
30+
31+
public static AchievementDTO from(MemberAchievement memberAchievement) {
32+
return AchievementDTO.builder()
33+
.id(memberAchievement.getId())
34+
.type(memberAchievement.getType())
35+
.title(memberAchievement.getType().getTitle())
36+
.description(memberAchievement.getType().getDescription())
37+
.currentProgress(memberAchievement.getCurrentProgress())
38+
.targetCount(memberAchievement.getType().getTargetCount())
39+
.unlocked(memberAchievement.isUnlocked())
40+
.unlockedAt(memberAchievement.getUnlockedAt())
41+
42+
// 트리거 Task 정보
43+
.triggerTaskId(memberAchievement.getTriggerTask() != null ?
44+
memberAchievement.getTriggerTask().getTaskId() : null)
45+
.triggerTaskTitle(memberAchievement.getTriggerTaskTitle())
46+
.triggerRepoName(memberAchievement.getTriggerRepoName())
47+
.triggerTaskUrl(memberAchievement.getTriggerTask() != null ?
48+
memberAchievement.getTriggerTask().getIssue().getGithubUrl() : null)
49+
.build();
50+
}
51+
52+
53+
}

0 commit comments

Comments
 (0)