From c9f6625c9ee5ed01b9a5fc8f47e3a4819dd03e89 Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Thu, 31 Jul 2025 21:27:14 +0900 Subject: [PATCH 01/20] =?UTF-8?q?fix(eooro):=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EC=83=81=ED=83=9C=EC=9E=84=EC=9D=84=20=EC=95=8C?= =?UTF-8?q?=EB=A0=A4=EC=A3=BC=EB=8A=94=20=EC=97=90=EB=9F=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepdirect/deepwebide_be/global/exception/ErrorCode.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index 3ac53a9..cd6f688 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -48,6 +48,8 @@ public enum ErrorCode { INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 리프레시 토큰입니다."), INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), MISSING_TOKEN(HttpStatus.UNAUTHORIZED, "Access Token이 누락되었습니다."), + EMAIL_NOT_VERIFIED(HttpStatus.UNAUTHORIZED, "이메일 인증이 완료되지 않았습니다."), + // 403 FORBIDDEN FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), From 243ab336530ca432fee4b0aa4e751f4a2697ee5f Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Thu, 31 Jul 2025 21:28:02 +0900 Subject: [PATCH 02/20] =?UTF-8?q?fix(service):=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=EC=9D=B4=20=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepdirect/deepwebide_be/member/service/UserService.java | 4 ++++ 1 file changed, 4 insertions(+) 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 cbf14c4..26ac8df 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 @@ -117,6 +117,10 @@ public SignInResponse signIn(SignInRequest request, HttpServletResponse servletR User user = userRepository.findByEmail(request.getEmail()) .orElseThrow(() -> new GlobalException(ErrorCode.WRONG_PASSWORD)); + if (!user.isEmailVerified()) { + throw new GlobalException(ErrorCode.EMAIL_NOT_VERIFIED); + } + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new GlobalException(ErrorCode.WRONG_PASSWORD); } From 8dc8dda5566b71ad87d12b50b0f7fcdcff28aa80 Mon Sep 17 00:00:00 2001 From: vayaconchoi Date: Thu, 31 Jul 2025 23:15:46 +0900 Subject: [PATCH 03/20] =?UTF-8?q?fix(=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8?= =?UTF-8?q?=20=EA=B8=80=EC=9E=90=EC=88=98=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80):=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B8=80=EC=9E=90=EC=88=98=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?DP-175?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 비밀번호 글자수 에러 메시지 추가 - 회원가입 비밀번호 설정 시 8자 미만으로 가면 포맷에러코드가 나옴 - 비밀번호가 8자 이상인지 먼저 확인하도록 로직 추가 + 에러 메시지 추가 --- .../deepdirect/deepwebide_be/global/exception/ErrorCode.java | 1 + .../deepdirect/deepwebide_be/member/service/UserService.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index 3ac53a9..3d6abf0 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -15,6 +15,7 @@ public enum ErrorCode { REPOSITORY_MEMBER_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "최대 인원이 초과되어 입장할 수 없습니다."), INVALID_USERNAME(HttpStatus.BAD_REQUEST, "이름은 한글 2자 이상만 입력 가능합니다."), INVALID_PASSWORD_FORMAT(HttpStatus.BAD_REQUEST, "비밀번호는 영어 대문자, 소문자, 숫자, 특수문자를 모두 포함해야 합니다."), + PASSWORD_TOO_SHORT(HttpStatus.BAD_REQUEST,"비밀번호는 8자 이상이어야 합니다."), VERIFICATION_CODE_EXPIRED(HttpStatus.BAD_REQUEST, "인증번호가 만료되었습니다."), NOT_OWNER_CHANGE(HttpStatus.BAD_REQUEST, "오너만 이름을 변경할 수 있습니다."), //오너만 이름 변경!! NOT_OWNER_DELETE(HttpStatus.BAD_REQUEST, "오너만 삭제할 수 있습니다."), //오너만 삭제 가능 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 cbf14c4..34e8fcc 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 @@ -75,6 +75,10 @@ public SignUpResponse signup(SignUpRequest request) { throw new GlobalException(ErrorCode.PASSWORDS_DO_NOT_MATCH); } + if (request.getPassword().length() < 8) { + throw new GlobalException(ErrorCode.PASSWORD_TOO_SHORT); + } + if (!PASSWORD_REGEX.matcher(request.getPassword()).matches()) { throw new GlobalException(ErrorCode.INVALID_PASSWORD_FORMAT); } From ed7a934d67d7d31017a742f75cf087cf6b8cabab Mon Sep 17 00:00:00 2001 From: sunsetkk Date: Thu, 31 Jul 2025 23:42:03 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feature(chat):=20chatMessage=20ManyToOne?= =?UTF-8?q?=20->=20OneToOne=20=EA=B4=80=EA=B3=84=20=EB=B3=80=EA=B2=BD=20DP?= =?UTF-8?q?-176?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/chat/domain/ChatMessageReference.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/domain/ChatMessageReference.java b/src/main/java/com/deepdirect/deepwebide_be/chat/domain/ChatMessageReference.java index 4049dec..7a4aad1 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/domain/ChatMessageReference.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/domain/ChatMessageReference.java @@ -16,11 +16,11 @@ public class ChatMessageReference { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "chat_message_id", nullable = false) private ChatMessage chatMessage; - @Column(name = "path", nullable = true) + @Column(name = "path", nullable = false) private String path; @Column(name = "created_at") From 26d8d5b6eb1631b7d142c10d20eb5cc834bf0dba Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Fri, 1 Aug 2025 00:06:22 +0900 Subject: [PATCH 05/20] =?UTF-8?q?fix(service):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=88=20=EC=95=88=20=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=8F=99=EC=9E=91=20|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepdirect/deepwebide_be/member/service/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 26ac8df..82d7b20 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 @@ -118,7 +118,7 @@ public SignInResponse signIn(SignInRequest request, HttpServletResponse servletR .orElseThrow(() -> new GlobalException(ErrorCode.WRONG_PASSWORD)); if (!user.isEmailVerified()) { - throw new GlobalException(ErrorCode.EMAIL_NOT_VERIFIED); + emailVerificationService.handleEmailVerification(user.getEmail()); } if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { From be0b0529ef688be4951683a43b41ccba1d2319c4 Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Fri, 1 Aug 2025 00:08:38 +0900 Subject: [PATCH 06/20] =?UTF-8?q?fix(repository):=20=EA=B0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=B5=9C=EA=B7=BC=20=EB=B0=9C=EC=86=A1=EB=90=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/EmailVerificationRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/repository/EmailVerificationRepository.java b/src/main/java/com/deepdirect/deepwebide_be/member/repository/EmailVerificationRepository.java index 263ed76..2ddfeef 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/repository/EmailVerificationRepository.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/repository/EmailVerificationRepository.java @@ -1,6 +1,7 @@ package com.deepdirect.deepwebide_be.member.repository; import com.deepdirect.deepwebide_be.member.domain.EmailVerification; +import org.springframework.data.domain.Limit; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -10,4 +11,6 @@ public interface EmailVerificationRepository extends JpaRepository findByEmailCode(String code); + + Optional findTopByEmailOrderByCreatedAtDesc(String email); } From 35f448e09d80cd5cb019b19d6634e66e8d59234a Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Fri, 1 Aug 2025 00:11:35 +0900 Subject: [PATCH 07/20] =?UTF-8?q?fix(service):=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B0=9C=EC=86=A1=20=EB=B0=8F=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EB=B0=98=ED=99=98=20|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/exception/ErrorCode.java | 1 + .../service/EmailVerificationService.java | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index cd6f688..0d70abc 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -49,6 +49,7 @@ public enum ErrorCode { INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), MISSING_TOKEN(HttpStatus.UNAUTHORIZED, "Access Token이 누락되었습니다."), EMAIL_NOT_VERIFIED(HttpStatus.UNAUTHORIZED, "이메일 인증이 완료되지 않았습니다."), + EMAIL_NOT_VERIFIED_CODE_RESENT(HttpStatus.UNAUTHORIZED, "인증 코드가 만료되어 새로운 인증 이메일을 발송했습니다."), // 403 FORBIDDEN 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 84ed379..95e6031 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 @@ -1,5 +1,7 @@ package com.deepdirect.deepwebide_be.member.service; +import com.deepdirect.deepwebide_be.global.exception.ErrorCode; +import com.deepdirect.deepwebide_be.global.exception.GlobalException; import com.deepdirect.deepwebide_be.member.domain.EmailVerification; import com.deepdirect.deepwebide_be.member.repository.EmailVerificationRepository; import lombok.RequiredArgsConstructor; @@ -7,8 +9,10 @@ import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.Optional; import java.util.UUID; @Service @@ -19,6 +23,7 @@ public class EmailVerificationService { private final EmailVerificationRepository emailVerificationRepository; // 인증 코드 생성 및 저장 + @Transactional public String createVerification(String email) { // 코드 생성 String code = UUID.randomUUID().toString(); @@ -69,4 +74,24 @@ public String findVerifiedEmailByCode(String code) { .map(EmailVerification::getEmail) .orElseThrow(() -> new IllegalArgumentException("해당 코드로 등록된 이메일이 없습니다.")); } + + @Transactional + public void handleEmailVerification(String email) { + Optional latestVerification = emailVerificationRepository.findTopByEmailOrderByCreatedAtDesc(email); + + if (latestVerification.isPresent()) { + EmailVerification verification = latestVerification.get(); + + // 인증 코드가 아직 유효한 경우 + if (verification.getExpiresAt().isAfter(LocalDateTime.now())) { + throw new GlobalException(ErrorCode.EMAIL_NOT_VERIFIED); + } + } + + // 새로운 인증 코드 발송 + String newCode = createVerification(email); + sendVerificationEmail(email, newCode); + + throw new GlobalException(ErrorCode.EMAIL_NOT_VERIFIED_CODE_RESENT); + } } From 834b6f0f151ea489d45ebf4d31cecadaa08a5290 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 00:13:29 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat(ErrorCode):=20FILE=5FUPLOAD=5FFAIL?= =?UTF-8?q?=20error=20code=20=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepdirect/deepwebide_be/global/exception/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index 3ac53a9..c21f9de 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -41,6 +41,7 @@ public enum ErrorCode { UNSUPPORTED_REPOSITORY_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 레포지토리 타입입니다."), REPOSITORY_FILES_NOT_FOUND(HttpStatus.BAD_REQUEST, "레포지토리 파일을 찾을 수 없습니다."), FILE_TREE_CONVERSION_FAILED(HttpStatus.BAD_REQUEST, "파일 트리 변환에 실패했습니다."), + FILE_UPLOAD_FAIL(HttpStatus.BAD_REQUEST, "파일 업로드에 실패했습니다."), // 401 UNAUTHORIZED UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증되지 않았습니다."), From a2a1cffbb06ac9345e60bb8666950a7aef68f3a9 Mon Sep 17 00:00:00 2001 From: sunsetkk Date: Fri, 1 Aug 2025 00:32:54 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feature(chat):=20references=20->=20refere?= =?UTF-8?q?nce=20=EB=8B=A8=EC=9D=BC=20=EC=B0=B8=EC=A1=B0=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20DP-176?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/ChatMessageResponse.java | 5 ++-- .../chat/service/ChatMessageService.java | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageResponse.java b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageResponse.java index 4d83e9e..87221b3 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageResponse.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageResponse.java @@ -5,7 +5,6 @@ import lombok.Getter; import java.time.LocalDateTime; -import java.util.List; @Getter @Builder @@ -27,8 +26,8 @@ public class ChatMessageResponse { @Schema(description = "메시지 본문") private final String message; - @Schema(description = "코드 참조 목록") - private final List codeReferences; + @Schema(description = "코드 참조") + private final CodeReferenceResponse codeReference; @Schema(description = "내 메시지 여부", name = "IsMine") private final boolean isMine; 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 8c6ebd4..3433cf8 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 @@ -26,6 +26,8 @@ import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; @Service @@ -75,10 +77,14 @@ public ChatMessagesResponse getMessages(Long repositoryId, Long userId, Long bef // 참조 메시지 조회 List messageIds = messages.stream().map(ChatMessage::getId).toList(); - Map> referenceMap = referenceRepository + Map referenceMap = referenceRepository .findByChatMessageIdIn(messageIds) .stream() - .collect(Collectors.groupingBy(ref -> ref.getChatMessage().getId())); + .collect(Collectors.toMap( + ref -> ref.getChatMessage().getId(), + Function.identity() + )); + // 응답 변환 List responses = messages.stream() @@ -88,11 +94,9 @@ public ChatMessagesResponse getMessages(Long repositoryId, Long userId, Long bef .senderNickname(msg.getSender().getNickname()) .senderProfileImageUrl(msg.getSender().getProfileImageUrl()) .message(msg.getMessage()) - .codeReferences(referenceMap - .getOrDefault(msg.getId(), List.of()) - .stream() + .codeReference(Optional.ofNullable(referenceMap.get(msg.getId())) .map(CodeReferenceResponse::from) - .toList()) + .orElse(null)) .isMine(msg.getSender().getId().equals(userId)) .sentAt(msg.getSentAt()) .build()) @@ -121,9 +125,11 @@ public ChatMessageSearchResponse searchMessages(Long repositoryId, Long userId, long total = chatMessageRepository.countByRepositoryIdAndMessageContainingIgnoreCase(repositoryId, keyword); - Map> ref = referenceRepository + Map ref = referenceRepository .findByChatMessageIdIn(result.stream().map(ChatMessage::getId).toList()) - .stream().collect(Collectors.groupingBy(r -> r.getChatMessage().getId())); + .stream() + .collect(Collectors.toMap(r -> r.getChatMessage().getId(), Function.identity() + )); List responses = result.stream().map(msg -> ChatMessageResponse.builder() @@ -132,9 +138,9 @@ public ChatMessageSearchResponse searchMessages(Long repositoryId, Long userId, .senderNickname(msg.getSender().getNickname()) .senderProfileImageUrl(msg.getSender().getProfileImageUrl()) .message(msg.getMessage()) - .codeReferences(ref.getOrDefault(msg.getId(), List.of()) - .stream().map(CodeReferenceResponse::from) - .toList()) + .codeReference(Optional.ofNullable(ref.get(msg.getId())) + .map(CodeReferenceResponse::from) + .orElse(null)) .isMine(msg.getSender().getId().equals(userId)) .sentAt(msg.getSentAt()) .build() From f5f5d9cd6889923fe8eea578ee5c9f32ba79a2b8 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 00:40:47 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat(FileUpload):=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/controller/FileController.java | 13 ++++ .../file/service/FileService.java | 61 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java b/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java index d6990ff..09bfdd2 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java @@ -14,6 +14,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -111,4 +112,16 @@ public ResponseEntity> saveFileContent( ); return ResponseEntity.ok(ApiResponseDto.of(200, "파일 내용 저장 완료했습니다.", response)); } + + @Operation(summary = "파일 업로드", description = "단일 파일을 업로드합니다.") + @PostMapping("/{repositoryId}/files/upload") + public ResponseEntity> uploadFile( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long repositoryId, + @RequestParam("parentId") Long parentId, + @RequestParam("file") MultipartFile file + ) { + FileNodeResponse response = fileService.uploadFile(repositoryId, userDetails.getId(), parentId, file); + return ResponseEntity.ok(ApiResponseDto.of(201, "파일 업로드 완료", response)); + } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java index 0592cfd..eb236b8 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java @@ -14,7 +14,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.*; @RequiredArgsConstructor @@ -302,4 +304,63 @@ private FileNode findFileNodeWithRepositoryCheck(Long repositoryId, Long fileId) } return fileNode; } + + + @Transactional + public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId, MultipartFile file) { + // 1. 권한/레포 체크 + Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) + .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); + + // 2. 부모 폴더 체크 + FileNode parent = null; + String parentPath = ""; + if (parentId != null) { + parent = findFileNodeWithRepositoryCheck(repositoryId, parentId); + if (!parent.getFileType().equals(FileType.FOLDER)) { + throw new GlobalException(ErrorCode.INVALID_PARENT_TYPE); + } + parentPath = parent.getPath(); + } + + // 3. 중복 이름 체크 + if (fileNodeRepository.existsByRepositoryIdAndParentIdAndName( + repositoryId, parentId, file.getOriginalFilename())) { + throw new GlobalException(ErrorCode.DUPLICATE_FILE_NAME); + } + + // 4. 경로 계산 + String newPath = parentPath.isEmpty() ? file.getOriginalFilename() : parentPath + "/" + file.getOriginalFilename(); + + // 5. FileNode 생성 + FileNode fileNode = FileNode.builder() + .repository(repo) + .name(file.getOriginalFilename()) + .fileType(FileType.FILE) + .parent(parent) + .path(newPath) + .build(); + fileNode = fileNodeRepository.save(fileNode); + + // 6. 파일 내용 저장 + try { + FileContent content = FileContent.builder() + .fileNode(fileNode) + .content(file.getBytes()) + .build(); + fileContentRepository.save(content); + } catch (IOException e) { + throw new GlobalException(ErrorCode.FILE_UPLOAD_FAIL); + } + + // 7. 응답 반환 + return FileNodeResponse.builder() + .fileId(fileNode.getId()) + .fileName(fileNode.getName()) + .fileType("FILE") + .parentId(parent == null ? null : parent.getId()) + .path(fileNode.getPath()) + .build(); + } + } From 9601f05668c0dea0f79fc24da172effcdba2dd6d Mon Sep 17 00:00:00 2001 From: Shin-Yu-1 Date: Fri, 1 Aug 2025 00:43:55 +0900 Subject: [PATCH 11/20] =?UTF-8?q?fix(domain):=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=9C=A0=EB=8B=88=ED=81=AC=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?|=20DP-171?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/member/domain/EmailVerification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/domain/EmailVerification.java b/src/main/java/com/deepdirect/deepwebide_be/member/domain/EmailVerification.java index 9798597..a60c61c 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/domain/EmailVerification.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/domain/EmailVerification.java @@ -21,7 +21,7 @@ public class EmailVerification { @Column(nullable = false) private String emailCode; - @Column(nullable = false, unique = true) + @Column(nullable = false) private String email; @Column(nullable = false) From ad789dd8c95528c7c1ce4b9da1fbe0f9ea77b1bf Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 00:50:47 +0900 Subject: [PATCH 12/20] =?UTF-8?q?feat(ErrorCode):=20PARENT=5FID=5FREQUIRED?= =?UTF-8?q?=20error=20code=20=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepdirect/deepwebide_be/global/exception/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index c21f9de..0ee58a0 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -42,6 +42,7 @@ public enum ErrorCode { REPOSITORY_FILES_NOT_FOUND(HttpStatus.BAD_REQUEST, "레포지토리 파일을 찾을 수 없습니다."), FILE_TREE_CONVERSION_FAILED(HttpStatus.BAD_REQUEST, "파일 트리 변환에 실패했습니다."), FILE_UPLOAD_FAIL(HttpStatus.BAD_REQUEST, "파일 업로드에 실패했습니다."), + PARENT_ID_REQUIRED(HttpStatus.BAD_REQUEST, "부모 폴더 ID는 필수입니다."), // 401 UNAUTHORIZED UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증되지 않았습니다."), From 8063e11e93c1d6e012c8ebc911e1be7fda8ae956 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 00:55:39 +0900 Subject: [PATCH 13/20] =?UTF-8?q?feat(Validation):=20=EB=B6=80=EB=AA=A8=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20ID=EC=99=80=20=EC=9D=B4=EB=8F=99=ED=95=A0?= =?UTF-8?q?=20=ED=8F=B4=EB=8D=94=20ID=20=ED=95=84=EC=88=98=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/file/dto/request/FileCreateRequest.java | 6 ++++-- .../deepwebide_be/file/dto/request/FileMoveRequest.java | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileCreateRequest.java b/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileCreateRequest.java index c1b555c..ef819b0 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileCreateRequest.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileCreateRequest.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter @@ -16,6 +17,7 @@ public class FileCreateRequest { @NotBlank(message = "파일 타입은 필수입니다.") private String fileType; - @Schema(description = "부모 폴더 ID (최상위면 null)", example = "1") - private Long parentId; // null 허용 -> 검증 X + @Schema(description = "부모 폴더 ID (필수)", example = "1") + @NotNull(message = "부모 폴더 ID는 필수입니다.") // 추가 + private Long parentId; } diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileMoveRequest.java b/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileMoveRequest.java index 3fba679..995d5b8 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileMoveRequest.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/dto/request/FileMoveRequest.java @@ -1,6 +1,7 @@ package com.deepdirect.deepwebide_be.file.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @@ -9,7 +10,7 @@ @Schema(description = "파일 또는 폴더 이동 요청 DTO") public class FileMoveRequest { - @Schema(description = "이동할 폴더 ID (최상위면 null) ", example = "1") + @Schema(description = "이동할 폴더 ID (필수) ", example = "1") + @NotNull(message = "이동할 폴더 ID는 필수입니다.") // 추가 private Long newParentId; - } From 4382dcebf56401ded6d5858c188eb6b045058988 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 00:56:08 +0900 Subject: [PATCH 14/20] =?UTF-8?q?feat(Validation):=20parentId=EB=A5=BC=20?= =?UTF-8?q?=ED=95=84=EC=88=98=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/controller/FileController.java | 4 +- .../file/service/FileService.java | 46 +++++++++++-------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java b/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java index 09bfdd2..32e9d77 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/controller/FileController.java @@ -10,6 +10,7 @@ import com.deepdirect.deepwebide_be.global.security.CustomUserDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -113,12 +114,11 @@ public ResponseEntity> saveFileContent( return ResponseEntity.ok(ApiResponseDto.of(200, "파일 내용 저장 완료했습니다.", response)); } - @Operation(summary = "파일 업로드", description = "단일 파일을 업로드합니다.") @PostMapping("/{repositoryId}/files/upload") public ResponseEntity> uploadFile( @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long repositoryId, - @RequestParam("parentId") Long parentId, + @RequestParam("parentId") @NotNull Long parentId, // 필수 지정 @RequestParam("file") MultipartFile file ) { FileNodeResponse response = fileService.uploadFile(repositoryId, userDetails.getId(), parentId, file); diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java index eb236b8..9292b96 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java @@ -65,6 +65,10 @@ public List getFileTree(Long repositoryId, Long userId) { @Transactional public FileNodeResponse createFileOrFolder(Long repositoryId, Long userId, FileCreateRequest req) { + + if (req.getParentId() == null) { + throw new GlobalException(ErrorCode.PARENT_ID_REQUIRED); + } // 1. 레포 권한 체크 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); @@ -192,6 +196,11 @@ private void deleteChildrenRecursively(FileNode parent) { @Transactional public FileNodeResponse moveFileOrFolder( Long repositoryId, Long fileId, Long userId, Long newParentId) { + + if (newParentId == null) { + throw new GlobalException(ErrorCode.PARENT_ID_REQUIRED); + } + repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); FileNode fileNode = findFileNodeWithRepositoryCheck(repositoryId, fileId); @@ -199,17 +208,15 @@ public FileNodeResponse moveFileOrFolder( // 새 부모 폴더 체크 FileNode newParent = null; String newParentPath = ""; - if (newParentId != null) { - newParent = findFileNodeWithRepositoryCheck(repositoryId, newParentId); - if (!newParent.isFolder()) { - throw new GlobalException(ErrorCode.INVALID_PARENT_TYPE); - } - // 순환구조 방지 (본인 또는 하위로 이동 불가) - if (isDescendant(fileNode, newParent)) { - throw new GlobalException(ErrorCode.CANNOT_MOVE_TO_CHILD); - } - newParentPath = newParent.getPath(); + newParent = findFileNodeWithRepositoryCheck(repositoryId, newParentId); + if (!newParent.isFolder()) { + throw new GlobalException(ErrorCode.INVALID_PARENT_TYPE); + } + // 순환구조 방지 (본인 또는 하위로 이동 불가) + if (isDescendant(fileNode, newParent)) { + throw new GlobalException(ErrorCode.CANNOT_MOVE_TO_CHILD); } + newParentPath = newParent.getPath(); // 같은 폴더에 동일 이름 체크 if (fileNodeRepository.existsByRepositoryIdAndParentAndName( @@ -228,7 +235,7 @@ public FileNodeResponse moveFileOrFolder( .fileId(fileNode.getId()) .fileName(fileNode.getName()) .fileType(fileNode.getFileType().name()) - .parentId(newParent == null ? null : newParent.getId()) + .parentId(newParent.getId()) .path(fileNode.getPath()) .build(); } @@ -308,6 +315,11 @@ private FileNode findFileNodeWithRepositoryCheck(Long repositoryId, Long fileId) @Transactional public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId, MultipartFile file) { + + if (parentId == null) { + throw new GlobalException(ErrorCode.PARENT_ID_REQUIRED); + } + // 1. 권한/레포 체크 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); @@ -315,13 +327,11 @@ public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId // 2. 부모 폴더 체크 FileNode parent = null; String parentPath = ""; - if (parentId != null) { - parent = findFileNodeWithRepositoryCheck(repositoryId, parentId); - if (!parent.getFileType().equals(FileType.FOLDER)) { - throw new GlobalException(ErrorCode.INVALID_PARENT_TYPE); - } - parentPath = parent.getPath(); + parent = findFileNodeWithRepositoryCheck(repositoryId, parentId); + if (!parent.getFileType().equals(FileType.FOLDER)) { + throw new GlobalException(ErrorCode.INVALID_PARENT_TYPE); } + parentPath = parent.getPath(); // 3. 중복 이름 체크 if (fileNodeRepository.existsByRepositoryIdAndParentIdAndName( @@ -358,7 +368,7 @@ public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId .fileId(fileNode.getId()) .fileName(fileNode.getName()) .fileType("FILE") - .parentId(parent == null ? null : parent.getId()) + .parentId(parent.getId()) .path(fileNode.getPath()) .build(); } From 7ef4ddba3c728a3009d42a24482e2fe120719126 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 01:21:34 +0900 Subject: [PATCH 15/20] =?UTF-8?q?feat(ErrorCode):=20FILE=5FEXTENSION=5FREQ?= =?UTF-8?q?UIRED=20error=20code=20=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepdirect/deepwebide_be/global/exception/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index 0ee58a0..8a030c3 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -43,6 +43,7 @@ public enum ErrorCode { FILE_TREE_CONVERSION_FAILED(HttpStatus.BAD_REQUEST, "파일 트리 변환에 실패했습니다."), FILE_UPLOAD_FAIL(HttpStatus.BAD_REQUEST, "파일 업로드에 실패했습니다."), PARENT_ID_REQUIRED(HttpStatus.BAD_REQUEST, "부모 폴더 ID는 필수입니다."), + FILE_EXTENSION_REQUIRED(HttpStatus.BAD_REQUEST, "파일 확장자는 필수입니다."), // 401 UNAUTHORIZED UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증되지 않았습니다."), From 4bb45d301d393822224461f2c43b88839fcc220b Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 01:21:44 +0900 Subject: [PATCH 16/20] =?UTF-8?q?feat(Validation):=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EC=97=90=20=ED=99=95=EC=9E=A5=EC=9E=90=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/service/FileService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java index 9292b96..b68666e 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java @@ -69,6 +69,11 @@ public FileNodeResponse createFileOrFolder(Long repositoryId, Long userId, FileC if (req.getParentId() == null) { throw new GlobalException(ErrorCode.PARENT_ID_REQUIRED); } + + if ("FILE".equals(req.getFileType())) { + validateFileNameHasExtension(req.getFileName()); + } + // 1. 레포 권한 체크 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); @@ -129,6 +134,10 @@ public FileRenameResponse renameFileOrFolder(Long repositoryId, Long fileId, Lon .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); FileNode fileNode = findFileNodeWithRepositoryCheck(repositoryId, fileId); + if (fileNode.getFileType() == FileType.FILE) { + validateFileNameHasExtension(newFileName); + } + // 2. 같은 폴더 내에 동일 이름 존재 체크 Long parentId = (fileNode.getParent() == null) ? null : fileNode.getParent().getId(); boolean isDuplicate = fileNodeRepository.existsByRepositoryIdAndParentIdAndName( @@ -320,6 +329,10 @@ public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId throw new GlobalException(ErrorCode.PARENT_ID_REQUIRED); } + String fileName = file.getOriginalFilename(); + validateFileNameHasExtension(fileName); + + // 1. 권한/레포 체크 Repository repo = repositoryRepository.findByIdAndMemberOrOwner(repositoryId, userId) .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); @@ -373,4 +386,10 @@ public FileNodeResponse uploadFile(Long repositoryId, Long userId, Long parentId .build(); } + private void validateFileNameHasExtension(String fileName) { + if (fileName == null || !fileName.contains(".") || fileName.startsWith(".") || fileName.endsWith(".")) { + throw new GlobalException(ErrorCode.FILE_EXTENSION_REQUIRED); + } + } + } From a371f31868a193fdfe8301f581e3723749764024 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 01:44:19 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat(FileService):=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=ED=99=95=EC=9E=A5=EC=9E=90=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EB=B3=80=ED=99=98=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/file/service/FileService.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java index b68666e..dc994a4 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileService.java @@ -265,17 +265,26 @@ public FileContentResponse getFileContent(Long repositoryId, Long fileId, Long u .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); FileNode fileNode = findFileNodeWithRepositoryCheck(repositoryId, fileId); - // 폴더는 열 수 없음 if (fileNode.getFileType() == FileType.FOLDER) { throw new GlobalException(ErrorCode.CANNOT_OPEN_FOLDER); } - // 파일 내용 조회 FileContent fileContent = fileContentRepository.findByFileNode(fileNode) .orElseThrow(() -> new GlobalException(ErrorCode.FILE_CONTENT_NOT_FOUND)); - // byte[] → String 변환 (UTF-8) - String content = new String(fileContent.getContent(), java.nio.charset.StandardCharsets.UTF_8); + // 확장자 체크 + String fileName = fileNode.getName(); + String extension = ""; + int idx = fileName.lastIndexOf('.'); + if (idx > 0) extension = fileName.substring(idx + 1).toLowerCase(); + + String content; + // 이미지/바이너리면 Base64, 텍스트면 UTF-8 + if (List.of("png", "jpg", "jpeg", "gif", "svg").contains(extension)) { + content = Base64.getEncoder().encodeToString(fileContent.getContent()); + } else { + content = new String(fileContent.getContent(), java.nio.charset.StandardCharsets.UTF_8); + } return FileContentResponse.builder() .fileId(fileNode.getId()) From b005c80cd864f297d0d9854c83470cbaa3ef59db Mon Sep 17 00:00:00 2001 From: sunsetkk Date: Fri, 1 Aug 2025 03:13:58 +0900 Subject: [PATCH 18/20] =?UTF-8?q?chore(chat):=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=80=EC=9E=A5=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20DP-178?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/deepdirect/deepwebide_be/global/exception/ErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java index 3ac53a9..1c9a1fa 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java +++ b/src/main/java/com/deepdirect/deepwebide_be/global/exception/ErrorCode.java @@ -41,6 +41,7 @@ public enum ErrorCode { UNSUPPORTED_REPOSITORY_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 레포지토리 타입입니다."), REPOSITORY_FILES_NOT_FOUND(HttpStatus.BAD_REQUEST, "레포지토리 파일을 찾을 수 없습니다."), FILE_TREE_CONVERSION_FAILED(HttpStatus.BAD_REQUEST, "파일 트리 변환에 실패했습니다."), + EMPTY_CHAT_MESSAGE(HttpStatus.BAD_REQUEST, "메시지는 공백일 수 없습니다."), // 401 UNAUTHORIZED UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증되지 않았습니다."), From abf9c5913768271b62b45c89ac0bd82fe45059c4 Mon Sep 17 00:00:00 2001 From: sunsetkk Date: Fri, 1 Aug 2025 03:14:40 +0900 Subject: [PATCH 19/20] =?UTF-8?q?fix(chat):=20=EA=B3=B5=EB=B0=B1=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=B0=A9=EC=A7=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20DP-178?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/chat/dto/request/ChatMessageRequest.java | 2 ++ .../deepwebide_be/chat/service/ChatMessageWriteService.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/ChatMessageRequest.java b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/ChatMessageRequest.java index d28965d..b7ecd62 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/ChatMessageRequest.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/ChatMessageRequest.java @@ -3,6 +3,7 @@ import com.deepdirect.deepwebide_be.chat.domain.ChatMessageType; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,6 +13,7 @@ @Schema(description = "채팅 메시지 전송 요청") public class ChatMessageRequest { + @NotBlank(message = "메시지는 공백일 수 없습니다.") @Schema(description = "메시지 내용", example = "코드 이부분 이상한 것 같아") private String message; diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageWriteService.java b/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageWriteService.java index bfd9075..a475c33 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageWriteService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/service/ChatMessageWriteService.java @@ -45,6 +45,10 @@ public ChatMessageBroadcast saveChatMessage(Long userId, Long repositoryId, Stri User sender = userRepository.findById(userId) .orElseThrow(() -> new GlobalException(USER_NOT_FOUND)); + if (content == null || content.isEmpty()) { + throw new GlobalException(EMPTY_CHAT_MESSAGE); + } + ChatMessage chatMessage = chatMessageRepository.save( ChatMessage.of(repository, sender, content) ); From bf0e42091aac1034b8d4e865b389e1883079acbe Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Fri, 1 Aug 2025 12:29:33 +0900 Subject: [PATCH 20/20] =?UTF-8?q?feat(FileService):=2030=EC=B4=88=EB=A7=88?= =?UTF-8?q?=EB=8B=A4=20=EC=A0=80=EC=9E=A5=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20DP-177?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deepwebide_be/file/service/FileContentSyncService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileContentSyncService.java b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileContentSyncService.java index 7be689b..2973eb9 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/file/service/FileContentSyncService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/file/service/FileContentSyncService.java @@ -22,7 +22,7 @@ public class FileContentSyncService { private final FileContentRepository fileContentRepository; private final FileNodeRepository fileNodeRepository; - @Scheduled(fixedRate = 60000) // 1분마다 실행 + @Scheduled(fixedRate = 30000) // 30초마다 실행 public void syncRedisToDb() { Set dirtyFileKeys = redisTemplate.opsForSet().members(DIRTY_SET_KEY); if (dirtyFileKeys == null) return;