Skip to content

Commit 9e16d33

Browse files
authored
Merge pull request #112 from Capstone-OpenStep/feature/#111-issue-detail-by-url
Feat: GitHub URL 기반 이슈 상세 조회 API 구현
2 parents 737377f + 15ac19e commit 9e16d33

4 files changed

Lines changed: 89 additions & 1 deletion

File tree

src/main/java/com/chungang/capstone/openstep/domain/Github/dto/GitHubIssueResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public static class Edge {
3131
public static class Repository {
3232
private String name;
3333
private Issues issues;
34+
private IssueNode issue;
3435
private Owner owner;
3536
private String nameWithOwner;
3637
}

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,67 @@ public GitHubIssueResponse searchIssues(String query) {
338338
}
339339

340340

341+
public GitHubIssueResponse.IssueNode fetchIssueByUrl(String url) {
342+
// 예: https://github.com/owner/repo/issues/123
343+
try {
344+
String[] parts = url.split("/");
345+
String owner = parts[3];
346+
String repo = parts[4];
347+
int number = Integer.parseInt(parts[6]);
348+
349+
String query = String.format("""
350+
{
351+
repository(owner: "%s", name: "%s") {
352+
issue(number: %d) {
353+
number
354+
title
355+
body
356+
url
357+
state
358+
createdAt
359+
updatedAt
360+
author {
361+
login
362+
avatarUrl
363+
}
364+
labels(first: 10) {
365+
nodes {
366+
name
367+
}
368+
}
369+
repository {
370+
name
371+
nameWithOwner
372+
owner {
373+
login
374+
avatarUrl
375+
}
376+
}
377+
}
378+
}
379+
}
380+
""", owner, repo, number);
381+
382+
HttpHeaders headers = new HttpHeaders();
383+
headers.setContentType(MediaType.APPLICATION_JSON);
384+
headers.setBearerAuth(githubToken);
385+
386+
HttpEntity<GitHubGraphQLRequest> request = new HttpEntity<>(new GitHubGraphQLRequest(query), headers);
387+
388+
ResponseEntity<GitHubIssueResponse> response = restTemplate.exchange(
389+
GITHUB_GRAPHQL_URL, HttpMethod.POST, request, GitHubIssueResponse.class
390+
);
391+
392+
GitHubIssueResponse.Repository repoRes = response.getBody().getData().getRepository();
393+
if (repoRes != null) return repoRes.getIssue();
394+
else return null;
395+
396+
} catch (Exception e) {
397+
log.error("[GraphQL] 이슈 단건 조회 실패", e);
398+
throw new GithubGraphQLException(ErrorStatus.GITHUB_GRAPHQL_ERROR);
399+
}
400+
}
401+
341402

342403

343404
}

src/main/java/com/chungang/capstone/openstep/domain/Issue/controller/IssueController.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
package com.chungang.capstone.openstep.domain.Issue.controller;
22

3+
import com.chungang.capstone.openstep.domain.Github.dto.GitHubIssueResponse;
4+
import com.chungang.capstone.openstep.domain.Github.service.GitHubGraphQLService;
35
import com.chungang.capstone.openstep.domain.Issue.service.IssueCommandService;
46
import com.chungang.capstone.openstep.domain.Issue.service.IssueQueryService;
57
import com.chungang.capstone.openstep.domain.Member.entity.Member;
8+
import com.chungang.capstone.openstep.domain.OpenAI.service.OpenAIService;
69
import com.chungang.capstone.openstep.domain.Task.entity.Task;
710
import com.chungang.capstone.openstep.domain.common.InterestLanguage;
811
import com.chungang.capstone.openstep.domain.common.UpdatePeriod;
12+
import com.chungang.capstone.openstep.global.apiPayload.code.status.ErrorStatus;
913
import com.chungang.capstone.openstep.global.apiPayload.code.status.SuccessStatus;
1014
import com.chungang.capstone.openstep.global.apiPayload.ApiResponse;
1115
import com.chungang.capstone.openstep.domain.Issue.converter.IssueConverter;
1216
import com.chungang.capstone.openstep.domain.Issue.dto.IssueResponseDTO;
1317
import com.chungang.capstone.openstep.domain.Issue.entity.Issue;
18+
import com.chungang.capstone.openstep.global.apiPayload.exception.handler.IssueHandler;
1419
import com.chungang.capstone.openstep.global.security.util.SecurityUtils;
1520

1621
import io.swagger.v3.oas.annotations.Parameter;
@@ -19,6 +24,7 @@
1924
import jakarta.validation.constraints.Max;
2025
import jakarta.validation.constraints.Min;
2126

27+
import lombok.extern.slf4j.Slf4j;
2228
import org.springframework.data.domain.PageRequest;
2329
import org.springframework.validation.annotation.Validated;
2430
import org.springframework.web.bind.annotation.RequestMapping;
@@ -35,12 +41,15 @@
3541
@RestController
3642
@RequiredArgsConstructor
3743
@Validated
44+
@Slf4j
3845
@RequestMapping("/issues")
3946
@Tag(name = "이슈 API", description = "GitHub 이슈 관련 API입니다.")
4047
public class IssueController {
4148

4249
private final IssueQueryService issueQueryService;
4350
private final IssueCommandService issueCommandService;
51+
private final GitHubGraphQLService gitHubGraphQLService;
52+
private final OpenAIService openAIService;
4453

4554
// 트렌딩 이슈 목록 조회 API
4655
@GetMapping("/trending")
@@ -105,6 +114,23 @@ public ApiResponse<IssueResponseDTO.IssueListDTO> searchIssuesByKeyword(
105114
IssueConverter.toIssueListDTO(issues, bookmarkedIds));
106115
}
107116

117+
@GetMapping("/detail-by-url")
118+
@Operation(summary = "GitHub URL 기반 이슈 상세 조회", description = "검색된 이슈의 GitHub URL로 상세 정보를 조회합니다.")
119+
public ApiResponse<IssueResponseDTO.IssueDetailDTO> getIssueDetailByUrl(@RequestParam String url) {
120+
GitHubIssueResponse.IssueNode node = gitHubGraphQLService.fetchIssueByUrl(url);
121+
if (node == null) throw new IssueHandler(ErrorStatus.ISSUE_NOT_FOUND);
122+
Issue issue = IssueConverter.fromGitHubIssueNode(node);
123+
try {
124+
String raw = openAIService.summarizeIssue(issue.getTitle(), issue.getBody());
125+
issue.setSummary(openAIService.rewriteNaturalKorean(raw));
126+
} catch (Exception e) {
127+
log.warn("[SUMMARY] 요약 실패: {}", url, e);
128+
issue.setSummary("요약을 생성할 수 없습니다.");
129+
}
130+
return ApiResponse.onSuccess(SuccessStatus.ISSUE_GET_DETAIL_OK, IssueConverter.toIssueDetailDTO(issue));
131+
}
132+
133+
108134

109135

110136

src/main/java/com/chungang/capstone/openstep/global/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
5353

5454
// Issue 관련 접근
5555
.requestMatchers("/issues/trending", "/issues/{issue-id}", "/summary/issue/{issue-id}").permitAll()
56-
.requestMatchers("/issues/suggest", "/issues/search/keyword").permitAll()
56+
.requestMatchers("/issues/suggest", "/issues/search/keyword", "/issues/detail-by-url").permitAll()
5757

5858
// Bookmark 관련 접근
5959
.requestMatchers("/bookmark/add/{issue-id}", "/bookmark/delete/{issue-id}", "/bookmark/list/").permitAll()

0 commit comments

Comments
 (0)