diff --git a/build.gradle b/build.gradle index abd15c8..6c9b4a4 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.session:spring-session-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-validation' + // p6spy 의존성 추가 - SQL 로그 확인 implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' diff --git a/src/main/java/econo/buddybridge/chat/chatmessage/exception/ChatMessageErrorCode.java b/src/main/java/econo/buddybridge/chat/chatmessage/exception/ChatMessageErrorCode.java new file mode 100644 index 0000000..1b303d2 --- /dev/null +++ b/src/main/java/econo/buddybridge/chat/chatmessage/exception/ChatMessageErrorCode.java @@ -0,0 +1,34 @@ +package econo.buddybridge.chat.chatmessage.exception; + +import econo.buddybridge.common.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +public enum ChatMessageErrorCode implements ErrorCode { + LAST_CHAT_MESSAGE_NOT_FOUND("CH01", HttpStatus.NOT_FOUND, "마지막 메시지가 존재하지 않습니다."), + ; + + private final String code; + private final HttpStatus httpStatus; + private final String message; + + ChatMessageErrorCode(String code, HttpStatus httpStatus, String message) { + this.code = code; + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/econo/buddybridge/chat/chatmessage/exception/LastChatMessageNotFoundException.java b/src/main/java/econo/buddybridge/chat/chatmessage/exception/LastChatMessageNotFoundException.java new file mode 100644 index 0000000..d891368 --- /dev/null +++ b/src/main/java/econo/buddybridge/chat/chatmessage/exception/LastChatMessageNotFoundException.java @@ -0,0 +1,10 @@ +package econo.buddybridge.chat.chatmessage.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class LastChatMessageNotFoundException extends BusinessException { + + public static BusinessException EXCEPTION = new LastChatMessageNotFoundException(); + + public LastChatMessageNotFoundException() { super(ChatMessageErrorCode.LAST_CHAT_MESSAGE_NOT_FOUND); } +} diff --git a/src/main/java/econo/buddybridge/chat/chatmessage/service/ChatMessageService.java b/src/main/java/econo/buddybridge/chat/chatmessage/service/ChatMessageService.java index 686209f..62fdde7 100644 --- a/src/main/java/econo/buddybridge/chat/chatmessage/service/ChatMessageService.java +++ b/src/main/java/econo/buddybridge/chat/chatmessage/service/ChatMessageService.java @@ -3,34 +3,36 @@ import econo.buddybridge.chat.chatmessage.dto.ChatMessageReqDto; import econo.buddybridge.chat.chatmessage.dto.ChatMessageResDto; import econo.buddybridge.chat.chatmessage.entity.ChatMessage; +import econo.buddybridge.chat.chatmessage.exception.LastChatMessageNotFoundException; import econo.buddybridge.chat.chatmessage.repository.ChatMessageRepository; import econo.buddybridge.matching.entity.Matching; -import econo.buddybridge.matching.repository.MatchingRepository; +import econo.buddybridge.matching.exception.MatchingUnauthorizedAccessException; +import econo.buddybridge.matching.service.MatchingService; import econo.buddybridge.member.entity.Member; import econo.buddybridge.member.service.MemberService; import econo.buddybridge.notification.entity.NotificationType; import econo.buddybridge.notification.service.EmitterService; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor public class ChatMessageService { private final MemberService memberService; - private final MatchingRepository matchingRepository; private final ChatMessageRepository chatMessageRepository; private final EmitterService emitterService; + private final MatchingService matchingService; @Transactional // 메시지 저장 public ChatMessageResDto save(Long senderId, ChatMessageReqDto chatMessageReqDto, Long matchingId) { Member sender = memberService.findMemberByIdOrThrow(senderId); - Matching matching = matchingRepository.findById(matchingId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭입니다.")); + Matching matching = matchingService.findMatchingByIdOrThrow(matchingId); ChatMessage chatMessage = ChatMessage.builder() .matching(matching) @@ -61,15 +63,14 @@ public ChatMessageResDto save(Long senderId, ChatMessageReqDto chatMessageReqDto } private Long getReceiverId(Long senderId, Long matchingId) { - Matching matching = matchingRepository.findById(matchingId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭입니다.")); + Matching matching = matchingService.findMatchingByIdOrThrow(matchingId); if (matching.getGiver().getId().equals(senderId)) { return matching.getTaker().getId(); } else if (matching.getTaker().getId().equals(senderId)) { return matching.getGiver().getId(); } else { - throw new IllegalArgumentException("매칭에 속하지 않은 사용자입니다."); + throw MatchingUnauthorizedAccessException.EXCEPTION; } } @@ -77,7 +78,7 @@ private Long getReceiverId(Long senderId, Long matchingId) { public ChatMessageResDto getLastChatMessage(Long matchingId) { List chatMessageList = chatMessageRepository.findLastMessageByMatchingId(matchingId, PageRequest.of(0, 1)); if (chatMessageList.isEmpty()) { - throw new IllegalArgumentException("마지막 메시지가 존재하지 않습니다."); + throw LastChatMessageNotFoundException.EXCEPTION; } ChatMessage chatMessage = chatMessageList.getFirst(); diff --git a/src/main/java/econo/buddybridge/matching/exception/MatchingErrorCode.java b/src/main/java/econo/buddybridge/matching/exception/MatchingErrorCode.java new file mode 100644 index 0000000..6ebe947 --- /dev/null +++ b/src/main/java/econo/buddybridge/matching/exception/MatchingErrorCode.java @@ -0,0 +1,35 @@ +package econo.buddybridge.matching.exception; + +import econo.buddybridge.common.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +public enum MatchingErrorCode implements ErrorCode { + MATCHING_NOT_FOUND("MA001", HttpStatus.NOT_FOUND, "존재하지 않는 매칭입니다."), + MATCHING_UNAUTHORIZED_ACCESS("MA002", HttpStatus.FORBIDDEN, "사용자가 생성한 매칭방이 아닙니다.") + ; + + private final String code; + private final HttpStatus httpStatus; + private final String message; + + MatchingErrorCode(String code, HttpStatus httpStatus, String message) { + this.code = code; + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/econo/buddybridge/matching/exception/MatchingNotFoundException.java b/src/main/java/econo/buddybridge/matching/exception/MatchingNotFoundException.java new file mode 100644 index 0000000..61dfdd9 --- /dev/null +++ b/src/main/java/econo/buddybridge/matching/exception/MatchingNotFoundException.java @@ -0,0 +1,11 @@ +package econo.buddybridge.matching.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class MatchingNotFoundException extends BusinessException { + + public static BusinessException EXCEPTION = new MatchingNotFoundException(); + + private MatchingNotFoundException() { super(MatchingErrorCode.MATCHING_NOT_FOUND); } + +} diff --git a/src/main/java/econo/buddybridge/matching/exception/MatchingUnauthorizedAccessException.java b/src/main/java/econo/buddybridge/matching/exception/MatchingUnauthorizedAccessException.java new file mode 100644 index 0000000..22b190b --- /dev/null +++ b/src/main/java/econo/buddybridge/matching/exception/MatchingUnauthorizedAccessException.java @@ -0,0 +1,10 @@ +package econo.buddybridge.matching.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class MatchingUnauthorizedAccessException extends BusinessException { + + public static BusinessException EXCEPTION = new MatchingUnauthorizedAccessException(); + + public MatchingUnauthorizedAccessException() { super(MatchingErrorCode.MATCHING_UNAUTHORIZED_ACCESS); } +} diff --git a/src/main/java/econo/buddybridge/matching/service/MatchingRoomService.java b/src/main/java/econo/buddybridge/matching/service/MatchingRoomService.java index 4e0380f..cc58a76 100644 --- a/src/main/java/econo/buddybridge/matching/service/MatchingRoomService.java +++ b/src/main/java/econo/buddybridge/matching/service/MatchingRoomService.java @@ -8,13 +8,11 @@ import econo.buddybridge.matching.dto.ReceiverDto; import econo.buddybridge.matching.entity.Matching; import econo.buddybridge.matching.entity.MatchingStatus; -import econo.buddybridge.matching.repository.MatchingRepository; +import econo.buddybridge.matching.exception.MatchingUnauthorizedAccessException; import econo.buddybridge.matching.repository.MatchingRepositoryCustom; import econo.buddybridge.member.entity.Member; import econo.buddybridge.member.service.MemberService; import econo.buddybridge.post.entity.Post; -import java.time.LocalDateTime; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -22,14 +20,17 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.util.List; + @Service @RequiredArgsConstructor public class MatchingRoomService { private final MemberService memberService; - private final MatchingRepository matchingRepository; private final ChatMessageRepository chatMessageRepository; private final MatchingRepositoryCustom matchingRepositoryCustom; + private final MatchingService matchingService; @Transactional public MatchingCustomPage getMatchings(Long memberId, Integer size, LocalDateTime cursor, MatchingStatus matchingStatus){ @@ -41,11 +42,10 @@ public MatchingCustomPage getMatchings(Long memberId, Integer size, LocalDateTim public ChatMessageCustomPage getMatchingRoomMessages(Long memberId, Long matchingId, Integer size, Long cursor){ // 사용자 확인 // TODO: 예외처리 필요, 사용자가 매칭방에 속해있지 않을 경우 500 발생 - Matching matching = matchingRepository.findById(matchingId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭방입니다.")); + Matching matching = matchingService.findMatchingByIdOrThrow(matchingId); if (!matching.getGiver().getId().equals(memberId) && !matching.getTaker().getId().equals(memberId)){ - throw new IllegalArgumentException("사용자가 매칭방에 속해있지 않습니다."); + throw MatchingUnauthorizedAccessException.EXCEPTION; } Pageable pageable = PageRequest.of(0, size+1); diff --git a/src/main/java/econo/buddybridge/matching/service/MatchingService.java b/src/main/java/econo/buddybridge/matching/service/MatchingService.java index 01aeb1c..f2d873b 100644 --- a/src/main/java/econo/buddybridge/matching/service/MatchingService.java +++ b/src/main/java/econo/buddybridge/matching/service/MatchingService.java @@ -7,12 +7,14 @@ import econo.buddybridge.matching.dto.MatchingUpdateDto; import econo.buddybridge.matching.entity.Matching; import econo.buddybridge.matching.entity.MatchingStatus; +import econo.buddybridge.matching.exception.MatchingNotFoundException; import econo.buddybridge.matching.repository.MatchingRepository; import econo.buddybridge.member.entity.Member; import econo.buddybridge.member.service.MemberService; import econo.buddybridge.post.entity.Post; import econo.buddybridge.post.entity.PostType; -import econo.buddybridge.post.repository.PostRepository; +import econo.buddybridge.post.exception.PostUnauthorizedAccessException; +import econo.buddybridge.post.service.PostService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,13 +24,19 @@ public class MatchingService { private final ChatMessageRepository chatMessageRepository; private final MatchingRepository matchingRepository; - private final PostRepository postRepository; private final MemberService memberService; + private final PostService postService; - @Transactional // TODO: 매칭 생성 -> 예외처리 필요 + 댓글에서 사용자 정보 가져오기 고려 + // 존재하는 매칭인지 확인 + @Transactional(readOnly = true) + public Matching findMatchingByIdOrThrow(Long matchingId) { + return matchingRepository.findById(matchingId) + .orElseThrow(() -> MatchingNotFoundException.EXCEPTION); + } + + @Transactional public Long createMatchingById(MatchingReqDto matchingReqDto, Long memberId) { - Post post = postRepository.findById(matchingReqDto.postId()) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다.")); + Post post = postService.findPostByIdOrThrow(matchingReqDto.postId()); Member loginMember = memberService.findMemberByIdOrThrow(memberId); @@ -60,8 +68,8 @@ public Long createMatchingById(MatchingReqDto matchingReqDto, Long memberId) { @Transactional // 매칭 업데이트 public Long updateMatching(Long matchingId, MatchingUpdateDto matchingUpdateDto, Long memberId) { - Matching matching = matchingRepository.findById(matchingId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭입니다.")); + + Matching matching = findMatchingByIdOrThrow(matchingId); validatePostAuthor(matching.getPost(), memberId); @@ -72,9 +80,7 @@ public Long updateMatching(Long matchingId, MatchingUpdateDto matchingUpdateDto, @Transactional // 매칭 삭제 public void deleteMatching(Long matchingId, Long memberId) { - Matching matching = matchingRepository.findById(matchingId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭입니다.")); - + Matching matching = findMatchingByIdOrThrow(matchingId); validatePostAuthor(matching.getPost(), memberId); matchingRepository.delete(matching); @@ -94,7 +100,7 @@ private Matching matchingReqToMatching(Post post, Member taker, Member giver) { private void validatePostAuthor(Post post, Long memberId) { if ((post.getPostType() == PostType.GIVER && !post.getAuthor().getId().equals(memberId)) || (post.getPostType() == PostType.TAKER && !post.getAuthor().getId().equals(memberId))) { - throw new IllegalArgumentException("회원님이 작성한 게시글이 아닙니다."); + throw PostUnauthorizedAccessException.EXCEPTION; } } } diff --git a/src/main/java/econo/buddybridge/post/exception/PostDeleteNotAllowedException.java b/src/main/java/econo/buddybridge/post/exception/PostDeleteNotAllowedException.java new file mode 100644 index 0000000..9aff24e --- /dev/null +++ b/src/main/java/econo/buddybridge/post/exception/PostDeleteNotAllowedException.java @@ -0,0 +1,11 @@ +package econo.buddybridge.post.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class PostDeleteNotAllowedException extends BusinessException { + public static BusinessException EXCEPTION = new PostDeleteNotAllowedException(); + + private PostDeleteNotAllowedException() { + super(PostErrorCode.POST_DELETE_NOT_ALLOWED); + } +} diff --git a/src/main/java/econo/buddybridge/post/exception/PostErrorCode.java b/src/main/java/econo/buddybridge/post/exception/PostErrorCode.java new file mode 100644 index 0000000..ab734ab --- /dev/null +++ b/src/main/java/econo/buddybridge/post/exception/PostErrorCode.java @@ -0,0 +1,39 @@ +package econo.buddybridge.post.exception; + +import econo.buddybridge.common.exception.ErrorCode; +import org.springframework.http.HttpStatus; + +public enum PostErrorCode implements ErrorCode { + // TODO: Implement PostErrorCode + // EX) INVALID_INPUT_VALUE("P001", HttpStatus.BAD_REQUEST, "요청 값이 잘못되었습니다."), + POST_NOT_FOUND("P001", HttpStatus.NOT_FOUND, "존재하지 않는 게시글입니다."), + POST_DELETE_NOT_ALLOWED("P002", HttpStatus.FORBIDDEN, "본인의 게시글만 삭제할 수 있습니다."), + POST_UPDATE_NOT_ALLOWED("P003", HttpStatus.FORBIDDEN, "본인의 게시글만 수정할 수 있습니다."), + POST_UNAUTHORIZED_ACCESS("P004", HttpStatus.BAD_REQUEST, "회원님이 작성한 게시글이 아닙니다."), + ; + + private final String code; + private final HttpStatus httpStatus; + private final String message; + + PostErrorCode(String code, HttpStatus httpStatus, String message) { + this.code = code; + this.httpStatus = httpStatus; + this.message = message; + } + + @Override + public String getCode() { + return code; + } + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/econo/buddybridge/post/exception/PostNotFoundException.java b/src/main/java/econo/buddybridge/post/exception/PostNotFoundException.java new file mode 100644 index 0000000..ffa1ca6 --- /dev/null +++ b/src/main/java/econo/buddybridge/post/exception/PostNotFoundException.java @@ -0,0 +1,12 @@ +package econo.buddybridge.post.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class PostNotFoundException extends BusinessException { + + public static BusinessException EXCEPTION = new PostNotFoundException(); + + private PostNotFoundException() { + super(PostErrorCode.POST_NOT_FOUND); + } +} diff --git a/src/main/java/econo/buddybridge/post/exception/PostUnauthorizedAccessException.java b/src/main/java/econo/buddybridge/post/exception/PostUnauthorizedAccessException.java new file mode 100644 index 0000000..f33d43d --- /dev/null +++ b/src/main/java/econo/buddybridge/post/exception/PostUnauthorizedAccessException.java @@ -0,0 +1,10 @@ +package econo.buddybridge.post.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class PostUnauthorizedAccessException extends BusinessException { + + public static BusinessException EXCEPTION = new PostUnauthorizedAccessException(); + + private PostUnauthorizedAccessException() {super(PostErrorCode.POST_UNAUTHORIZED_ACCESS);} +} diff --git a/src/main/java/econo/buddybridge/post/exception/PostUpdateNotAllowedException.java b/src/main/java/econo/buddybridge/post/exception/PostUpdateNotAllowedException.java new file mode 100644 index 0000000..b7c20a1 --- /dev/null +++ b/src/main/java/econo/buddybridge/post/exception/PostUpdateNotAllowedException.java @@ -0,0 +1,11 @@ +package econo.buddybridge.post.exception; + +import econo.buddybridge.common.exception.BusinessException; + +public class PostUpdateNotAllowedException extends BusinessException { + + public static BusinessException EXCEPTION = new PostUpdateNotAllowedException(); + + private PostUpdateNotAllowedException() { super(PostErrorCode.POST_UPDATE_NOT_ALLOWED); } + +} diff --git a/src/main/java/econo/buddybridge/post/service/PostService.java b/src/main/java/econo/buddybridge/post/service/PostService.java index c8db3a6..1156f21 100644 --- a/src/main/java/econo/buddybridge/post/service/PostService.java +++ b/src/main/java/econo/buddybridge/post/service/PostService.java @@ -10,6 +10,9 @@ import econo.buddybridge.post.entity.Post; import econo.buddybridge.post.entity.PostStatus; import econo.buddybridge.post.entity.PostType; +import econo.buddybridge.post.exception.PostDeleteNotAllowedException; +import econo.buddybridge.post.exception.PostNotFoundException; +import econo.buddybridge.post.exception.PostUpdateNotAllowedException; import econo.buddybridge.post.repository.PostRepository; import econo.buddybridge.post.repository.PostRepositoryCustom; import lombok.RequiredArgsConstructor; @@ -25,10 +28,16 @@ public class PostService { private final PostRepositoryCustom postRepositoryCustom; private final MemberService memberService; + // 존재하는 포스트인지 확인 + @Transactional(readOnly = true) + public Post findPostByIdOrThrow(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> PostNotFoundException.EXCEPTION); + } + @Transactional(readOnly = true) // 단일 게시글 조회 public PostResDto findPost(Long postId) { - Post post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다.")); + Post post = findPostByIdOrThrow(postId); return new PostResDto(post); } @@ -49,11 +58,10 @@ public Long createPost(PostReqDto postReqDto, Long memberId) { @Transactional // 게시글 수정 public Long updatePost(Long postId, PostReqDto postReqDto, Long memberId) { - Post post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다.")); + Post post = findPostByIdOrThrow(postId); if (!post.getAuthor().getId().equals(memberId)) { - throw new IllegalArgumentException("본인의 게시글만 수정할 수 있습니다."); + throw PostUpdateNotAllowedException.EXCEPTION; } post.updatePost(postReqDto); @@ -63,10 +71,9 @@ public Long updatePost(Long postId, PostReqDto postReqDto, Long memberId) { @Transactional // 게시글 삭제 public void deletePost(Long postId, Long memberId) { - Post post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다.")); + Post post = findPostByIdOrThrow(postId); if (!post.getAuthor().getId().equals(memberId)) { - throw new IllegalArgumentException("본인의 게시글만 수정할 수 있습니다."); + throw PostDeleteNotAllowedException.EXCEPTION; } postRepository.deleteById(postId); }