Skip to content

Commit 849b234

Browse files
authored
feat(analysis): repository, analysis 조회 관련 메서드 수정
feat(analysis): repository, analysis 조회 관련 메서드 수정
2 parents a115257 + d11ddf6 commit 849b234

File tree

10 files changed

+380
-145
lines changed

10 files changed

+380
-145
lines changed
Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.backend.domain.analysis.controller;
22

3+
import com.backend.domain.analysis.dto.request.AnalysisRequest;
34
import com.backend.domain.analysis.dto.response.AnalysisResultResponseDto;
45
import com.backend.domain.analysis.dto.response.HistoryResponseDto;
5-
import com.backend.domain.analysis.dto.request.AnalysisRequest;
66
import com.backend.domain.analysis.entity.AnalysisResult;
7-
import com.backend.domain.analysis.entity.Score;
87
import com.backend.domain.analysis.service.AnalysisService;
8+
import com.backend.domain.repository.dto.response.RepositoryResponse;
99
import com.backend.domain.repository.entity.Repositories;
1010
import com.backend.domain.repository.service.RepositoryService;
11+
import com.backend.global.exception.BusinessException;
12+
import com.backend.global.exception.ErrorCode;
1113
import com.backend.global.response.ApiResponse;
1214
import lombok.RequiredArgsConstructor;
1315
import org.springframework.http.ResponseEntity;
@@ -16,7 +18,6 @@
1618

1719
import java.util.ArrayList;
1820
import java.util.List;
19-
import java.util.Optional;
2021

