Skip to content
Merged

Dev #38

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ public class SecurityPath {
// hasRole("USER")
public static final String[] USER_ENDPOINTS = {
"/api/auth/connect/github/state",
"/api/auth/logout",
"/api/auth/social",
"/api/users/me",
"/api/users/me/**",
"/api/auth/logout",
"/api/repos",
"/api/repos/**",
"/api/mindmaps/**",
"/api/history/**"
"/api/history/**",
"/api/s3/bucket/**"
};

// hasRole("ADMIN")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ public ResponseEntity<MindmapResponseDto> createMindmap(
// λ§ˆμΈλ“œλ§΅ 상세 쑰회 (μœ μ € 인가 ν™•μΈν•„μš”?)
@GetMapping("/{mapId}")
public ResponseEntity<MindmapDetailResponseDto> getMindmap(
@PathVariable Long mapId
@PathVariable Long mapId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId);
MindmapDetailResponseDto responseDto = mindmapService.getMindmap(mapId, userDetails.getId());
return ResponseEntity.ok(responseDto);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.teamEWSN.gitdeun.mindmap.controller;

import com.teamEWSN.gitdeun.mindmap.service.MindmapSseService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
@RequiredArgsConstructor
public class MindmapSseController {

private final MindmapSseService mindmapSseService;

// ν΄λΌμ΄μ–ΈνŠΈμ˜ νŠΉμ • λ§ˆμΈλ“œλ§΅μ˜ μ—…λ°μ΄νŠΈλ₯Ό ꡬ독
@GetMapping(value = "/api/mindmaps/{mapId}/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribeToMindmapUpdates(@PathVariable Long mapId) {
return mindmapSseService.subscribe(mapId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.teamEWSN.gitdeun.mindmap.mapper.MindmapMapper;
import com.teamEWSN.gitdeun.mindmapmember.repository.MindmapMemberRepository;
import com.teamEWSN.gitdeun.mindmap.repository.MindmapRepository;
import com.teamEWSN.gitdeun.mindmapmember.service.MindmapAuthService;
import com.teamEWSN.gitdeun.repo.entity.Repo;
import com.teamEWSN.gitdeun.repo.repository.RepoRepository;
import com.teamEWSN.gitdeun.repo.service.RepoService;
Expand All @@ -36,6 +37,8 @@ public class MindmapService {

private final VisitHistoryService visitHistoryService;
private final RepoService repoService;
private final MindmapSseService mindmapSseService;
private final MindmapAuthService mindmapAuthService;
private final MindmapMapper mindmapMapper;
private final MindmapRepository mindmapRepository;
private final MindmapMemberRepository mindmapMemberRepository;
Expand Down Expand Up @@ -117,9 +120,15 @@ private long findNextCheckSequence(User user) {

/**
* λ§ˆμΈλ“œλ§΅ 상세 정보 쑰회
* μ €μž₯μ†Œ μ—…λ°μ΄νŠΈλŠ” Fast API Webhook μ•Œλ¦Ό
* λ§ˆμΈλ“œλ§΅ λ³€κ²½μ΄λ‚˜ 리뷰 생성 μ‹œ SSE μ μš©μ„ ν†΅ν•œ μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ (μƒˆλ‘œκ³ μΉ¨ x)
*/
@Transactional
public MindmapDetailResponseDto getMindmap(Long mapId) {
public MindmapDetailResponseDto getMindmap(Long mapId, Long userId) {
if (!mindmapAuthService.hasView(mapId, userId)) {
throw new GlobalException(ErrorCode.FORBIDDEN_ACCESS); // 멀버 κΆŒν•œ 확인
}

Mindmap mindmap = mindmapRepository.findById(mapId)
.orElseThrow(() -> new GlobalException(ErrorCode.MINDMAP_NOT_FOUND));

Expand Down Expand Up @@ -148,9 +157,14 @@ public MindmapDetailResponseDto refreshMindmap(Long mapId, Long userId) {

// 데이터 μ΅œμ‹ ν™”
mindmap.getRepo().updateWithAnalysis(dto);
mindmap.updateMapData(dto.getMapData()); // Mindmap 엔티티에 편의 λ©”μ„œλ“œ μΆ”κ°€ ν•„μš”
mindmap.updateMapData(dto.getMapData());

return mindmapMapper.toDetailResponseDto(mindmap);
MindmapDetailResponseDto responseDto = mindmapMapper.toDetailResponseDto(mindmap);

// μ—…λ°μ΄νŠΈλœ λ§ˆμΈλ“œλ§΅ 정보λ₯Ό λͺ¨λ“  κ΅¬λ…μžμ—κ²Œ 방솑
mindmapSseService.broadcastUpdate(mapId, responseDto);

return responseDto;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.teamEWSN.gitdeun.mindmap.service;

import com.teamEWSN.gitdeun.mindmap.dto.MindmapDetailResponseDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Service
public class MindmapSseService {

// μŠ€λ ˆλ“œ μ•ˆμ „(thread-safe)ν•œ 자료ꡬ쑰 μ‚¬μš©
private final Map<Long, List<SseEmitter>> emitters = new ConcurrentHashMap<>();

// ν΄λΌμ΄μ–ΈνŠΈκ°€ ꡬ독을 μš”μ²­ν•  λ•Œ 호좜
public SseEmitter subscribe(Long mapId) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); // νƒ€μž„μ•„μ›ƒμ„ 맀우 길게 μ„€μ •
// νŠΉμ • λ§ˆμΈλ“œλ§΅ ID에 ν•΄λ‹Ήν•˜λŠ” Emitter λ¦¬μŠ€νŠΈμ— μΆ”κ°€
this.emitters.computeIfAbsent(mapId, k -> new CopyOnWriteArrayList<>()).add(emitter);

emitter.onCompletion(() -> this.emitters.get(mapId).remove(emitter));
emitter.onTimeout(() -> this.emitters.get(mapId).remove(emitter));

// μ—°κ²° 성곡을 μ•Œλ¦¬λŠ” 더미 이벀트 전솑
try {
emitter.send(SseEmitter.event().name("connect").data("Connected!"));
} catch (IOException e) {
log.error("SSE μ—°κ²° 쀑 였λ₯˜ λ°œμƒ", e);
}

return emitter;
}

// λ§ˆμΈλ“œλ§΅μ΄ μ—…λ°μ΄νŠΈλ˜λ©΄ 이 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ λͺ¨λ“  κ΅¬λ…μžμ—κ²Œ 방솑
public void broadcastUpdate(Long mapId, MindmapDetailResponseDto updatedMindmap) {
List<SseEmitter> mapEmitters = this.emitters.get(mapId);
if (mapEmitters == null) {
return;
}

mapEmitters.forEach(emitter -> {
try {
emitter.send(SseEmitter.event()
.name("mindmap-update") // 이벀트 이름 μ§€μ •
.data(updatedMindmap)); // μ—…λ°μ΄νŠΈλœ λ§ˆμΈλ“œλ§΅ 데이터 전솑
} catch (IOException e) {
log.error("SSE 데이터 전솑 쀑 였λ₯˜ λ°œμƒ, emitter 제거", e);
mapEmitters.remove(emitter);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ public interface MindmapMemberRepository extends JpaRepository<MindmapMember, Lo
/* OWNER/EDITOR/VIEWER μ—¬λΆ€ */
boolean existsByMindmapIdAndUserId(Long mindmapId, Long userId);

boolean existsByMindmapIdAndUserIdAndRole(
Long mindmapId, Long userId, MindmapRole role);
boolean existsByMindmapIdAndUserIdAndRole(Long mindmapId, Long userId, MindmapRole role);

boolean existsByMindmapIdAndUserIdAndRoleIn(
Long mindmapId, Long userId, Collection<MindmapRole> roles);
boolean existsByMindmapIdAndUserIdAndRoleIn(Long mindmapId, Long userId, Collection<MindmapRole> roles);

// κΆŒν•œ λ³€κ²½
Optional<MindmapMember> findByIdAndMindmapId(Long memberId, Long mindmapId);
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/teamEWSN/gitdeun/repo/entity/Repo.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public class Repo {
@Column(name = "github_repo_url", length = 512, nullable = false, unique = true)
private String githubRepoUrl;

// @Column(name="repo_name", length = 255, nullable = false)
// private String repoName;
//
// @Column(length = 100)
// private String language;

@Column(name = "default_branch", length = 100)
private String defaultBranch; // 기본 브랜치

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public RepoUpdateCheckResponseDto checkUpdateNeeded(Long repoId) {
// λ§ˆμΈλ“œλ§΅ 생성 μ‹œ repo 생성 및 μ—…λ°μ΄νŠΈ
@Transactional
public Repo createOrUpdate(String repoUrl, AnalysisResultDto dto) {
// github repository url 일치 여뢀에 따라
return repoRepository.findByGithubRepoUrl(repoUrl)
.map(r -> { r.updateWithAnalysis(dto); return r; })
.orElseGet(() -> {
Expand Down