diff --git a/src/main/java/com/teamEWSN/gitdeun/common/fastapi/FastApiClient.java b/src/main/java/com/teamEWSN/gitdeun/common/fastapi/FastApiClient.java index 558957d..2b36489 100644 --- a/src/main/java/com/teamEWSN/gitdeun/common/fastapi/FastApiClient.java +++ b/src/main/java/com/teamEWSN/gitdeun/common/fastapi/FastApiClient.java @@ -51,16 +51,11 @@ public AnalysisResultDto analyzeResult(String repoUrl, String prompt, String aut AnalyzeResponse analyzeResult = analyzeAI(repoUrl, prompt, authorizationHeader); log.info("AI 분석 완료 - 디렉터리: {}", analyzeResult.getDirs_analyzed()); - // Step 3: 생성된 그래프 데이터 조회 - MindmapGraphDto graphData = getGraph(mapId, authorizationHeader); - log.info("그래프 조회 완료 - 노드: {}, 엣지: {}", - graphData.getNodes().size(), graphData.getEdges().size()); - - // Step 4: 저장소 정보 조회 (TODO: FastAPI에 엔드포인트 추가 필요) + // Step 3: 저장소 정보 조회 RepoInfoResponse repoInfo = getRepoInfo(mapId, authorizationHeader); // 모든 데이터를 종합하여 DTO 생성 - return buildAnalysisResultDto(mapId, analyzeResult, graphData, repoInfo); + return buildAnalysisResultDto(repoInfo); } catch (Exception e) { log.error("저장소 분석 실패: {}", e.getMessage(), e); @@ -83,14 +78,11 @@ public AnalysisResultDto refreshMindmap(String repoUrl, String prompt, String au log.info("새로고침 완료 - 변경 파일: {}, 분석 디렉터리: {}", refreshResult.getChanged_files(), refreshResult.getDirs_analyzed()); - // Step 2: 업데이트된 그래프 조회 - MindmapGraphDto graphData = getGraph(mapId, authorizationHeader); - - // Step 3: 저장소 정보 조회 + // Step 2: 저장소 정보 조회 RepoInfoResponse repoInfo = getRepoInfo(mapId, authorizationHeader); // 새로고침 결과를 DTO로 변환 - return buildRefreshResultDto(mapId, refreshResult, graphData, repoInfo); + return buildRefreshResultDto(repoInfo); } catch (Exception e) { log.error("마인드맵 새로고침 실패: {}", e.getMessage(), e); @@ -202,7 +194,9 @@ public RefreshResponse refreshLatest(String mapId, String prompt, String authHea /** * ArangoDB에서 repo_url 기반으로 마인드맵 데이터를 삭제 */ - public void deleteMindmapData(String mapId, String authorizationHeader) { + public void deleteMindmapData(String repoUrl, String authorizationHeader) { + String mapId = extractMapId(repoUrl); + webClient.delete() .uri(uriBuilder -> uriBuilder .path("/mindmap/{mapId}") @@ -225,59 +219,25 @@ private String extractMapId(String repoUrl) { } private AnalysisResultDto buildAnalysisResultDto( - String mapId, - AnalyzeResponse analyzeResult, - MindmapGraphDto graphData, RepoInfoResponse repoInfo ) { - // 그래프 데이터를 JSON 문자열로 변환 - String mapDataJson = convertGraphToJson(graphData); - - // AI가 생성한 제목 또는 기본 제목 - // TODO: 제목 생성 fastapi 메서드 필요 - String title = String.format("%s 프로젝트 구조 분석 (디렉터리 %d개)", - mapId, analyzeResult.getDirs_analyzed()); - return AnalysisResultDto.builder() .defaultBranch(repoInfo.getDefaultBranch()) .lastCommit(repoInfo.getLastCommit()) - .title(title) .errorMessage(null) .build(); } private AnalysisResultDto buildRefreshResultDto( - String mapId, - RefreshResponse refreshResult, - MindmapGraphDto graphData, RepoInfoResponse repoInfo ) { - String mapDataJson = convertGraphToJson(graphData); - - // TODO: 제목 생성 fastapi 메서드 필요 - String title = String.format("%s (변경 파일 %d개 반영)", - mapId, refreshResult.getChanged_files()); - return AnalysisResultDto.builder() .defaultBranch(repoInfo.getDefaultBranch()) .lastCommit(repoInfo.getLastCommit()) - .title(title) .errorMessage(null) .build(); } - private String convertGraphToJson(MindmapGraphDto graph) { - // TODO: ObjectMapper 사용하여 실제 JSON 변환 - return String.format( - "{\"nodes\":%d,\"edges\":%d,\"data\":\"%s\"}", - graph.getNodes().size(), - graph.getEdges().size(), - graph.toString() - ); - } - - - // === Response DTOs === @Getter @@ -315,8 +275,6 @@ public static class RepoInfoResponse { @JsonProperty("last_commit") @JsonDeserialize(using = IsoToLocalDateTimeDeserializer.class) private LocalDateTime lastCommit; - - } @Getter diff --git a/src/main/java/com/teamEWSN/gitdeun/common/fastapi/dto/AnalysisResultDto.java b/src/main/java/com/teamEWSN/gitdeun/common/fastapi/dto/AnalysisResultDto.java index a68a057..5ed759e 100644 --- a/src/main/java/com/teamEWSN/gitdeun/common/fastapi/dto/AnalysisResultDto.java +++ b/src/main/java/com/teamEWSN/gitdeun/common/fastapi/dto/AnalysisResultDto.java @@ -13,7 +13,7 @@ public class AnalysisResultDto { private LocalDateTime lastCommit; // Mindmap 관련 정보 - private String title; // 프롬프트 및 mindmap 정보 요약 + private String summary; // 프롬프트 요약 private String errorMessage; // 실패 시 전달될 에러메세지 // TODO: FastAPI 응답에 맞춰 필드 정의 } diff --git a/src/main/java/com/teamEWSN/gitdeun/invitation/controller/InvitationController.java b/src/main/java/com/teamEWSN/gitdeun/invitation/controller/InvitationController.java index cd70662..5e503e9 100644 --- a/src/main/java/com/teamEWSN/gitdeun/invitation/controller/InvitationController.java +++ b/src/main/java/com/teamEWSN/gitdeun/invitation/controller/InvitationController.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -40,7 +41,7 @@ public ResponseEntity inviteByEmail( public ResponseEntity> getInvitations( @PathVariable Long mapId, @AuthenticationPrincipal CustomUserDetails userDetails, - @PageableDefault(size = 10, sort = "createdAt,desc") Pageable pageable + @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { return ResponseEntity.ok(invitationService.getInvitationsByMindmap(mapId, userDetails.getId(), pageable)); } diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/controller/MindmapController.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/controller/MindmapController.java index fcaa86c..a09b510 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/controller/MindmapController.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/controller/MindmapController.java @@ -65,9 +65,10 @@ public ResponseEntity createMindmapAsync( @GetMapping("/{mapId}") public ResponseEntity getMindmap( @PathVariable Long mapId, - @AuthenticationPrincipal CustomUserDetails userDetails + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestHeader("Authorization") String authorizationHeader ) { - MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId, userDetails.getId()); + MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId, userDetails.getId(), authorizationHeader); return ResponseEntity.ok(responseDto); } @@ -80,7 +81,10 @@ public ResponseEntity updateMindmapTitle( @AuthenticationPrincipal CustomUserDetails userDetails, @RequestBody MindmapTitleUpdateDto request ) { - MindmapDetailResponseDto responseDto = mindmapService.updateMindmapTitle(mapId, userDetails.getId(), request); + MindmapDetailResponseDto responseDto = mindmapService.updateMindmapTitle( + mapId, + userDetails.getId(), + request); return ResponseEntity.ok(responseDto); } @@ -152,12 +156,12 @@ public ResponseEntity analyzePromptPreview( public ResponseEntity applyPromptHistory( @PathVariable Long mapId, @AuthenticationPrincipal CustomUserDetails userDetails, - @RequestBody PromptApplyRequestDto request + @RequestBody PromptApplyRequestDto request, + @RequestHeader("Authorization") String authorizationHeader ) { promptHistoryService.applyPromptHistory(mapId, userDetails.getId(), request); - // TODO: 마인드맵 정보 갱신 요청 후 상세 조회 - MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId, userDetails.getId()); + MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId, userDetails.getId(), authorizationHeader); return ResponseEntity.ok(responseDto); } diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/dto/request/MindmapCreateRequestDto.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/dto/request/MindmapCreateRequestDto.java index 771788f..f905a69 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/dto/request/MindmapCreateRequestDto.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/dto/request/MindmapCreateRequestDto.java @@ -7,5 +7,5 @@ @NoArgsConstructor public class MindmapCreateRequestDto { private String repoUrl; - private String prompt; + private String title; } diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/entity/PromptHistory.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/entity/PromptHistory.java index da07c3d..60b10d8 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/entity/PromptHistory.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/entity/PromptHistory.java @@ -27,7 +27,7 @@ public class PromptHistory extends CreatedEntity { private String prompt; @Column(length = 50) - private String title; // 분석 결과 요약 (기록 제목) + private String summary; // 분석 결과 요약 (기록 제목) @Builder.Default @Column(name = "applied", nullable = false) diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/mapper/MindmapMapper.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/mapper/MindmapMapper.java index 45210fc..9586921 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/mapper/MindmapMapper.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/mapper/MindmapMapper.java @@ -1,6 +1,7 @@ package com.teamEWSN.gitdeun.mindmap.mapper; import com.teamEWSN.gitdeun.mindmap.dto.MindmapDetailResponseDto; +import com.teamEWSN.gitdeun.mindmap.dto.MindmapGraphResponseDto; import com.teamEWSN.gitdeun.mindmap.dto.MindmapResponseDto; import com.teamEWSN.gitdeun.mindmap.dto.prompt.PromptHistoryResponseDto; import com.teamEWSN.gitdeun.mindmap.entity.Mindmap; @@ -28,4 +29,11 @@ public interface MindmapMapper { @Mapping(source = "appliedPromptHistory", target = "appliedPromptHistory") MindmapDetailResponseDto toDetailResponseDto(Mindmap mindmap); + @Mapping(source = "mindmap.id", target = "mindmapId") + @Mapping(source = "mindmap.promptHistories", target = "promptHistories") + @Mapping(source = "mindmap.appliedPromptHistory", target = "appliedPromptHistory") + @Mapping(source = "graphData", target = "mindmapGraph") + MindmapDetailResponseDto toDetailResponseDto(Mindmap mindmap, + MindmapGraphResponseDto graphData); + } diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapOrchestrationService.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapOrchestrationService.java index 954620b..226e748 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapOrchestrationService.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapOrchestrationService.java @@ -43,7 +43,7 @@ public void createMindmap(MindmapCreateRequestDto request, Long userId, String a log.info("마인드맵 생성 요청 검증 시작 - 사용자: {}", userId); ValidatedMindmapRequest validatedRequest = requestValidator.validateAndProcess( request.getRepoUrl(), - request.getPrompt(), + null, userId ); @@ -59,13 +59,13 @@ public void createMindmap(MindmapCreateRequestDto request, Long userId, String a // prompt가 null이면 기본 분석, 있으면 프롬프트 포함 분석 return fastApiClient.analyzeResult( normalizedUrl, - processedPrompt, + null, authHeader ); }).thenApply(analysisResult -> { // 2. 분석 결과를 바탕으로 DB에 마인드맵 정보 저장 (트랜잭션) log.info("분석 완료, DB 저장 시작 - 사용자: {}", userId); - return mindmapService.saveMindmapFromAnalysis(analysisResult, normalizedUrl, processedPrompt, userId); + return mindmapService.saveMindmapFromAnalysis(analysisResult, normalizedUrl, request.getTitle(), userId); }).whenComplete((mindmap, throwable) -> { // 3. 최종 결과에 따라 알림 전송 if (throwable != null) { @@ -96,7 +96,7 @@ public void refreshMindmap(Long mapId, String authHeader) { ); // 분석 결과를 DB에 업데이트 (트랜잭션) - mindmapService.updateMindmapFromAnalysis(mapId, analysisResult); + mindmapService.updateMindmapFromAnalysis(mapId,authHeader, analysisResult); log.info("비동기 새로고침 성공 - 마인드맵 ID: {}", mapId); } catch (Exception e) { diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapService.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapService.java index 6ff528a..c14438a 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapService.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/MindmapService.java @@ -7,6 +7,7 @@ import com.teamEWSN.gitdeun.mindmap.dto.*; import com.teamEWSN.gitdeun.mindmap.entity.Mindmap; import com.teamEWSN.gitdeun.mindmap.mapper.MindmapMapper; +import com.teamEWSN.gitdeun.mindmap.util.MindmapGraphCache; import com.teamEWSN.gitdeun.mindmapmember.entity.MindmapMember; import com.teamEWSN.gitdeun.mindmapmember.entity.MindmapRole; import com.teamEWSN.gitdeun.mindmapmember.repository.MindmapMemberRepository; @@ -33,24 +34,24 @@ public class MindmapService { private final VisitHistoryService visitHistoryService; private final MindmapSseService mindmapSseService; private final MindmapAuthService mindmapAuthService; - private final PromptHistoryService promptHistoryService; private final MindmapMapper mindmapMapper; private final MindmapRepository mindmapRepository; private final MindmapMemberRepository mindmapMemberRepository; private final RepoRepository repoRepository; private final UserRepository userRepository; + private final MindmapGraphCache mindmapGraphCache; // FastAPI 분석 결과를 받아 마인드맵을 생성하고 DB에 저장 (단일 트랜잭션) @Transactional - public Mindmap saveMindmapFromAnalysis(AnalysisResultDto analysisResult, String repoUrl, String prompt, Long userId) { + public Mindmap saveMindmapFromAnalysis(AnalysisResultDto analysisResult, String repoUrl, String requestTitle, Long userId) { User user = userRepository.findByIdAndDeletedAtIsNull(userId) .orElseThrow(() -> new GlobalException(ErrorCode.USER_NOT_FOUND_BY_ID)); - // 1. 저장소 정보 처리 (기존에 있으면 업데이트, 없으면 생성) + // 저장소 정보 처리 (기존에 있으면 업데이트, 없으면 생성) Repo repo = processRepository(repoUrl, analysisResult); - // 2. AI가 생성한 제목 또는 기본 제목 결정 - String title = determineTitle(analysisResult, user); + // AI가 생성한 제목 또는 기본 제목 결정 + String title = determineTitle(requestTitle, user); // 3. 마인드맵 엔티티 생성 및 저장 Mindmap mindmap = Mindmap.builder() @@ -61,15 +62,10 @@ public Mindmap saveMindmapFromAnalysis(AnalysisResultDto analysisResult, String .build(); mindmapRepository.save(mindmap); - // 4. 초기 프롬프트 히스토리 생성 (프롬프트가 있는 경우) - if (StringUtils.hasText(prompt)) { - promptHistoryService.createInitialPromptHistory(mindmap, prompt, title); - } - - // 5. 마인드맵 소유자 멤버로 등록 + // 마인드맵 소유자 멤버로 등록 mindmapMemberRepository.save(MindmapMember.of(mindmap, user, MindmapRole.OWNER)); - // 6. 방문 기록 생성 + // 방문 기록 생성 visitHistoryService.createVisitHistory(user, mindmap); log.info("마인드맵 DB 저장 완료 - ID: {}, 제목: {}", mindmap.getId(), title); @@ -78,7 +74,7 @@ public Mindmap saveMindmapFromAnalysis(AnalysisResultDto analysisResult, String // 마인드맵 상세 정보 조회 @Transactional(readOnly = true) - public MindmapDetailResponseDto getMindmap(Long mapId, Long userId) { + public MindmapDetailResponseDto getMindmap(Long mapId, Long userId, String authHeader) { if (!mindmapAuthService.hasView(mapId, userId)) { throw new GlobalException(ErrorCode.FORBIDDEN_ACCESS); } @@ -86,7 +82,13 @@ public MindmapDetailResponseDto getMindmap(Long mapId, Long userId) { Mindmap mindmap = mindmapRepository.findByIdAndDeletedAtIsNull(mapId) .orElseThrow(() -> new GlobalException(ErrorCode.MINDMAP_NOT_FOUND)); - return mindmapMapper.toDetailResponseDto(mindmap); + // 캐싱된 그래프 데이터 조회 + MindmapGraphResponseDto graphData = mindmapGraphCache.getGraphWithHybridCache( + mindmap.getRepo().getGithubRepoUrl(), + authHeader + ); + + return mindmapMapper.toDetailResponseDto(mindmap, graphData); } //마인드맵 제목 수정 @@ -110,13 +112,21 @@ public MindmapDetailResponseDto updateMindmapTitle(Long mapId, Long userId, Mind // 분석 결과를 바탕으로 기존 마인드맵을 업데이트 (새로고침) @Transactional - public MindmapDetailResponseDto updateMindmapFromAnalysis(Long mapId, AnalysisResultDto analysisResult) { + public MindmapDetailResponseDto updateMindmapFromAnalysis(Long mapId, String authHeader, AnalysisResultDto analysisResult) { Mindmap mindmap = mindmapRepository.findByIdAndDeletedAtIsNull(mapId) .orElseThrow(() -> new GlobalException(ErrorCode.MINDMAP_NOT_FOUND)); mindmap.getRepo().updateWithAnalysis(analysisResult); - MindmapDetailResponseDto responseDto = mindmapMapper.toDetailResponseDto(mindmap); + // 새로고침 시 그래프 캐시 무효화 + mindmapGraphCache.evictCache(mindmap.getRepo().getGithubRepoUrl()); + + MindmapGraphResponseDto graphData = mindmapGraphCache.getGraphWithHybridCache( + mindmap.getRepo().getGithubRepoUrl(), + authHeader + ); + + MindmapDetailResponseDto responseDto = mindmapMapper.toDetailResponseDto(mindmap, graphData); mindmapSseService.broadcastUpdate(mapId, responseDto); log.info("마인드맵 새로고침 DB 업데이트 완료 - ID: {}", mapId); @@ -136,6 +146,9 @@ public Repo deleteMindmap(Long mapId, Long userId) { Mindmap mindmap = mindmapRepository.findByIdAndDeletedAtIsNull(mapId) .orElseThrow(() -> new GlobalException(ErrorCode.MINDMAP_NOT_FOUND)); + // 삭제 시 관련 캐시도 무효화 + mindmapGraphCache.evictCache(mindmap.getRepo().getGithubRepoUrl()); + mindmap.softDelete(); log.info("마인드맵 소프트 삭제 완료 (DB) - ID: {}", mapId); @@ -146,7 +159,7 @@ public Repo deleteMindmap(Long mapId, Long userId) { * TODO: Webhook을 통한 마인드맵 업데이트 */ @Transactional - public void updateMindmapFromWebhook(WebhookUpdateDto dto, String authorizationHeader) { + public void updateMindmapFromWebhook(WebhookUpdateDto dto, String authHeader) { Repo repo = repoRepository.findByGithubRepoUrl(dto.getRepoUrl()) .orElseThrow(() -> new GlobalException(ErrorCode.REPO_NOT_FOUND_BY_URL)); @@ -156,8 +169,17 @@ public void updateMindmapFromWebhook(WebhookUpdateDto dto, String authorizationH repo.updateWithWebhookData(dto); + // Webhook 업데이트 시 관련 캐시 무효화 + mindmapGraphCache.evictCache(repo.getGithubRepoUrl()); + for (Mindmap mindmap : mindmapsToUpdate) { - MindmapDetailResponseDto responseDto = mindmapMapper.toDetailResponseDto(mindmap); + // 새로운 그래프 데이터로 업데이트된 응답 생성 + MindmapGraphResponseDto graphData = mindmapGraphCache.getGraphWithHybridCache( + repo.getGithubRepoUrl(), + authHeader + ); + + MindmapDetailResponseDto responseDto = mindmapMapper.toDetailResponseDto(mindmap, graphData); mindmapSseService.broadcastUpdate(mindmap.getId(), responseDto); log.info("Webhook으로 마인드맵 ID {} 업데이트 및 SSE 전송 완료", mindmap.getId()); } @@ -181,9 +203,9 @@ private Repo processRepository(String repoUrl, AnalysisResultDto analysisResult) }); } - private String determineTitle(AnalysisResultDto analysisResult, User user) { - if (StringUtils.hasText(analysisResult.getTitle())) { - return analysisResult.getTitle(); + private String determineTitle(String title, User user) { + if (StringUtils.hasText(title)) { + return title; } long userMindmapCount = mindmapRepository.countByUserAndDeletedAtIsNull(user); return "마인드맵 " + (userMindmapCount + 1); diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/PromptHistoryService.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/PromptHistoryService.java index f59ca3e..e7c198a 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/service/PromptHistoryService.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/service/PromptHistoryService.java @@ -52,7 +52,7 @@ public PromptPreviewResponseDto createPromptPreview(Long mapId, Long userId, Min AnalysisResultDto analysisResult = fastApiClient.refreshMindmap(repoUrl, req.getPrompt(), authorizationHeader); // FastAPI로부터 받은 analysisSummary 사용 - String summary = analysisResult.getTitle(); + String summary = analysisResult.getSummary(); // analysisSummary가 없거나 비어있는 경우 대체 로직 사용 if (summary == null || summary.trim().isEmpty()) { @@ -62,7 +62,7 @@ public PromptPreviewResponseDto createPromptPreview(Long mapId, Long userId, Min PromptHistory history = PromptHistory.builder() .mindmap(mindmap) .prompt(req.getPrompt()) - .title(summary) + .summary(summary) .applied(false) .build(); @@ -145,16 +145,6 @@ public void applyPromptHistory(Long mapId, Long userId, PromptApplyRequestDto re mindmap.applyPromptHistory(historyToApply); - // 마인드맵 제목도 프롬프트 타이틀로 업데이트 - mindmap.updateTitle(historyToApply.getTitle()); - - log.info("프롬프트 히스토리 적용 완료 - 마인드맵 ID: {}, 히스토리 ID: {}, 제목 변경: {}", - mapId, req.getHistoryId(), historyToApply.getTitle()); - - // 제목 변경 브로드캐스트 추가 - mindmapSseService.broadcastTitleChanged(mapId, historyToApply.getTitle()); - - mindmapSseService.broadcastPromptApplied(mapId, historyToApply.getId()); } @@ -180,23 +170,6 @@ public void deletePromptHistory(Long mapId, Long historyId, Long userId) { log.info("프롬프트 히스토리 삭제 완료 - 히스토리 ID: {}", historyId); } - /** - * 마인드맵 생성 시 초기 프롬프트 히스토리 생성 - */ - public void createInitialPromptHistory(Mindmap mindmap, String prompt, String promptTitle) { - if (prompt != null && !prompt.trim().isEmpty()) { - PromptHistory history = PromptHistory.builder() - .mindmap(mindmap) - .prompt(prompt) - .title(promptTitle) - .applied(true) - .build(); - - promptHistoryRepository.save(history); - log.info("초기 프롬프트 히스토리 생성 완료 - 마인드맵 ID: {}", mindmap.getId()); - } - } - /** * 프롬프트 결과 대체 요약 생성 */ diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapGraphCache.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapGraphCache.java index 75b57cd..af39e71 100644 --- a/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapGraphCache.java +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapGraphCache.java @@ -5,8 +5,6 @@ import com.teamEWSN.gitdeun.mindmap.dto.MindmapGraphResponseDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.annotation.CachePut; -import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -19,19 +17,14 @@ public class MindmapGraphCache { private final RedisTemplate redisTemplate; private final FastApiClient fastApiClient; - - // L1 캐시: Caffeine - @Cacheable(value = "MINDMAP_GRAPH_L1", key = "#mapId") - public MindmapGraphResponseDto getGraphFromL1Cache(String mapId) { - return null; // 캐시 미스 시 L2로 진행 - } + private final MindmapL1Cache mindmapL1Cache; // L2 캐시: Redis public MindmapGraphResponseDto getGraphWithHybridCache(String repoUrl, String authHeader) { String mapId = extractMapId(repoUrl); // 1. L1 캐시 확인 (Caffeine) - MindmapGraphResponseDto l1Result = getGraphFromL1Cache(mapId); + MindmapGraphResponseDto l1Result = mindmapL1Cache.getGraphFromL1Cache(mapId); if (l1Result != null) { log.debug("마인드맵 그래프 L1 캐시 히트 - mapId: {}", mapId); return l1Result; @@ -45,7 +38,7 @@ public MindmapGraphResponseDto getGraphWithHybridCache(String repoUrl, String au if (l2Result != null) { log.debug("마인드맵 그래프 L2 캐시 히트 - mapId: {}", mapId); // L1 캐시에 다시 저장 - cacheToL1(mapId, l2Result); + mindmapL1Cache.cacheToL1(mapId, l2Result); return l2Result; } } catch (Exception e) { @@ -66,7 +59,7 @@ public MindmapGraphResponseDto getGraphWithHybridCache(String repoUrl, String au log.warn("Redis 캐시 저장 실패 - mapId: {}", mapId, e); } - cacheToL1(mapId, responseDto); // L1: 30분 (CacheType에서 정의) + mindmapL1Cache.cacheToL1(mapId, responseDto); // L1: 30분 (CacheType에서 정의) return responseDto; @@ -81,11 +74,6 @@ public MindmapGraphResponseDto getGraphWithHybridCache(String repoUrl, String au } } - @CachePut(value = "MINDMAP_GRAPH_L1", key = "#mapId") - public MindmapGraphResponseDto cacheToL1(String mapId, MindmapGraphResponseDto data) { - return data; - } - // 캐시 무효화 (마인드맵 새로고침 시) public void evictCache(String repoUrl) { String mapId = extractMapId(repoUrl); @@ -94,6 +82,8 @@ public void evictCache(String repoUrl) { try { redisTemplate.delete(redisKey); log.info("마인드맵 그래프 캐시 무효화 완료 - mapId: {}", mapId); + // L1 캐시(Caffeine) 삭제 + mindmapL1Cache.evictL1Cache(mapId); } catch (Exception e) { log.warn("캐시 무효화 실패 - mapId: {}", mapId, e); } diff --git a/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapL1Cache.java b/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapL1Cache.java new file mode 100644 index 0000000..bcc7866 --- /dev/null +++ b/src/main/java/com/teamEWSN/gitdeun/mindmap/util/MindmapL1Cache.java @@ -0,0 +1,28 @@ +package com.teamEWSN.gitdeun.mindmap.util; + +import com.teamEWSN.gitdeun.mindmap.dto.MindmapGraphResponseDto; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Component; + +@Component +public class MindmapL1Cache { + + // L1 캐시에서 데이터를 가져오는 역할만 수행 + // 캐시가 있으면 반환, 없으면 null 반환 + @Cacheable(value = "MINDMAP_GRAPH_L1", key = "#mapId") + public MindmapGraphResponseDto getGraphFromL1Cache(String mapId) { + return null; + } + + // L1 캐시에 데이터를 저장하는 역할만 수행 + @CachePut(value = "MINDMAP_GRAPH_L1", key = "#mapId") + public MindmapGraphResponseDto cacheToL1(String mapId, MindmapGraphResponseDto data) { + return data; + } + + // L1 캐시 데이터 삭제 + @CacheEvict(value = "MINDMAP_GRAPH_L1", key = "#mapId") + public void evictL1Cache(String mapId) {} +} \ No newline at end of file diff --git a/src/main/java/com/teamEWSN/gitdeun/notification/controller/NotificationController.java b/src/main/java/com/teamEWSN/gitdeun/notification/controller/NotificationController.java index 0920563..a122231 100644 --- a/src/main/java/com/teamEWSN/gitdeun/notification/controller/NotificationController.java +++ b/src/main/java/com/teamEWSN/gitdeun/notification/controller/NotificationController.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -23,7 +24,7 @@ public class NotificationController { @GetMapping public ResponseEntity> getMyNotifications( @AuthenticationPrincipal CustomUserDetails userDetails, - @PageableDefault(size = 10, sort = "createdAt,desc") Pageable pageable) { + @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { Page notifications = notificationService.getNotifications(userDetails.getId(), pageable); return ResponseEntity.ok(notifications); } diff --git a/src/main/java/com/teamEWSN/gitdeun/visithistory/controller/VisitHistoryController.java b/src/main/java/com/teamEWSN/gitdeun/visithistory/controller/VisitHistoryController.java index 48f626b..19e62dd 100644 --- a/src/main/java/com/teamEWSN/gitdeun/visithistory/controller/VisitHistoryController.java +++ b/src/main/java/com/teamEWSN/gitdeun/visithistory/controller/VisitHistoryController.java @@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -43,7 +44,7 @@ public SseEmitter streamVisitHistoryUpdates( @GetMapping("/visits") public ResponseEntity> getVisitHistories( @AuthenticationPrincipal CustomUserDetails userDetails, - @PageableDefault(size = 10, sort = "lastVisitedAt,desc") Pageable pageable + @PageableDefault(size = 10, sort = "lastVisitedAt", direction = Sort.Direction.DESC) Pageable pageable ) { Page histories = visitHistoryService.getVisitHistories(userDetails.getId(), pageable); return ResponseEntity.ok(histories);