Skip to content

Commit d9a557d

Browse files
authored
feat(analysis[fe], user[be]): [fe] 분석 진행 화면, [be] sse 로직 추가, jwt 토큰에 userId 추가
feat(analysis[fe], user[be]): [fe] 분석 진행 화면, [be] sse 로직 추가, jwt 토큰에 userId 추가
2 parents f3eda67 + 07a8482 commit d9a557d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2932
-331
lines changed

backend/src/main/java/com/backend/domain/analysis/controller/AnalysisController.java

Lines changed: 90 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
import com.backend.domain.analysis.dto.request.AnalysisRequest;
44
import com.backend.domain.analysis.dto.response.AnalysisResultResponseDto;
5+
import com.backend.domain.analysis.dto.response.AnalysisStartResponse;
56
import com.backend.domain.analysis.dto.response.HistoryResponseDto;
67
import com.backend.domain.analysis.entity.AnalysisResult;
8+
import com.backend.domain.analysis.service.AnalysisProgressService;
79
import com.backend.domain.analysis.service.AnalysisService;
810
import com.backend.domain.repository.dto.response.RepositoryResponse;
911
import com.backend.domain.repository.entity.Repositories;
1012
import com.backend.domain.repository.service.RepositoryService;
13+
import com.backend.domain.user.util.JwtUtil;
1114
import com.backend.global.exception.BusinessException;
1215
import com.backend.global.exception.ErrorCode;
1316
import com.backend.global.response.ApiResponse;
17+
import jakarta.servlet.http.HttpServletRequest;
1418
import lombok.RequiredArgsConstructor;
1519
import org.springframework.http.ResponseEntity;
1620
import org.springframework.transaction.annotation.Transactional;
1721
import org.springframework.web.bind.annotation.*;
22+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
1823

