Skip to content

Commit

Permalink
[FEAT] 회원, 댓글, 알림 도메인 예외처리 (#121)
Browse files Browse the repository at this point in the history
* refactor(OAuthInfoService): EnumMap으로 변경

* refactor(SessionInterceptor): CORS 요청 허용 분기는 CorsUtils를 사용하도록 변경

* feat(GlobalExceptionHandler): 전역 예외처리 로직 및 비즈니스 예외 구현

* refactor(ApiResponse): 예외 발생 시 커스텀 응답 구현

* feat(CommentErrorCode): 댓글 도메인 비즈니스 예외 적용

* refactor(CommentService): 메서드 분리 리펙터링

* feat(CommentService): 댓글 페이지네이션 정렬방식 비즈니스 예외 추가

* refactor(MemberService): 코드 간략화

- DTO to Entity는 생성자를 이용해 간략화
- OAuthInfo 응답 인터페이스 내부 default 메서드를 구현해 코드 간략화

* feat(MemberErrorCode): 회원 도메인 비즈니스 예외 적용

* refactor: memberRepository의존성을 memberService로 변경 후 중복 코드 제거

- `findMemberByIdOrThrow` 메서드로 중복 코드 제거

* feat(NotificationErrorCode): 알림 도메인 비즈니스 예외 적용

* refactor: 메서드 분리 및 lombok 적용으로 리펙터링
  • Loading branch information
Profile-exe authored Aug 18, 2024
1 parent aea91bd commit d44b048
Show file tree
Hide file tree
Showing 30 changed files with 454 additions and 116 deletions.
16 changes: 16 additions & 0 deletions src/main/java/econo/buddybridge/auth/dto/OAuthInfoResponse.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
package econo.buddybridge.auth.dto;

import econo.buddybridge.member.entity.DisabilityType;
import econo.buddybridge.member.entity.Gender;
import econo.buddybridge.member.entity.Member;

public interface OAuthInfoResponse {
String getEmail();
String getName();
String getNickname();
Integer getAge();
String getGender();
String getProfileImageUrl();

default Member toMember() {
return Member.builder()
.email(getEmail())
.name(getName())
.nickname(getNickname())
.age(getAge())
.gender(Gender.fromEnglishName(getGender()))
.profileImageUrl(getProfileImageUrl())
.disabilityType(DisabilityType.없음) // 회원가입 시 초기 장애 유형은 없음으로 설정
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import econo.buddybridge.auth.client.OAuthApiClient;
import econo.buddybridge.auth.dto.OAuthInfoResponse;
import econo.buddybridge.auth.dto.OAuthLoginParams;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand All @@ -18,9 +19,8 @@ public class OAuthInfoService {
private final Map<OAuthProvider, OAuthApiClient> clients;

public OAuthInfoService(List<OAuthApiClient> clients) {
this.clients = clients.stream().collect(
Collectors.toUnmodifiableMap(OAuthApiClient::getOAuthProvider, Function.identity())
);
this.clients = new EnumMap<>(clients.stream()
.collect(Collectors.toMap(OAuthApiClient::getOAuthProvider, Function.identity())));
}

public OAuthInfoResponse getUserInfo(OAuthLoginParams params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,27 @@
import econo.buddybridge.matching.entity.Matching;
import econo.buddybridge.matching.repository.MatchingRepository;
import econo.buddybridge.member.entity.Member;
import econo.buddybridge.member.repository.MemberRepository;
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 MemberRepository memberRepository;
private final MemberService memberService;
private final MatchingRepository matchingRepository;
private final ChatMessageRepository chatMessageRepository;
private final EmitterService emitterService;

@Transactional // 메시지 저장
public ChatMessageResDto save(Long senderId, ChatMessageReqDto chatMessageReqDto, Long matchingId) {
Member sender = memberRepository.findById(senderId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));
Member sender = memberService.findMemberByIdOrThrow(senderId);

Matching matching = matchingRepository.findById(matchingId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 매칭입니다."));
Expand All @@ -42,8 +40,7 @@ public ChatMessageResDto save(Long senderId, ChatMessageReqDto chatMessageReqDto
.build();

Long receiverId = getReceiverId(sender.getId(), matching.getId());
Member receiver = memberRepository.findById(receiverId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다."));
Member receiver = memberService.findMemberByIdOrThrow(receiverId);

emitterService.send( // 채팅을 받는 사용자에게 알림 전송
receiver,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package econo.buddybridge.comment.exception;

import econo.buddybridge.common.exception.BusinessException;

public class CommentDeleteNotAllowedException extends BusinessException {

public static BusinessException EXCEPTION = new CommentDeleteNotAllowedException();

private CommentDeleteNotAllowedException() {
super(CommentErrorCode.COMMENT_DELETE_NOT_ALLOWED);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package econo.buddybridge.comment.exception;

import econo.buddybridge.common.exception.ErrorCode;
import org.springframework.http.HttpStatus;

public enum CommentErrorCode implements ErrorCode {
COMMENT_NOT_FOUND("C001", HttpStatus.NOT_FOUND, "존재하지 않는 댓글입니다."),
COMMENT_UPDATE_NOT_ALLOWED("C002", HttpStatus.FORBIDDEN, "본인의 댓글만 수정할 수 있습니다."),
COMMENT_DELETE_NOT_ALLOWED("C003", HttpStatus.FORBIDDEN, "본인의 댓글만 삭제할 수 있습니다."),
COMMENT_INVALID_DIRECTION("C004", HttpStatus.BAD_REQUEST, "올바르지 않은 정렬 방식입니다."),
;

private final String code;
private final HttpStatus httpStatus;
private final String message;

CommentErrorCode(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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package econo.buddybridge.comment.exception;

import econo.buddybridge.common.exception.BusinessException;

public class CommentInvalidDirectionException extends BusinessException {

public static BusinessException EXCEPTION = new CommentInvalidDirectionException();

private CommentInvalidDirectionException() {
super(CommentErrorCode.COMMENT_INVALID_DIRECTION);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package econo.buddybridge.comment.exception;

import econo.buddybridge.common.exception.BusinessException;

public class CommentNotFoundException extends BusinessException {

public static BusinessException EXCEPTION = new CommentNotFoundException();

private CommentNotFoundException() {
super(CommentErrorCode.COMMENT_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package econo.buddybridge.comment.exception;

import econo.buddybridge.common.exception.BusinessException;

public class CommentUpdateNotAllowedException extends BusinessException {

public static BusinessException EXCEPTION = new CommentUpdateNotAllowedException();

private CommentUpdateNotAllowedException() {
super(CommentErrorCode.COMMENT_UPDATE_NOT_ALLOWED);
}

}
48 changes: 31 additions & 17 deletions src/main/java/econo/buddybridge/comment/service/CommentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import econo.buddybridge.comment.dto.CommentCustomPage;
import econo.buddybridge.comment.dto.CommentReqDto;
import econo.buddybridge.comment.entity.Comment;
import econo.buddybridge.comment.exception.CommentDeleteNotAllowedException;
import econo.buddybridge.comment.exception.CommentInvalidDirectionException;
import econo.buddybridge.comment.exception.CommentNotFoundException;
import econo.buddybridge.comment.exception.CommentUpdateNotAllowedException;
import econo.buddybridge.comment.repository.CommentRepository;
import econo.buddybridge.comment.repository.CommentRepositoryCustom;
import econo.buddybridge.member.entity.Member;
import econo.buddybridge.member.repository.MemberRepository;
import econo.buddybridge.member.service.MemberService;
import econo.buddybridge.notification.entity.NotificationType;
import econo.buddybridge.notification.service.EmitterService;
import econo.buddybridge.post.entity.Post;
Expand All @@ -23,48 +27,55 @@
@RequiredArgsConstructor
public class CommentService {

private final MemberRepository memberRepository;
private final MemberService memberService;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final CommentRepositoryCustom commentRepositoryCustom;
private final EmitterService emitterService;

@Transactional(readOnly = true) // 댓글 조회
public CommentCustomPage getComments(Long postId, Integer size, String order, Long cursor) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다."));
Post post = findPostByIdOrThrow(postId);

Direction direction;
try {
direction = Direction.valueOf(order);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("올바르지 않은 정렬 방식입니다.");
throw CommentInvalidDirectionException.EXCEPTION;
}

PageRequest page = PageRequest.of(0, size, Sort.by(direction, "id"));

return commentRepositoryCustom.findByPost(post, cursor, page);
}

private Post findPostByIdOrThrow(Long postId) {
return postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다."));
}

@Transactional // 댓글 생성
public Long createComment(CommentReqDto commentReqDto, Long postId, Long memberId) {

Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다."));
Member member = memberService.findMemberByIdOrThrow(memberId);

Post post = postRepository.findById(postId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 게시글입니다."));
Post post = findPostByIdOrThrow(postId);

Comment comment = commentReqToComment(commentReqDto, post, member);

// 게시글 작성자에게 댓글 알림 전송
sendNotificationToPostAuthor(member, comment, post);

return commentRepository.save(comment).getId();
}

private void sendNotificationToPostAuthor(Member member, Comment comment, Post post) {
// 알림 내용은 댓글 작성자 이름과 댓글 내용
String notificationContent = member.getName() + "님이 댓글을 남겼습니다. - " + comment.getContent();
String notificationUrl = getCommentNotificationUrl(post.getPostType(), post.getId());

// 댓글 알림은 게시글 작성자에게 전송
emitterService.send(post.getAuthor(), notificationContent, notificationUrl, NotificationType.COMMENT);

return commentRepository.save(comment).getId();
}

private String getCommentNotificationUrl(PostType postType, Long postId) {
Expand All @@ -81,11 +92,10 @@ private String getCommentNotificationUrl(PostType postType, Long postId) {

@Transactional // 댓글 수정
public Long updateComment(Long commentId, CommentReqDto commentReqDto, Long memberId) {
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 댓글입니다."));
Comment comment = findCommentByIdOrThrow(commentId);

if (!comment.getAuthor().getId().equals(memberId)) {
throw new IllegalArgumentException("본인의 댓글만 수정할 수 있습니다.");
throw CommentUpdateNotAllowedException.EXCEPTION;
}

comment.updateContent(commentReqDto.content());
Expand All @@ -95,16 +105,20 @@ public Long updateComment(Long commentId, CommentReqDto commentReqDto, Long memb

@Transactional // 댓글 삭제
public void deleteComment(Long commentId, Long memberId) {
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 댓글입니다."));
Comment comment = findCommentByIdOrThrow(commentId);

if (!comment.getAuthor().getId().equals(memberId)) {
throw new IllegalArgumentException("본인의 댓글만 삭제할 수 있습니다.");
throw CommentDeleteNotAllowedException.EXCEPTION;
}

commentRepository.delete(comment);
}

private Comment findCommentByIdOrThrow(Long commentId) {
return commentRepository.findById(commentId)
.orElseThrow(() -> CommentNotFoundException.EXCEPTION);
}

private Comment commentReqToComment(CommentReqDto commentReqDto, Post post, Member member) {
return Comment.builder()
.post(post)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package econo.buddybridge.common.exception;

public class BusinessException extends RuntimeException {

private final ErrorCode errorCode;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public ErrorCode getErrorCode() {
return errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package econo.buddybridge.common.exception;

import org.springframework.http.HttpStatus;

public enum CommonErrorCode implements ErrorCode {
INVALID_INPUT_VALUE("C001", HttpStatus.BAD_REQUEST, "요청 값이 잘못되었습니다."),
;

private final String code;
private final HttpStatus httpStatus;
private final String message;

CommonErrorCode(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;
}
}
10 changes: 10 additions & 0 deletions src/main/java/econo/buddybridge/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package econo.buddybridge.common.exception;

import org.springframework.http.HttpStatus;

public interface ErrorCode {

String getCode();
HttpStatus getHttpStatus();
String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package econo.buddybridge.common.exception;

import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
import lombok.Getter;
import lombok.Setter;

@Getter
public class ErrorResponse {

private final String code;
private final int status;
private final String message;

@Setter
@JsonInclude(JsonInclude.Include.NON_NULL)
private List<ValidationError> invalidParams;

public ErrorResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.status = errorCode.getHttpStatus().value();
this.message = errorCode.getMessage();
}
}
Loading

0 comments on commit d44b048

Please sign in to comment.