diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/controller/ChatWebSocketController.java b/src/main/java/com/deepdirect/deepwebide_be/chat/controller/ChatWebSocketController.java index a3ee0017..852fe647 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/controller/ChatWebSocketController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/controller/ChatWebSocketController.java @@ -5,7 +5,6 @@ import com.deepdirect.deepwebide_be.chat.service.ChatMessageWriteService; import com.deepdirect.deepwebide_be.chat.util.RedisPublisher; import lombok.RequiredArgsConstructor; -import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.stereotype.Controller; @@ -19,17 +18,17 @@ public class ChatWebSocketController { @MessageMapping("/repositories/{repositoryId}/chat/send") public void sendMessage( - @DestinationVariable Long repositoryId, ChatMessageRequest request, SimpMessageHeaderAccessor headerAccessor ) { + // 1. WebSocket 세션에서 userId 추출 Long userId = (Long) headerAccessor.getSessionAttributes().get("userId"); // 2. 메시지 저장 + DTO 응답 변환 - ChatMessageBroadcast broadcast = chatMessageWriteService.saveChatMessage(userId, repositoryId, request.getMessage()); + ChatMessageBroadcast broadcast = chatMessageWriteService.saveChatMessage(userId, request); // 3. Redis 채널로 publish - redisPublisher.publish("chatroom:" + repositoryId, broadcast); + redisPublisher.publish("chatroom:" + request.getRepositoryId(), broadcast); } } \ No newline at end of file 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 7a4aad1e..a1411737 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 @@ -1,9 +1,7 @@ package com.deepdirect.deepwebide_be.chat.domain; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; @@ -11,6 +9,8 @@ @Table(name = "chat_message_references") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder public class ChatMessageReference { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) 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 b7ecd625..a560b0a8 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 @@ -21,9 +21,17 @@ public class ChatMessageRequest { @JsonProperty("type") private ChatMessageType type; + @Schema(description = "레포지토리 ID") + private Long repositoryId; + + @Schema(description = "코드 참조 정보") + private CodeReferenceRequest codeReference; + @Builder - public ChatMessageRequest(String message, ChatMessageType type) { + public ChatMessageRequest(String message, ChatMessageType type, Long repositoryId, CodeReferenceRequest codeReference) { this.message = message; this.type = type; + this.repositoryId = repositoryId; + this.codeReference = codeReference; } } diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/CodeReferenceRequest.java b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/CodeReferenceRequest.java new file mode 100644 index 00000000..f90fa0da --- /dev/null +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/request/CodeReferenceRequest.java @@ -0,0 +1,16 @@ +package com.deepdirect.deepwebide_be.chat.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "코드 참조 요청 정보") +public class CodeReferenceRequest { + + private Long referenceId; + private String path; +} diff --git a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageBroadcast.java b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageBroadcast.java index d03eea82..05049e0e 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageBroadcast.java +++ b/src/main/java/com/deepdirect/deepwebide_be/chat/dto/response/ChatMessageBroadcast.java @@ -35,6 +35,9 @@ public class ChatMessageBroadcast { @Schema(description = "메시지 내용") private String message; + @Schema(description = "코드 참조 정보", nullable = true) + private CodeReferenceResponse codeReference; + @Schema(description = "보낸 시간") private LocalDateTime sentAt; @@ -42,7 +45,7 @@ public class ChatMessageBroadcast { private boolean isMine; - public static ChatMessageBroadcast of(ChatMessage message, User sender, Long repositoryId) { + public static ChatMessageBroadcast of(ChatMessage message, User sender, Long repositoryId, CodeReferenceResponse codeReference) { return ChatMessageBroadcast.builder() .repositoryId(repositoryId) .type(ChatMessageType.CHAT) //일반 채팅 @@ -51,8 +54,9 @@ public static ChatMessageBroadcast of(ChatMessage message, User sender, Long rep .senderNickname(sender.getNickname()) .senderProfileImageUrl(sender.getProfileImageUrl()) .message(message.getMessage()) + .codeReference(codeReference) .sentAt(message.getSentAt()) - .isMine(true) + .isMine(false) .build(); } @@ -65,6 +69,7 @@ public static ChatMessageBroadcast system(User user, ChatMessageType type, Strin .senderNickname(user.getNickname()) .senderProfileImageUrl(user.getProfileImageUrl()) .message(message) + .codeReference(null) .sentAt(LocalDateTime.now()) .isMine(false) .build(); 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 a475c333..1710e41f 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 @@ -2,7 +2,10 @@ import com.deepdirect.deepwebide_be.chat.domain.ChatMessage; import com.deepdirect.deepwebide_be.chat.domain.ChatMessageReference; +import com.deepdirect.deepwebide_be.chat.dto.request.ChatMessageRequest; +import com.deepdirect.deepwebide_be.chat.dto.request.CodeReferenceRequest; import com.deepdirect.deepwebide_be.chat.dto.response.ChatMessageBroadcast; +import com.deepdirect.deepwebide_be.chat.dto.response.CodeReferenceResponse; import com.deepdirect.deepwebide_be.chat.repository.ChatMessageReferenceRepository; import com.deepdirect.deepwebide_be.chat.repository.ChatMessageRepository; import com.deepdirect.deepwebide_be.global.exception.GlobalException; @@ -30,7 +33,9 @@ public class ChatMessageWriteService { private final ChatMessageReferenceRepository referenceRepository; @Transactional - public ChatMessageBroadcast saveChatMessage(Long userId, Long repositoryId, String content) { + public ChatMessageBroadcast saveChatMessage(Long userId, ChatMessageRequest request) { + Long repositoryId = request.getRepositoryId(); + Repository repository = repositoryRepository.findByIdAndDeletedAtIsNull(repositoryId) .orElseThrow(() -> new GlobalException(REPOSITORY_NOT_FOUND)); @@ -45,14 +50,29 @@ public ChatMessageBroadcast saveChatMessage(Long userId, Long repositoryId, Stri User sender = userRepository.findById(userId) .orElseThrow(() -> new GlobalException(USER_NOT_FOUND)); + String content = request.getMessage(); if (content == null || content.isEmpty()) { throw new GlobalException(EMPTY_CHAT_MESSAGE); } + // 1. 메시지 저장 ChatMessage chatMessage = chatMessageRepository.save( ChatMessage.of(repository, sender, content) ); - return ChatMessageBroadcast.of(chatMessage, sender, repositoryId); + // 2. 참조 저장 (있으면) + CodeReferenceRequest refRequest = request.getCodeReference(); + ChatMessageReference reference = null; + if (refRequest != null && refRequest.getPath() != null) { + reference = referenceRepository.save(ChatMessageReference.builder() + .chatMessage(chatMessage) + .path(refRequest.getPath()) + .build()); + } + + // 3. 응답 DTO 변환 + CodeReferenceResponse codeReferenceResponse = reference != null ? CodeReferenceResponse.from(reference) : null; + + return ChatMessageBroadcast.of(chatMessage, sender, repositoryId, codeReferenceResponse); } -} \ No newline at end of file +} 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 7bb9ae7f..226c4c91 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 { UNSUPPORTED_REPOSITORY_TYPE(HttpStatus.BAD_REQUEST, "지원하지 않는 레포지토리 타입입니다."), REPOSITORY_FILES_NOT_FOUND(HttpStatus.BAD_REQUEST, "레포지토리 파일을 찾을 수 없습니다."), FILE_TREE_CONVERSION_FAILED(HttpStatus.BAD_REQUEST, "파일 트리 변환에 실패했습니다."), + INVALID_PHONE_FORMAT(HttpStatus.BAD_REQUEST, "올바른 휴대전화 번호 형식이 아닙니다."), FILE_UPLOAD_FAIL(HttpStatus.BAD_REQUEST, "파일 업로드에 실패했습니다."), PARENT_ID_REQUIRED(HttpStatus.BAD_REQUEST, "부모 폴더 ID는 필수입니다."), FILE_EXTENSION_REQUIRED(HttpStatus.BAD_REQUEST, "파일 확장자는 필수입니다."), diff --git a/src/main/java/com/deepdirect/deepwebide_be/member/service/PhoneVerificationService.java b/src/main/java/com/deepdirect/deepwebide_be/member/service/PhoneVerificationService.java index 84bbf64e..02ea55ed 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/member/service/PhoneVerificationService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/member/service/PhoneVerificationService.java @@ -16,6 +16,7 @@ import java.time.LocalDateTime; import java.util.Random; +import java.util.regex.Pattern; @Service @RequiredArgsConstructor @@ -55,7 +56,13 @@ private String generateRandomNumber() { } // 문자 발송 + public int sendVerificationCode(String phoneNumber, String reqUserName, AuthType reqAuthType) { + // 휴대전화 번호 형식 정규식 (010으로 시작하고 숫자 총 10~11자리) + Pattern phonePattern = Pattern.compile("^01[0|1|6|7|8|9][0-9]{7,8}$"); + if (!phonePattern.matcher(phoneNumber).matches()) { + throw new GlobalException(ErrorCode.INVALID_PHONE_FORMAT); + } if (reqAuthType == AuthType.SIGN_UP && isUserExists(phoneNumber, reqUserName)) { throw new GlobalException(ErrorCode.DUPLICATE_NAME_AND_PHONE); @@ -74,7 +81,6 @@ public int sendVerificationCode(String phoneNumber, String reqUserName, AuthType throw new GlobalException(ErrorCode.SMS_SEND_FAILED); } - // 인증 정보 저장 phoneVerificationRepository.save( PhoneVerification.builder() .phoneNumber(phoneNumber) 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 c58f5466..bec03265 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 @@ -86,7 +86,10 @@ public SignUpResponse signup(SignUpRequest request) { if (userRepository.existsByEmail(request.getEmail())) { throw new GlobalException(ErrorCode.DUPLICATE_EMAIL); } - + Pattern phonePattern = Pattern.compile("^01[0|1|6|7|8|9][0-9]{7,8}$"); + if (!phonePattern.matcher(request.getPhoneNumber()).matches()) { + throw new GlobalException(ErrorCode.INVALID_PHONE_FORMAT); + } if (userRepository.existsByPhoneNumber(request.getPhoneNumber())) { throw new GlobalException(ErrorCode.PHONE_NUMBER_ALREADY_USED); }