1924
import java.util.ArrayList;
2025
import java.util.List;
@@ -25,45 +30,59 @@
2530
public class AnalysisController {
2631
private final AnalysisService analysisService;
2732
private final RepositoryService repositoryService;
33+
private final AnalysisProgressService analysisProgressService;
34+
private final JwtUtil jwtUtil;
2835

2936
// POST: 분석 요청
3037
@PostMapping
31-
public ResponseEntity<ApiResponse<Void>> analyzeRepository(@RequestBody AnalysisRequest request) {
38+
public ResponseEntity<ApiResponse<AnalysisStartResponse>> analyzeRepository(
39+
@RequestBody AnalysisRequest request,
40+
HttpServletRequest httpRequest
41+
) {
3242
if (request == null) {
3343
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
3444
}
45+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
3546

36-
analysisService.analyze(request.githubUrl());
37-
return ResponseEntity.ok(ApiResponse.success());
47+
Long repositoryId = analysisService.analyze(request.githubUrl(), jwtUserId);
48+
AnalysisStartResponse response = new AnalysisStartResponse(repositoryId);
49+
50+
return ResponseEntity.ok(ApiResponse.success(response));
3851
}
3952

4053
// GET: 사용자의 모든 Repository 목록 조회
41-
@GetMapping("/{memberId}/repositories")
54+
@GetMapping("/{userId}/repositories")
4255
@Transactional(readOnly = true)
43-
public ResponseEntity<List<RepositoryResponse>> getMemberHistory(
44-
@PathVariable Long memberId
56+
public ResponseEntity<ApiResponse<List<RepositoryResponse>>> getMemberHistory(
57+
@PathVariable Long userId,
58+
HttpServletRequest httpRequest
4559
){
46-
if (memberId == null) {
47-
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
60+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
61+
if (!jwtUserId.equals(userId)) {
62+
throw new BusinessException(ErrorCode.FORBIDDEN);
4863
}
4964

5065
List<RepositoryResponse> repositories = repositoryService
51-
.findRepositoryByMember(memberId)
66+
.findRepositoryByUser(userId)
5267
.stream()
5368
.map(RepositoryResponse::new)
5469
.toList();
5570

56-
return ResponseEntity.ok(repositories);
71+
return ResponseEntity.ok(ApiResponse.success(repositories));
5772
}
5873

5974
// GET: 특정 Repository의 분석 히스토리 조회, 모든 분석 결과 조회
60-
@GetMapping("/{memberId}/repositories/{repositoriesId}")
75+
@GetMapping("/{userId}/repositories/{repositoriesId}")
6176
@Transactional(readOnly = true)
62-
public ResponseEntity<HistoryResponseDto> getAnalysisByRepositoriesId(
63-
@PathVariable("memberId") Long memberId,
64-
@PathVariable("repositoriesId") Long repoId
77+
public ResponseEntity<ApiResponse<HistoryResponseDto>> getAnalysisByRepositoriesId(
78+
@PathVariable("userId") Long userId,
79+
@PathVariable("repositoriesId") Long repoId,
80+
HttpServletRequest httpRequest
6581
){
66-
// TODO: 추후 인증/인가 기능 완성 후 소유권 검증 로직 추가
82+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
83+
if (!jwtUserId.equals(userId)) {
84+
throw new BusinessException(ErrorCode.FORBIDDEN);
85+
}
6786

6887
// 1. Repository 정보 조회
6988
Repositories repository = repositoryService.findById(repoId)
@@ -87,18 +106,22 @@ public ResponseEntity<HistoryResponseDto> getAnalysisByRepositoriesId(
87106
// 4. 응답 조합
88107
HistoryResponseDto response = HistoryResponseDto.of(repositoryResponse, versions);
89108

90-
return ResponseEntity.ok(response);
109+
return ResponseEntity.ok(ApiResponse.success(response));
91110
}
92111

93112
// GET: 특정 분석 결과 상세 조회
94-
@GetMapping("/{memberId}/repositories/{repositoryId}/results/{analysisId}")
113+
@GetMapping("/{userId}/repositories/{repositoryId}/results/{analysisId}")
95114
@Transactional(readOnly = true)
96-
public ResponseEntity<AnalysisResultResponseDto> getAnalysisDetail(
97-
@PathVariable Long memberId,
115+
public ResponseEntity<ApiResponse<AnalysisResultResponseDto>> getAnalysisDetail(
116+
@PathVariable Long userId,
98117
@PathVariable Long repositoryId,
99-
@PathVariable Long analysisId
118+
@PathVariable Long analysisId,
119+
HttpServletRequest httpRequest
100120
) {
101-
// TODO: 추후 인증/인가 검증
121+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
122+
if (!jwtUserId.equals(userId)) {
123+
throw new BusinessException(ErrorCode.FORBIDDEN);
124+
}
102125

103126
// 분석 결과 조회
104127
AnalysisResult analysisResult = analysisService.getAnalysisById(analysisId);
@@ -110,33 +133,64 @@ public ResponseEntity<AnalysisResultResponseDto> getAnalysisDetail(
110133
AnalysisResultResponseDto response =
111134
new AnalysisResultResponseDto(analysisResult, analysisResult.getScore());
112135

113-
return ResponseEntity.ok(response);
136+
return ResponseEntity.ok(ApiResponse.success(response));
114137
}
115138

116139
// Repository 삭제
117-
@DeleteMapping("/{memberId}/repositories/{repositoriesId}")
118-
public void deleteRepository(@PathVariable("repositoriesId") Long repositoriesId){
119-
analysisService.delete(repositoriesId);
140+
@DeleteMapping("/{userId}/repositories/{repositoriesId}")
141+
public ResponseEntity<ApiResponse<Void>> deleteRepository(
142+
@PathVariable("repositoriesId") Long repositoriesId,
143+
@PathVariable Long userId,
144+
HttpServletRequest httpRequest){
145+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
146+
if (!jwtUserId.equals(userId)) {
147+
throw new BusinessException(ErrorCode.FORBIDDEN);
148+
}
149+
150+
analysisService.delete(repositoriesId, userId);
151+
return ResponseEntity.ok(ApiResponse.success());
120152
}
121153

122154
// 특정 AnalysisResult 삭제
123-
@DeleteMapping("/{memberId}/repositories/{repositoryId}/results/{analysisId}")
155+
@DeleteMapping("/{userId}/repositories/{repositoryId}/results/{analysisId}")
124156
public ResponseEntity<ApiResponse<Void>> deleteAnalysisResult(
125-
@PathVariable Long memberId,
157+
@PathVariable Long userId,
126158
@PathVariable Long repositoryId,
127-
@PathVariable Long analysisId
128-
) {
129-
analysisService.deleteAnalysisResult(analysisId, memberId);
159+
@PathVariable Long analysisId,
160+
HttpServletRequest httpRequest){
161+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
162+
if (!jwtUserId.equals(userId)) {
163+
throw new BusinessException(ErrorCode.FORBIDDEN);
164+
}
165+
166+
analysisService.deleteAnalysisResult(analysisId, userId);
130167
return ResponseEntity.ok(ApiResponse.success());
131168
}
132169

133170
// 분석 결과 공개 여부 변경
134-
@PutMapping("/{memberId}/repositories/{repositoryId}/public")
135-
public ResponseEntity updatePublicStatus(
136-
@PathVariable Long memberId,
137-
@PathVariable Long repositoryId
138-
) {
139-
analysisService.updatePublicStatus(repositoryId, memberId);
171+
@PutMapping("/{userId}/repositories/{repositoryId}/public")
172+
public ResponseEntity<ApiResponse<Void>> updatePublicStatus(
173+
@PathVariable Long userId,
174+
@PathVariable Long repositoryId,
175+
HttpServletRequest httpRequest){
176+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
177+
if (!jwtUserId.equals(userId)) {
178+
throw new BusinessException(ErrorCode.FORBIDDEN);
179+
}
180+
181+
analysisService.updatePublicStatus(repositoryId, userId);
140182
return ResponseEntity.ok(ApiResponse.success());
141183
}
184+
185+
// 분석 현황 Sse
186+
@GetMapping("/stream/{userId}")
187+
public SseEmitter stream(@PathVariable Long userId,
188+
HttpServletRequest httpRequest){
189+
Long jwtUserId = jwtUtil.getUserId(httpRequest);
190+
if (!jwtUserId.equals(userId)) {
191+
throw new BusinessException(ErrorCode.FORBIDDEN);
192+
}
193+
194+
return analysisProgressService.connect(userId);
195+
}
142196
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public record AnalysisResultResponseDto(
1313
int testScore,
1414
int commitScore,
1515
int cicdScore,
16-
String summery,
16+
String summary,
1717
String strengths,
1818
String improvements
1919
) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.backend.domain.analysis.dto.response;
2+
3+
public record AnalysisStartResponse(
4+
Long repositoryId
5+
) {}
6+

backend/src/main/java/com/backend/domain/analysis/entity/AnalysisResult.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package com.backend.domain.analysis.entity;
22

3-
import com.backend.domain.community.entity.Comment;
43
import com.backend.domain.repository.entity.Repositories;
54
import jakarta.persistence.*;
65
import lombok.*;
76

87
import java.time.LocalDateTime;
9-
import java.util.ArrayList;
10-
import java.util.List;
118

129
@Entity
1310
@Table (name = "analysis_result")
@@ -24,13 +21,13 @@ public class AnalysisResult {
2421
@JoinColumn(name = "repository_id", nullable = false)
2522
private Repositories repositories;
2623

27-
@Column(nullable = false)
24+
@Column(nullable = false, columnDefinition = "TEXT")
2825
private String summary;
2926

30-
@Column(nullable = false)
27+
@Column(nullable = false, columnDefinition = "TEXT")
3128
private String strengths;
3229

33-
@Column(nullable = false)
30+
@Column(nullable = false, columnDefinition = "TEXT")
3431
private String improvements;
3532

3633
@Column(nullable = false, name = "createData")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.backend.domain.analysis.service;
2+
3+
import org.springframework.stereotype.Service;
4+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
5+
6+
import java.io.IOException;
7+
import java.util.Map;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
@Service
11+
public class AnalysisProgressService {
12+
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
13+
14+
public SseEmitter connect(Long userId) {
15+
SseEmitter emitter = new SseEmitter(10 * 60 * 1000L);
16+
emitters.put(userId, emitter);
17+
18+
emitter.onCompletion(() -> emitters.remove(userId));
19+
emitter.onTimeout(() -> emitters.remove(userId));
20+
emitter.onError((e) -> emitters.remove(userId));
21+
22+
sendEvent(userId, "connected", "SSE 연결 완료");
23+
return emitter;
24+
}
25+
26+
public void sendEvent(Long userId, String eventName, String data) {
27+
SseEmitter emitter = emitters.get(userId);
28+
if(emitter == null) return;
29+
30+
try {
31+
emitter.send(SseEmitter.event().name(eventName).data(data));
32+
} catch(IOException e) {
33+
emitters.remove(userId);
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)