Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ out/

### 보안정보
application-secret.yml
*.pem

src/main/generated
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class AuthService {

public void signup(SignupRequestDto dto, MultipartFile image){
if (userRepository.existsByEmail(dto.getEmail())) {
throw new BusinessException("중복된 이메일입니다.");
throw new BusinessException(AuthErrorStatus.DUPLICATE_EMAIL);
}
userRepository.findByEmailAndUserStatus(dto.getEmail(), UserStatus.DELETED)
.ifPresent(deletedUser -> {
Expand All @@ -50,7 +50,7 @@ public void signup(SignupRequestDto dto, MultipartFile image){
}
});
if (userRepository.existsByNickname(dto.getNickname())){
throw new BusinessException("중복된 닉네임입니다.");
throw new BusinessException(AuthErrorStatus.DUPLICATE_NICKNAME);
}
// TODO: bio, 닉네임에 금칙어 검사
String encodedPassword = passwordEncoder.encode(dto.getPassword());
Expand Down Expand Up @@ -117,7 +117,7 @@ public void logout(User user){

public void signOut(User user, com.daramg.server.user.dto.PasswordRequestDto request){
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new BusinessException("비밀번호가 일치하지 않습니다.");
throw new BusinessException(AuthErrorStatus.INVALID_PASSWORD);
}
redisTemplate.delete(user.getEmail());
user.withdraw();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.daramg.server.auth.util.MimeMessageGenerator;
import com.daramg.server.auth.util.VerificationCodeGenerator;
import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.user.exception.UserErrorStatus;
import com.daramg.server.user.repository.UserRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
Expand All @@ -35,7 +36,7 @@ public void sendVerificationEmail(EmailVerificationRequestDto request) {
case SIGNUP -> sendForSignup(request);
case PASSWORD_RESET -> sendForPasswordReset(request);
case EMAIL_CHANGE -> sendForEmailChange(request);
default -> throw new BusinessException("지원하지 않는 이메일 발송 목적입니다.");
default -> throw new BusinessException(AuthErrorStatus.UNSUPPORTED_EMAIL_PURPOSE);
}
}

Expand All @@ -53,7 +54,7 @@ private void sendForPasswordReset(EmailVerificationRequestDto request) {
private void sendForEmailChange(EmailVerificationRequestDto request) {
if (userRepository.existsByEmail(request.getEmail())
&& !request.getEmail().equals(request.getOriginalEmail())) {
throw new BusinessException("이미 가입되어 있는 이메일입니다.");
throw new BusinessException(UserErrorStatus.DUPLICATE_EMAIL);
}
sendVerificationCode(request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public enum AuthErrorStatus implements BaseErrorCode {

USER_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, ErrorCategory.AUTH.generate(404_1), "존재하지 않는 사용자입니다."),

DUPLICATE_EMAIL(HttpStatus.CONFLICT, ErrorCategory.AUTH.generate(409_1), "중복된 이메일입니다."),
DUPLICATE_NICKNAME(HttpStatus.CONFLICT, ErrorCategory.AUTH.generate(409_2), "중복된 닉네임입니다."),
INVALID_PASSWORD(HttpStatus.BAD_REQUEST, ErrorCategory.AUTH.generate(400_8), "비밀번호가 일치하지 않습니다."),
UNSUPPORTED_EMAIL_PURPOSE(HttpStatus.BAD_REQUEST, ErrorCategory.AUTH.generate(400_9), "지원하지 않는 이메일 발송 목적입니다."),

SEND_VERIFICATION_EMAIL_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCategory.AUTH.generate(500), "이메일 전송에 실패했습니다."),
REDIS_CONNECTION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCategory.AUTH.generate(500_1), "Redis 연결에 실패했습니다. 서버 관리자에게 문의 바랍니다.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.daramg.server.comment.repository.CommentLikeRepository;
import com.daramg.server.comment.repository.CommentRepository;
import com.daramg.server.common.application.EntityUtils;
import com.daramg.server.comment.exception.CommentErrorStatus;
import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.post.domain.Post;
import com.daramg.server.notification.domain.NotificationType;
Expand All @@ -31,7 +32,7 @@ public class CommentService {
public void createComment(Long postId, CommentCreateDto request, User user){
Post post = entityUtils.getEntity(postId, Post.class);
if (post.isBlocked()){
throw new BusinessException("블락된 포스트에는 댓글을 남길 수 없습니다.");
throw new BusinessException(CommentErrorStatus.BLOCKED_POST);
}

Comment comment = Comment.of(
Expand All @@ -53,11 +54,11 @@ public void createComment(Long postId, CommentCreateDto request, User user){
public void createReply(Long commentId, CommentReplyCreateDto request, User user){
Comment parentComment = entityUtils.getEntity(commentId, Comment.class);
if (parentComment.isDeleted() || parentComment.isBlocked()){
throw new BusinessException("삭제되었거나 블락된 댓글에는 대댓글을 남길 수 없습니다.");
throw new BusinessException(CommentErrorStatus.BLOCKED_OR_DELETED_COMMENT_REPLY);
}
Post post = parentComment.getPost();
if (post.isBlocked()){
throw new BusinessException("블락된 포스트에는 댓글을 남길 수 없습니다.");
throw new BusinessException(CommentErrorStatus.BLOCKED_POST);
}

Comment reply = Comment.of(
Expand All @@ -79,7 +80,7 @@ public void createReply(Long commentId, CommentReplyCreateDto request, User user
public CommentLikeResponseDto toggleCommentLike(Long commentId, User user){
Comment comment = entityUtils.getEntity(commentId, Comment.class);
if (comment.isDeleted() || comment.isBlocked()){
throw new BusinessException("삭제되었거나 블락된 댓글에는 좋아요를 누를 수 없습니다.");
throw new BusinessException(CommentErrorStatus.BLOCKED_OR_DELETED_COMMENT_LIKE);
}

boolean alreadyLiked = commentLikeRepository
Expand All @@ -105,10 +106,10 @@ public void deleteComment(Long commentId, User user){
Comment comment = entityUtils.getEntity(commentId, Comment.class);

if (comment.isDeleted()){
throw new BusinessException("이미 삭제 처리된 댓글입니다.");
throw new BusinessException(CommentErrorStatus.ALREADY_DELETED);
}
if (comment.getUser() == null || !comment.getUser().getId().equals(user.getId())){
throw new BusinessException("댓글을 작성한 유저만 댓글을 삭제할 수 있습니다.");
throw new BusinessException(CommentErrorStatus.NOT_COMMENT_AUTHOR);
}

comment.softDelete();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.daramg.server.comment.exception;

import com.daramg.server.common.exception.BaseErrorCode;
import com.daramg.server.common.exception.ErrorCategory;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum CommentErrorStatus implements BaseErrorCode {

BLOCKED_POST(HttpStatus.FORBIDDEN, ErrorCategory.COMMENT.generate(403_1), "블락된 포스트에는 댓글을 남길 수 없습니다."),
BLOCKED_OR_DELETED_COMMENT_REPLY(HttpStatus.FORBIDDEN, ErrorCategory.COMMENT.generate(403_2), "삭제되었거나 블락된 댓글에는 대댓글을 남길 수 없습니다."),
BLOCKED_OR_DELETED_COMMENT_LIKE(HttpStatus.FORBIDDEN, ErrorCategory.COMMENT.generate(403_3), "삭제되었거나 블락된 댓글에는 좋아요를 누를 수 없습니다."),
ALREADY_DELETED(HttpStatus.BAD_REQUEST, ErrorCategory.COMMENT.generate(400_1), "이미 삭제 처리된 댓글입니다."),
NOT_COMMENT_AUTHOR(HttpStatus.FORBIDDEN, ErrorCategory.COMMENT.generate(403_4), "댓글을 작성한 유저만 댓글을 삭제할 수 있습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum CommonErrorStatus implements BaseErrorCode {
GATEWAY_TIMEOUT(HttpStatus.GATEWAY_TIMEOUT, ErrorCategory.COMMON.generate(504), "타임아웃 에러, 서버 관리자에게 문의 바랍니다."),

BAD_REQUEST(HttpStatus.BAD_REQUEST, ErrorCategory.COMMON.generate(400), "유효하지 않은 요청입니다."),
INVALID_CURSOR(HttpStatus.BAD_REQUEST, ErrorCategory.COMMON.generate(400_1), "유효하지 않은 커서 포맷입니다."),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, ErrorCategory.COMMON.generate(401), "인증이 필요합니다."),
FORBIDDEN(HttpStatus.FORBIDDEN, ErrorCategory.COMMON.generate(403), "금지된 요청입니다."),
NOT_FOUND(HttpStatus.NOT_FOUND , ErrorCategory.COMMON.generate(404), "찾을 수 없는 리소스입니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ public enum ErrorCategory {
AUTH("AUTH_"),
USER("USER_"),
POST("POST_"),
IMAGE("IMAGE_");
IMAGE("IMAGE_"),
COMMENT("COMMENT_"),
NOTIFICATION("NOTIFICATION_");

private final String prefix;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
public ResponseEntity<ErrorResponse> handleGeneralException(BusinessException e) {
BaseErrorCode errorCode = e.getErrorCode();

log.warn("GeneralException: {}", errorCode.getMessage());
log.warn("GeneralException: {} - {}", errorCode.getMessage(), e.getMessage());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

로그 메시지 형식을 개선하면 더 유용할 것 같습니다. 현재 형식은 BusinessException(BaseErrorCode) 생성자를 사용할 때 메시지가 중복으로 로깅될 수 있습니다. (GeneralException: <msg> - <msg>)

e.getMessage()BusinessException(String message, BaseErrorCode errorCode) 생성자에서 errorCode.getMessage()를 포함하도록 구성되어 있으므로, 다음과 같이 변경하면 중복을 피하고 에러 코드를 명시적으로 포함하여 디버깅에 더 도움이 될 것입니다.

log.warn("GeneralException: {} (code: {})", e.getMessage(), errorCode.getCode());

이렇게 하면 다음과 같이 로깅됩니다.

  • new BusinessException(errorCode) 사용 시: GeneralException: <메시지> (code: <에러코드>)
  • new BusinessException(detail, errorCode) 사용 시: GeneralException: <메시지><상세정보> (code: <에러코드>)
Suggested change
log.warn("GeneralException: {} - {}", errorCode.getMessage(), e.getMessage());
log.warn("GeneralException: {} (code: {})", e.getMessage(), errorCode.getCode());

return ResponseEntity
.status(errorCode.getHttpStatus())
.body(ErrorResponse.of(errorCode));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/daramg/server/common/util/PagingUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.daramg.server.common.dto.PageRequestDto;
import com.daramg.server.common.dto.PageResponseDto;
import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.common.exception.CommonErrorStatus;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.NumberPath;
Expand Down Expand Up @@ -116,7 +117,7 @@ private Cursor decodeCursor(String cursorString) {
return new Cursor(LocalDateTime.parse(parts[0]), Long.parseLong(parts[1]));
} catch (IllegalArgumentException | ArrayIndexOutOfBoundsException |
DateTimeParseException e) {
throw new BusinessException("유효하지 않은 커서 포맷입니다.");
throw new BusinessException(CommonErrorStatus.INVALID_CURSOR);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.notice.domain.Notice;
import com.daramg.server.notification.exception.NotificationErrorStatus;
import com.daramg.server.user.domain.User;

public class NoticeUserValidator {

public static void check(Notice notice, User user) {
if (!notice.getUser().getId().equals(user.getId())) {
throw new BusinessException("공지사항의 작성자가 일치하지 않습니다.");
throw new BusinessException(NotificationErrorStatus.NOT_NOTICE_AUTHOR);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.daramg.server.common.application.EntityUtils;
import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.notification.exception.NotificationErrorStatus;
import com.daramg.server.notification.domain.Notification;
import com.daramg.server.notification.repository.NotificationRepository;
import com.daramg.server.user.domain.User;
Expand Down Expand Up @@ -35,7 +36,7 @@ public void delete(Long notificationId, User user) {

private void validateReceiver(Notification notification, User user) {
if (!notification.getReceiver().getId().equals(user.getId())) {
throw new BusinessException("본인의 알림만 처리할 수 있습니다.");
throw new BusinessException(NotificationErrorStatus.NOT_OWN_NOTIFICATION);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.daramg.server.notification.exception;

import com.daramg.server.common.exception.BaseErrorCode;
import com.daramg.server.common.exception.ErrorCategory;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum NotificationErrorStatus implements BaseErrorCode {

NOT_OWN_NOTIFICATION(HttpStatus.FORBIDDEN, ErrorCategory.NOTIFICATION.generate(403_1), "본인의 알림만 처리할 수 있습니다."),
NOT_NOTICE_AUTHOR(HttpStatus.FORBIDDEN, ErrorCategory.NOTIFICATION.generate(403_2), "공지사항의 작성자가 일치하지 않습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.daramg.server.common.application.EntityUtils;
import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.post.exception.PostErrorStatus;
import com.daramg.server.common.exception.NotFoundException;
import com.daramg.server.composer.domain.Composer;
import com.daramg.server.composer.repository.ComposerRepository;
Expand Down Expand Up @@ -61,7 +62,7 @@ public void createCuration(PostCreateDto.CreateCuration dto, User user) {
if (dto.getPrimaryComposerId() != null) {
primaryComposer = entityUtils.getEntity(dto.getPrimaryComposerId(), Composer.class);
} else if (dto.getPostStatus() == PostStatus.PUBLISHED) {
throw new BusinessException("발행 시 주요 작곡가는 필수입니다.");
throw new BusinessException(PostErrorStatus.PRIMARY_COMPOSER_REQUIRED);
}
List<Composer> additionalComposers = new ArrayList<>();

Expand Down Expand Up @@ -95,7 +96,7 @@ public void createStory(PostCreateDto.CreateStory dto, User user) {
if (dto.getPrimaryComposerId() != null) {
primaryComposer = entityUtils.getEntity(dto.getPrimaryComposerId(), Composer.class);
} else if (dto.getPostStatus() == PostStatus.PUBLISHED) {
throw new BusinessException("발행 시 주요 작곡가는 필수입니다.");
throw new BusinessException(PostErrorStatus.PRIMARY_COMPOSER_REQUIRED);
}
PostCreateVo.Story vo = new PostCreateVo.Story(
user,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.daramg.server.post.exception;

import com.daramg.server.common.exception.BaseErrorCode;
import com.daramg.server.common.exception.ErrorCategory;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum PostErrorStatus implements BaseErrorCode {

PRIMARY_COMPOSER_REQUIRED(HttpStatus.BAD_REQUEST, ErrorCategory.POST.generate(400_1), "발행 시 주요 작곡가는 필수입니다."),
NOT_POST_AUTHOR(HttpStatus.FORBIDDEN, ErrorCategory.POST.generate(403_1), "포스트와 작성자가 일치하지 않습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import com.daramg.server.common.exception.BusinessException;
import com.daramg.server.post.domain.Post;
import com.daramg.server.post.exception.PostErrorStatus;
import com.daramg.server.user.domain.User;

public class PostUserValidator {

public static void check(Post post, User user) {
if (!post.getUser().getId().equals(user.getId())) {
throw new BusinessException("포스트와 작성자가 일치하지 않습니다.");
throw new BusinessException(PostErrorStatus.NOT_POST_AUTHOR);
}
}
}
Loading
Loading