2122
@RestController
2223
@RequiredArgsConstructor
@@ -25,54 +26,90 @@ public class AnalysisController {
2526
private final AnalysisService analysisService;
2627
private final RepositoryService repositoryService;
2728

29+
// POST: 분석 요청
2830
@PostMapping
2931
public ResponseEntity<ApiResponse<Void>> analyzeRepository(@RequestBody AnalysisRequest request) {
32+
if (request == null) {
33+
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
34+
}
35+
3036
analysisService.analyze(request.githubUrl());
3137
return ResponseEntity.ok(ApiResponse.success());
3238
}
3339

34-
@GetMapping("/user/repository/{memberId}")
40+
// GET: 사용자의 모든 Repository 목록 조회
41+
@GetMapping("/{memberId}/repositories")
42+
@Transactional(readOnly = true)
43+
public ResponseEntity<List<RepositoryResponse>> getMemberHistory(
44+
@PathVariable Long memberId
45+
){
46+
if (memberId == null) {
47+
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
48+
}
49+
50+
List<RepositoryResponse> repositories = repositoryService
51+
.findRepositoryByMember(memberId)
52+
.stream()
53+
.map(RepositoryResponse::new)
54+
.toList();
55+
56+
return ResponseEntity.ok(repositories);
57+
}
58+
59+
// GET: 특정 Repository의 분석 히스토리 조회, 모든 분석 결과 조회
60+
@GetMapping("/{memberId}/repositories/{repositoriesId}")
3561
@Transactional(readOnly = true)
36-
public ResponseEntity<List<HistoryResponseDto>> getMemberHistory(@PathVariable Long memberId){
37-
List<Repositories> repositories = repositoryService.findRepositoryByMember(memberId);
38-
List<HistoryResponseDto> historyList = new ArrayList<>();
62+
public ResponseEntity<HistoryResponseDto> getAnalysisByRepositoriesId(
63+
@PathVariable("memberId") Long memberId,
64+
@PathVariable("repositoriesId") Long repoId
65+
){
66+
// TODO: 추후 인증/인가 기능 완성 후 소유권 검증 로직 추가
67+
68+
// 1. Repository 정보 조회
69+
Repositories repository = repositoryService.findById(repoId)
70+
.orElseThrow(() -> new BusinessException(ErrorCode.GITHUB_REPO_NOT_FOUND));
3971

40-
for (Repositories repo : repositories) {
41-
Optional<AnalysisResult> optionalAnalysis = analysisService.findByRepositoryId(repo.getId());
72+
RepositoryResponse repositoryResponse = new RepositoryResponse(repository);
4273

43-
if (optionalAnalysis.isPresent()) { // 존재하는지 확인
44-
AnalysisResult analysisResult = optionalAnalysis.get();
45-
Score score = analysisResult.getScore();
74+
// 2. 분석 결과 목록 조회 (최신순 정렬)
75+
List<AnalysisResult> analysisResults =
76+
analysisService.getAnalysisResultList(repoId);
4677

47-
List<String> languages = repositoryService.findLanguagesByRepisotryId(repo.getId())
48-
.stream()
49-
.map(Enum::name) // RepositoryLanguage -> Language enum
50-
.toList();
78+
// 3. 버전 DTO 생성 (최신이 가장 큰 번호)
79+
List<HistoryResponseDto.AnalysisVersionDto> versions = new ArrayList<>();
80+
int versionNumber = analysisResults.size();
5181

52-
HistoryResponseDto dto = new HistoryResponseDto(repo, analysisResult, score, languages);
53-
historyList.add(dto);
54-
}
82+
for (AnalysisResult analysis : analysisResults) {
83+
versions.add(HistoryResponseDto.AnalysisVersionDto.from(analysis, versionNumber));
84+
versionNumber--;
5585
}
5686

57-
// 최신순 정렬
58-
historyList.sort((a, b) -> b.createDate().compareTo(a.createDate()));
87+
// 4. 응답 조합
88+
HistoryResponseDto response = HistoryResponseDto.of(repositoryResponse, versions);
5989

60-
return ResponseEntity.ok(historyList);
90+
return ResponseEntity.ok(response);
6191
}
6292

63-
@GetMapping("repository/{repositoriesId}")
93+
// GET: 특정 분석 결과 상세 조회
94+
@GetMapping("/{memberId}/repositories/{repositoryId}/results/{analysisId}")
6495
@Transactional(readOnly = true)
65-
public ResponseEntity<List<AnalysisResultResponseDto>> getAnalysisByRepositoriesId(@PathVariable("repositoriesId") Long repoId){
66-
List<AnalysisResult> optionalResult = analysisService.getAnalysisResultList(repoId);
67-
List<AnalysisResultResponseDto> resultList = new ArrayList<>();
96+
public ResponseEntity<AnalysisResultResponseDto> getAnalysisDetail(
97+
@PathVariable Long memberId,
98+
@PathVariable Long repositoryId,
99+
@PathVariable Long analysisId
100+
) {
101+
// TODO: 추후 인증/인가 검증
68102

103+
// 분석 결과 조회
104+
AnalysisResult analysisResult = analysisService.getAnalysisById(analysisId);
69105

70-
for(AnalysisResult result : optionalResult){
71-
Score score = result.getScore();
72-
AnalysisResultResponseDto dto = new AnalysisResultResponseDto(result, score);
73-
resultList.add(dto);
106+
if (!analysisResult.getRepositories().getId().equals(repositoryId)) {
107+
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
74108
}
75109

76-
return ResponseEntity.ok(resultList);
110+
AnalysisResultResponseDto response =
111+
new AnalysisResultResponseDto(analysisResult, analysisResult.getScore());
112+
113+
return ResponseEntity.ok(response);
77114
}
78115
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
package com.backend.domain.analysis.dto.request;
22

3-
public record AnalysisRequest(String githubUrl) {}
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.Pattern;
5+
6+
public record AnalysisRequest(
7+
@NotBlank(message = "GitHub URL은 필수입니다")
8+
@Pattern(regexp = "^https://github\\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+/?$",
9+
message = "올바른 GitHub 저장소 URL 형식이 아닙니다")
10+
String githubUrl
11+
) {}

backend/src/main/java/com/backend/domain/analysis/dto/response/AnalysisResultResponseDto.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import com.backend.domain.analysis.entity.AnalysisResult;
44
import com.backend.domain.analysis.entity.Score;
55

6+
/**
7+
* 특정 분석 결과의 상세 정보 응답 DTO
8+
* 분석 점수, 피드백 등을 포함
9+
*/
610
public record AnalysisResultResponseDto(
711
int totalScore,
812
int readmeScore,
Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
package com.backend.domain.analysis.dto.response;
22

33
import com.backend.domain.analysis.entity.AnalysisResult;
4-
import com.backend.domain.analysis.entity.Score;
5-
import com.backend.domain.repository.entity.Repositories;
4+
import com.backend.domain.repository.dto.response.RepositoryResponse;
65

76
import java.time.LocalDateTime;
87
import java.util.List;
98

9+
/**
10+
* Repository 상세 정보 + 분석 버전 목록 응답 DTO
11+
* 특정 Repository의 모든 분석 버전을 조회할 때 사용
12+
*/
1013
public record HistoryResponseDto(
11-
String repositoryName,
12-
LocalDateTime createDate,
13-
List<String> languages,
14-
int totalScore,
15-
boolean publicStatus
14+
RepositoryResponse repository, // Repository 기본 정보
15+
List<AnalysisVersionDto> analysisVersions
1616
) {
17-
public HistoryResponseDto(Repositories repositories, AnalysisResult analysisResult, Score score, List<String> languages){
18-
this(
19-
repositories.getName(),
20-
analysisResult.getCreateDate(),
21-
languages,
22-
score.getTotalScore(),
23-
repositories.isPublicRepository()
24-
);
25-
}
17+
public static HistoryResponseDto of(
18+
RepositoryResponse repository,
19+
List<AnalysisVersionDto> versions
20+
) {
21+
return new HistoryResponseDto(repository, versions);
22+
}
23+
24+
public record AnalysisVersionDto(
25+
Long analysisId,
26+
LocalDateTime analysisDate,
27+
Integer totalScore,
28+
String versionLabel
29+
) {
30+
public static AnalysisVersionDto from(
31+
AnalysisResult analysis,
32+
int versionNumber
33+
) {
34+
return new AnalysisVersionDto(
35+
analysis.getId(),
36+
analysis.getCreateDate(),
37+
analysis.getScore().getTotalScore(),
38+
String.format("v%d (%s)",
39+
versionNumber,
40+
analysis.getCreateDate().toLocalDate())
41+
);
42+
}
43+
}
2644
}

backend/src/main/java/com/backend/domain/analysis/service/AnalysisService.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.backend.domain.analysis.service;
22

3-
import com.backend.domain.repository.dto.response.RepositoryData;
43
import com.backend.domain.analysis.entity.AnalysisResult;
54
import com.backend.domain.analysis.repository.AnalysisResultRepository;
5+
import com.backend.domain.repository.dto.response.RepositoryData;
66
import com.backend.domain.repository.service.RepositoryService;
77
import com.backend.global.exception.BusinessException;
88
import com.backend.global.exception.ErrorCode;
@@ -12,7 +12,6 @@
1212
import org.springframework.transaction.annotation.Transactional;
1313

1414
import java.util.List;
15-
import java.util.Optional;
1615

1716
@Slf4j
1817
@Service
@@ -21,26 +20,44 @@ public class AnalysisService {
2120
private final RepositoryService repositoryService;
2221
private final AnalysisResultRepository analysisResultRepository;
2322

23+
/* Analysis 분석 프로세스 오케스트레이션 담당
24+
* 1. GitHub URL 파싱 및 검증
25+
* 2. Repository 도메인을 통한 데이터 수집
26+
* 3. Evaluation 도메인을 통한 AI 평가
27+
* 4. 분석 결과 저장
28+
* */
2429
@Transactional
2530
public void analyze(String githubUrl) {
2631
String[] repoInfo = parseGitHubUrl(githubUrl);
2732
String owner = repoInfo[0];
2833
String repo = repoInfo[1];
2934

3035
// Repository 데이터 수집
31-
RepositoryData repositoryData = repositoryService.fetchAndSaveRepository(owner, repo);
36+
RepositoryData repositoryData;
37+
38+
try {
39+
repositoryData = repositoryService.fetchAndSaveRepository(owner, repo);
40+
log.info("🫠 Repository Data 수집 완료: {}", repositoryData);
41+
} catch (BusinessException e) {
42+
log.error("Repository 데이터 수집 실패: {}/{}", owner, repo, e);
43+
throw handleRepositoryFetchError(e, owner, repo);
44+
}
3245

33-
log.info("🫠 ResponseData: {}", repositoryData);
3446
// TODO: AI 평가
3547
// EvaluationResult evaluation = evaluationService.evaluate(repositoryData);
3648

3749
// TODO: AI 평가 저장
3850
}
3951

52+
// GitHub URL 파싱하여 owner와 repo 이름 추출
4053
private String[] parseGitHubUrl(String githubUrl) {
4154
log.info("🚩 분석 요청 url: {}", githubUrl);
4255

43-
if (githubUrl == null || !githubUrl.startsWith("https://github.com/")) {
56+
if (githubUrl == null) {
57+
throw new BusinessException(ErrorCode.INVALID_GITHUB_URL);
58+
}
59+
60+
if (!githubUrl.startsWith("https://github.com/")) {
4461
throw new BusinessException(ErrorCode.INVALID_GITHUB_URL);
4562
}
4663

@@ -58,13 +75,25 @@ private String[] parseGitHubUrl(String githubUrl) {
5875
return new String[]{parts[0].trim(), parts[1].trim()};
5976
}
6077

61-
// AnalysisRresult에서 repository id로 분석 결과 찾기
62-
public Optional<AnalysisResult> findByRepositoryId(Long RepositoryId) {
63-
return analysisResultRepository.findByRepositoriesId(RepositoryId);
78+
// Repository 데이터 수집 중 발생한 예외 처리
79+
private BusinessException handleRepositoryFetchError(BusinessException e, String owner, String repo) {
80+
return switch (e.getErrorCode()) {
81+
case GITHUB_REPO_NOT_FOUND ->
82+
new BusinessException(ErrorCode.GITHUB_REPO_NOT_FOUND);
83+
case GITHUB_RATE_LIMIT_EXCEEDED ->
84+
new BusinessException(ErrorCode.GITHUB_RATE_LIMIT_EXCEEDED);
85+
default -> e;
86+
};
87+
}
88+
89+
// 특정 Repository의 모든 분석 결과 조회 (최신순)
90+
public List<AnalysisResult> getAnalysisResultList(Long repositoryId){
91+
return analysisResultRepository.findAnalysisResultByRepositoriesId(repositoryId);
6492
}
6593

66-
// AnalysisResult를 list로 반환
67-
public List<AnalysisResult> getAnalysisResultList(Long RepositoryId){
68-
return analysisResultRepository.findAnalysisResultByRepositoriesId(RepositoryId);
94+
// 분석 결과 ID로 단건 조회
95+
public AnalysisResult getAnalysisById(Long analysisId) {
96+
return analysisResultRepository.findById(analysisId)
97+
.orElseThrow(() -> new BusinessException(ErrorCode.ANALYSIS_NOT_FOUND));
6998
}
7099
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package com.backend.domain.repository.controller;
22

3-
import com.backend.domain.repository.entity.Repositories;
43
import com.backend.domain.repository.service.RepositoryService;
54
import lombok.RequiredArgsConstructor;
65
import org.springframework.web.bind.annotation.*;
76

87
@RestController
98
@RequiredArgsConstructor
109
@RequestMapping("/api/repository")
11-
public class RepositoriseController {
10+
public class RepositoryController {
1211
private final RepositoryService repositoryService;
1312

1413
// 리포지토리 삭제

backend/src/main/java/com/backend/domain/repository/dto/response/RepositoryResponse.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
import java.util.List;
66
import java.util.stream.Collectors;
77

8+
/**
9+
* Repository 기본 정보 응답 DTO
10+
* 사용자의 Repository 목록 조회 시 사용
11+
*/
812
public record RepositoryResponse(
913
Long id,
1014
String name,

0 commit comments

Comments
 (0)