diff --git a/backend/src/main/java/com/stackup/stackup/github/application/GithubRepositoryService.java b/backend/src/main/java/com/stackup/stackup/github/application/GithubRepositoryService.java index 39ba37e3..6312a1b9 100644 --- a/backend/src/main/java/com/stackup/stackup/github/application/GithubRepositoryService.java +++ b/backend/src/main/java/com/stackup/stackup/github/application/GithubRepositoryService.java @@ -42,7 +42,11 @@ public GithubRepositoryResult register(Long userId, RegisterRepositoryCommand co User user = userRepository.findByIdAndDeletedFalse(userId) .orElseThrow(() -> new DomainException(ApiErrorCode.USER_NOT_FOUND)); - if (repositoryRepository.findByUser_IdAndGithubRepoIdAndDeletedFalse(userId, command.githubRepoId()).isPresent()) { + // soft delete 된 행이 있으면 새 INSERT 대신 그 행을 복구한다 + GithubRepository existing = repositoryRepository + .findByUser_IdAndGithubRepoId(userId, command.githubRepoId()) + .orElse(null); + if (existing != null && !existing.isDeleted()) { throw new DomainException(ApiErrorCode.REPO_ALREADY_REGISTERED); } @@ -55,14 +59,20 @@ public GithubRepositoryResult register(Long userId, RegisterRepositoryCommand co throw new DomainException(ApiErrorCode.VALIDATION_ERROR); } - GithubRepository repo = repositoryRepository.save(GithubRepository.create( - user, - meta.id(), - meta.name(), - meta.fullName(), - meta.htmlUrl(), - meta.defaultBranch() - )); + GithubRepository repo; + if (existing != null) { + existing.reactivate(meta.name(), meta.fullName(), meta.htmlUrl(), meta.defaultBranch()); + repo = existing; + } else { + repo = repositoryRepository.save(GithubRepository.create( + user, + meta.id(), + meta.name(), + meta.fullName(), + meta.htmlUrl(), + meta.defaultBranch() + )); + } events.publishEvent(new RepositoryRegisteredEvent(userId, repo.getId())); return GithubRepositoryResult.of(repo); diff --git a/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepository.java b/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepository.java index 09f967b6..eee1bf87 100644 --- a/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepository.java +++ b/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepository.java @@ -117,4 +117,11 @@ public void markFailed() { public void markDeleted() { this.deleted = true; } + + // 이전에 soft delete 된 레포를 다시 등록할 때 같은 행을 복구한다. + public void reactivate(String repoName, String repoFullName, String repoUrl, String defaultBranch) { + this.deleted = false; + this.status = RepositoryStatus.PENDING; + updateMetadata(repoName, repoFullName, repoUrl, defaultBranch); + } } diff --git a/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepositoryRepository.java b/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepositoryRepository.java index b7c2514a..560a1c4a 100644 --- a/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepositoryRepository.java +++ b/backend/src/main/java/com/stackup/stackup/github/domain/GithubRepositoryRepository.java @@ -13,5 +13,7 @@ public interface GithubRepositoryRepository extends JpaRepository findByUser_IdAndGithubRepoIdAndDeletedFalse(Long userId, Long githubRepoId); + Optional findByUser_IdAndGithubRepoId(Long userId, Long githubRepoId); + List findByUser_IdAndGithubRepoIdInAndDeletedFalse(Long userId, Set githubRepoIds); } diff --git a/frontend/src/features/analysis/model/types.ts b/frontend/src/features/analysis/model/types.ts index 71b7c1d3..d5bad745 100644 --- a/frontend/src/features/analysis/model/types.ts +++ b/frontend/src/features/analysis/model/types.ts @@ -20,7 +20,7 @@ export type AnalyzedDocument = { updatedAt: string } -// /api/stream/me 의 DOC_STATE / REPO_STATE 이벤트 data 의 payload 필드. +// /realtime/stream/me 의 DOC_STATE / REPO_STATE 이벤트 data 의 payload 필드. export type AnalysisEventPayload = { targetType: AnalysisSourceType targetId: number diff --git a/frontend/src/pages/Workspace/model/useWorkspaceAnalysisStream.ts b/frontend/src/pages/Workspace/model/useWorkspaceAnalysisStream.ts index 53a43e4c..77d65b1e 100644 --- a/frontend/src/pages/Workspace/model/useWorkspaceAnalysisStream.ts +++ b/frontend/src/pages/Workspace/model/useWorkspaceAnalysisStream.ts @@ -6,7 +6,6 @@ import { repoKeys } from '@/features/repo' import { documentKeys } from '@/features/analysis' import { useEventStream } from '@/shared/hooks' -// /api/stream/me 를 구독해 분석 상태(DOC_STATE/REPO_STATE) 가 오면 // 이력서·레포·문서 쿼리를 무효화 → 화면이 자동으로 최신 상태로 갱신된다. export function useWorkspaceAnalysisStream() { const queryClient = useQueryClient() @@ -20,7 +19,7 @@ export function useWorkspaceAnalysisStream() { }, [queryClient]) useEventStream({ - path: '/api/stream/me', + path: '/realtime/stream/me', getToken, handlers: { DOC_STATE: invalidateAll,