diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageService.java b/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageService.java index b913f057..8c6ebd46 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageService.java @@ -10,6 +10,7 @@ import com.deepdirect.deepwebide_be.chat.repository.ChatMessageReferenceRepository; import com.deepdirect.deepwebide_be.chat.repository.ChatMessageRepository; import com.deepdirect.deepwebide_be.file.domain.FileNode; +import com.deepdirect.deepwebide_be.file.domain.FileType; import com.deepdirect.deepwebide_be.file.repository.FileNodeRepository; import com.deepdirect.deepwebide_be.global.exception.ErrorCode; import com.deepdirect.deepwebide_be.global.exception.GlobalException; @@ -164,15 +165,9 @@ public CodePathListResponse getCodePaths(Long repositoryId, Long userId) { List fileNodes = fileNodeRepository.findAllByRepositoryId(repositoryId); List paths = fileNodes.stream() + .filter(node -> node.getFileType() == FileType.FILE) // ← 폴더는 제외 .map(FileNode::getPath) - .sorted((a, b) -> { - // 폴더 먼저, 사전순 정렬 - boolean isAFolder = a.endsWith("/"); - boolean isBFolder = b.endsWith("/"); - if (isAFolder && !isBFolder) return -1; - if (!isAFolder && isBFolder) return 1; - return a.compareToIgnoreCase(b); - }) + .sorted(String::compareToIgnoreCase) .toList(); return CodePathListResponse.builder().paths(paths).build(); diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/repository/FileContentRepository.java b/src/main/java/com/deepdirect/deepwebide_be/file/repository/FileContentRepository.java index 85ada5f4..f983711a 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/repository/FileContentRepository.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/repository/FileContentRepository.java @@ -3,7 +3,10 @@ import com.deepdirect.deepwebide_be.file.domain.FileContent; import com.deepdirect.deepwebide_be.file.domain.FileNode; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface FileContentRepository extends JpaRepository { @@ -12,4 +15,6 @@ public interface FileContentRepository extends JpaRepository Optional findByFileNode(FileNode fileNode); + @Query("SELECT fc FROM FileContent fc WHERE fc.fileNode.repository.id = :repositoryId") + List findAllByRepositoryId(@Param("repositoryId") Long repositoryId); } diff --git a/src/main/java/com/deepdirect/deepwebide_be/history/dto/request/HistorySaveRequest.java b/src/main/java/com/deepdirect/deepwebide_be/history/dto/request/HistorySaveRequest.java index 65449a6d..28060d3b 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/history/dto/request/HistorySaveRequest.java +++ b/src/main/java/com/deepdirect/deepwebide_be/history/dto/request/HistorySaveRequest.java @@ -14,33 +14,4 @@ public class HistorySaveRequest { @Schema(description = "저장 메시지(커밋 메시지)", example = "1차 개발 완료") private String message; - @Schema(description = "저장할 파일/폴더 노드 목록 (트리 구조 아님, 전체 파일/폴더의 납작한 배열)") - private List nodes; - - @Getter - @NoArgsConstructor - @Schema(description = "파일 또는 폴더 노드 정보") - public static class NodeDto { - - @Schema(description = "파일/폴더 ID (생성 직후는 null일 수 있음, 기존 파일은 PK)", - example = "4") - private Long fileId; - - @Schema(description = "파일/폴더명", example = "Main.java") - private String fileName; - - @Schema(description = "노드 타입 (FILE: 파일, FOLDER: 폴더)", example = "FILE") - private String fileType; - - @Schema(description = "부모 폴더 ID (최상위면 null)", example = "1") - private Long parentId; - - @Schema(description = "파일/폴더 전체 경로", example = "src/Main.java") - private String path; - - @Schema(description = "파일 내용 (파일일 때만 존재, 폴더면 null)", - example = "public class Main { ... }", - nullable = true) - private String content; - } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/history/service/HistoryService.java b/src/main/java/com/deepdirect/deepwebide_be/history/service/HistoryService.java index ab2df92c..6eb7b4a4 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/history/service/HistoryService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/history/service/HistoryService.java @@ -45,74 +45,48 @@ public class HistoryService { @Transactional public HistorySaveResponse saveHistory(Long repositoryId, Long userId, HistorySaveRequest request) { - - for (HistorySaveRequest.NodeDto dto : request.getNodes()) { - System.out.println( - "fileId=" + dto.getFileId() + - ", fileName=" + dto.getFileName() + - ", parentId=" + dto.getParentId() + - ", path=" + dto.getPath() - ); - } - // 1. 권한 체크 및 레포 조회 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); - // 2. 요청에서 전체 fileId set 추출 - Set requestFileIds = request.getNodes().stream() - .map(HistorySaveRequest.NodeDto::getFileId) - .collect(Collectors.toSet()); - - // 3. 현재 DB에 있는 FileNode 전체 조회 (삭제 대상 판별용) + // 2. 현재 DB의 파일/폴더 전체 조회 List dbNodes = fileNodeRepository.findAllByRepositoryId(repositoryId); - // 4. DB에 있지만 요청에 없는 FileNode 삭제 (자식 먼저 삭제) - List toDelete = dbNodes.stream() - .filter(node -> !requestFileIds.contains(node.getId())) - .sorted((a, b) -> b.getPath().length() - a.getPath().length()) - .toList(); - for (FileNode node : toDelete) { - if (node.getFileType() == FileType.FILE) { - fileContentRepository.deleteByFileNode(node); - } - fileNodeRepository.delete(node); - } - - // 5. id → FileNode (DB에 이미 있던 것만) - Map idToNode = dbNodes.stream() - .collect(Collectors.toMap(FileNode::getId, n -> n)); - - // 6. id → 요청 NodeDto - Map idToDto = request.getNodes().stream() - .collect(Collectors.toMap(HistorySaveRequest.NodeDto::getFileId, n -> n)); - - // 7. parent → 자식 순서로 재귀 저장 - for (Long nodeId : idToDto.keySet()) { - saveNodeRecursive(nodeId, idToNode, idToDto, repo); - } - - // 8. History & HistoryFile 기록 (스냅샷처럼) - History history = History.builder() - .repository(repo) - .message(request.getMessage()) - .authorId(userId) - .createdAt(LocalDateTime.now()) - .build(); - history = historyRepository.save(history); - - History finalHistory = history; - List historyFiles = request.getNodes().stream() - .map(dto -> HistoryFile.builder() - .history(finalHistory) - .fileId(dto.getFileId()) - .fileName(dto.getFileName()) - .fileType(dto.getFileType()) - .parentId(dto.getParentId()) - .path(dto.getPath()) - .content(dto.getContent()) + // 3. 파일 내용도 모두 조회 (FileContent와 Join, 또는 fileContentRepository 사용) + Map nodeIdToContent = fileContentRepository.findAllByRepositoryId(repositoryId) + .stream().collect(Collectors.toMap( + c -> c.getFileNode().getId(), c -> c + )); + + // 4. History 생성 + History history = historyRepository.save( + History.builder() + .repository(repo) + .message(request.getMessage()) + .authorId(userId) + .createdAt(LocalDateTime.now()) + .build() + ); + + // 5. HistoryFile (스냅샷) 기록 + List historyFiles = dbNodes.stream() + .map(node -> HistoryFile.builder() + .history(history) + .fileId(node.getId()) + .fileName(node.getName()) + .fileType(node.getFileType().name()) + .parentId(node.getParent() != null ? node.getParent().getId() : null) + .path(node.getPath()) + .content( + node.getFileType() == FileType.FILE + ? (nodeIdToContent.get(node.getId()) != null + ? new String(nodeIdToContent.get(node.getId()).getContent(), StandardCharsets.UTF_8) + : null) + : null + ) .build() ).toList(); + historyFileRepository.saveAll(historyFiles); return HistorySaveResponse.builder() @@ -120,43 +94,6 @@ public HistorySaveResponse saveHistory(Long repositoryId, Long userId, HistorySa .build(); } - private void saveNodeRecursive( - Long clientFileId, - Map idToNode, - Map idToDto, - Repository repo - ) { - // 이미 저장된 노드는 무시 - if (idToNode.containsKey(clientFileId)) return; - - HistorySaveRequest.NodeDto dto = idToDto.get(clientFileId); - - FileNode parent = null; - if (dto.getParentId() != null) { - saveNodeRecursive(dto.getParentId(), idToNode, idToDto, repo); - parent = idToNode.get(dto.getParentId()); - } - - FileNode fileNode = FileNode.builder() - .repository(repo) - .name(dto.getFileName()) - .fileType(FileType.valueOf(dto.getFileType())) - .parent(parent) - .path(dto.getPath()) - .build(); - fileNode = fileNodeRepository.save(fileNode); - - idToNode.put(clientFileId, fileNode); - - if (fileNode.getFileType() == FileType.FILE) { - FileContent content = FileContent.builder() - .fileNode(fileNode) - .content(dto.getContent() == null ? new byte[0] : dto.getContent().getBytes(StandardCharsets.UTF_8)) - .build(); - fileContentRepository.save(content); - } - } - @Transactional(readOnly = true) public HistoryDetailResponse getHistoryDetail(Long repositoryId, Long historyId, Long userId) { // 1. 권한 체크 & 레포 확인 diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/controller/EmailVerificationController.java b/src/main/java/com/deepdirect/deepwebide_be/member/controller/EmailVerificationController.java index 3ff6a404..a8b8ad6a 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/controller/EmailVerificationController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/controller/EmailVerificationController.java @@ -1,24 +1,15 @@ package com.deepdirect.deepwebide_be.member.controller; import com.deepdirect.deepwebide_be.member.service.EmailVerificationService; +import com.deepdirect.deepwebide_be.member.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.view.RedirectView; -import java.util.HashMap; -import java.util.Map; - -//@CrossOrigin( -// origins = { -// "http://localhost:5173", -// "https://www.deepdirect.site", -// "https://api.deepdirect.site" -// }, -// allowCredentials = "true" -//) @RestController @RequiredArgsConstructor @RequestMapping("/api/auth/email") @@ -26,24 +17,22 @@ public class EmailVerificationController { private final EmailVerificationService emailVerificationService; + private final UserService userService; @Operation( summary = "이메일 인증" ) @GetMapping("/send-code") - public ResponseEntity> verifyEmail(@RequestParam String code) { + public RedirectView verifyEmail(@RequestParam String code) { boolean result = emailVerificationService.verifyEmailCode(code); - Map response = new HashMap<>(); + String email = emailVerificationService.findVerifiedEmailByCode(code); - // TODO: 리다이랙트 넣기~ if (result) { - response.put("success", true); - response.put("message", "이메일 인증이 완료되었습니다."); - return ResponseEntity.ok(response); - } else { - response.put("success", false); - response.put("message", "인증 코드가 만료되었거나 유효하지 않습니다."); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + userService.setEmailVerificationService(email); } + + RedirectView redirectView = new RedirectView(); + redirectView.setUrl("https://www.deepdirect.site/sign-in"); + return redirectView; } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/service/EmailVerificationService.java b/src/main/java/com/deepdirect/deepwebide_be/member/service/EmailVerificationService.java index 9ab8961d..84ed3791 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/service/EmailVerificationService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/service/EmailVerificationService.java @@ -40,7 +40,7 @@ public String createVerification(String email) { // 이메일 인증 요청 메일 발송 public void sendVerificationEmail(String email, String code) { // String link = "http://localhost:8080/api/auth/email/send-code?code=" + code; - String link = "https://api.deepwebide.site/api/auth/email/send-code?code=" + code; + String link = "https://api.deepdirect.site/api/auth/email/send-code?code=" + code; SimpleMailMessage message = new SimpleMailMessage(); message.setTo(email); @@ -53,7 +53,6 @@ public void sendVerificationEmail(String email, String code) { // 이메일 인증 코드 검증 public boolean verifyEmailCode(String code) { return emailVerificationRepository.findByEmailCode(code) - .filter(verification -> !verification.isVerified()) // 기존 인증 여부 확인 .filter(verification -> verification.getExpiresAt().isAfter(LocalDateTime.now())) // 만료 여부 확인 .map(verification -> { @@ -64,4 +63,10 @@ public boolean verifyEmailCode(String code) { }) .orElse(false); } + + public String findVerifiedEmailByCode(String code) { + return emailVerificationRepository.findByEmailCode(code) + .map(EmailVerification::getEmail) + .orElseThrow(() -> new IllegalArgumentException("해당 코드로 등록된 이메일이 없습니다.")); + } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/service/UserService.java b/src/main/java/com/deepdirect/deepwebide_be/member/service/UserService.java index 9dad9720..cbf14c42 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/service/UserService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/service/UserService.java @@ -266,4 +266,13 @@ public void verifyAndResetPassword(PasswordResetRequest request, String authoriz userRepository.save(user); reauthTokenService.delete(email); } + + @Transactional + public void setEmailVerificationService(String email) { + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new GlobalException(ErrorCode.USER_NOT_FOUND)); + + user.setEmailVerified(true); + userRepository.save(user); + } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/util/NicknameGenerator.java b/src/main/java/com/deepdirect/deepwebide_be/member/util/NicknameGenerator.java index ea13853d..38a352f4 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/util/NicknameGenerator.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/util/NicknameGenerator.java @@ -6,11 +6,15 @@ public class NicknameGenerator { private static final List ADJECTIVES = List.of( - "슬기로운", "권태로운", "우는", "웃는", "기쁜", "화난", "차가운", "뜨거운", "평범한", "용감한", "행복한", - "느긋한", "조용한", "시끄러운", "상냥한", "무뚝뚝한", "겁쟁이인", "귀여운", "엉뚱한", "멍한", "똑똑한", "배고픈", - "침착한", "우울한", "광기어린", "수줍은", "분노한", "우아한", "애매한", "날카로운", "귀찮은", "엉망진창인", - "반짝이는", "무서운", "요란한", "재빠른", "정중한", "자신감 넘치는", "불안한", "느린", "심심한", "정신없는", - "끈질긴", "달콤한", "과묵한", "산만한", "진지한", "까칠한", "활기찬", "졸린", "자유로운", "호기심 많은", "고통스러운" + "겁쟁이인", "고상한", "고통스러운", "과묵한", "광기어린", "괴상한", "권태로운", "귀여운", "귀찮은", "기쁜", + "까칠한", "깨끗한", "끈질긴", "날카로운", "노련한", "느긋한", "느린", "달콤한", "따뜻한", "똑똑한", + "뜨거운", "멍한", "무뚝뚝한", "무서운", "반짝이는", "밝은", "배고픈", "부끄러운", "분노한", "불안한", + "빠른", "산만한", "상냥한", "상쾌한", "서투른", "수줍은", "쉬운", "슬기로운", "시끄러운", "신난", + "심란한", "심심한", "쓸쓸한", "애매한", "어두운", "어려운", "엉뚱한", "엉망진창인", "엉큼한", "여유로운", + "예민한", "요란한", "요망한", "용감한", "우는", "우아한", "우울한", "웃는", "의젓한", "자신감넘치는", + "자유로운", "작은", "장난스런", "재빠른", "정신없는", "정중한", "조급한", "조용한", "졸린", "즐거운", + "진지한", "차가운", "침착한", "큰", "평범한", "피곤한", "행복한", "호기심 많은", "홀가분한", "화난", + "활기찬", "활발한", "흥분한", "희망찬" ); private static final Random RANDOM = new Random();