From 9b107dd06baa29cf10e0668bdb765d2a5875c9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B5=AC=ED=99=98=EC=A4=80/=EB=AA=A8=EA=B1=B4?= Date: Sun, 21 Jul 2024 16:12:39 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=AC=B8=EA=B5=AC=EC=9D=98=20=EA=B5=AC?= =?UTF-8?q?=EC=B2=B4=ED=99=94=20=EB=B0=8F=20=EC=9D=BD=EC=9D=8C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/comment/business/CommentService.java | 27 +++++++++++++ .../business/InvitationService.java | 12 ++++++ .../business/NotificationMapper.java | 28 +++++++++++++- .../business/NotificationService.java | 38 +++++++++++++++++++ .../business/NotificationUtil.java | 36 ++++++++++++++++++ .../implement/NotificationCommandAdapter.java | 13 +++++++ .../implement/NotificationQueryAdapter.java | 10 +++++ .../presentation/NotificationApi.java | 11 ++++++ .../dto/NotificationRequestDTO.java | 23 +++++++++++ .../dto/NotificationResponseDTO.java | 10 +++++ .../server/api/post/business/PostService.java | 12 ++++++ .../entity/notification/Notification.java | 7 ++-- .../ThrowClass/NotificationException.java | 9 +++++ 13 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 src/main/java/treehouse/server/api/notification/business/NotificationUtil.java create mode 100644 src/main/java/treehouse/server/api/notification/presentation/dto/NotificationRequestDTO.java create mode 100644 src/main/java/treehouse/server/global/exception/ThrowClass/NotificationException.java diff --git a/src/main/java/treehouse/server/api/comment/business/CommentService.java b/src/main/java/treehouse/server/api/comment/business/CommentService.java index 7229b8e..2259fd8 100644 --- a/src/main/java/treehouse/server/api/comment/business/CommentService.java +++ b/src/main/java/treehouse/server/api/comment/business/CommentService.java @@ -13,6 +13,8 @@ import treehouse.server.api.comment.presentation.dto.CommentRequestDTO; import treehouse.server.api.comment.presentation.dto.CommentResponseDTO; import treehouse.server.api.member.implementation.MemberQueryAdapter; +import treehouse.server.api.notification.business.NotificationService; +import treehouse.server.api.notification.presentation.dto.NotificationRequestDTO; import treehouse.server.api.post.implement.PostQueryAdapter; import treehouse.server.api.reaction.business.ReactionMapper; import treehouse.server.api.reaction.implementation.ReactionCommandAdapter; @@ -28,6 +30,7 @@ import treehouse.server.global.entity.comment.Comment; import treehouse.server.global.entity.comment.CommentType; import treehouse.server.global.entity.member.Member; +import treehouse.server.global.entity.notification.NotificationType; import treehouse.server.global.entity.post.Post; import treehouse.server.global.entity.reaction.Reaction; import treehouse.server.global.entity.report.Report; @@ -63,6 +66,8 @@ public class CommentService { private final BranchQueryAdapter branchQueryAdapter; + private final NotificationService notificationService; + public void reportComment(User user, CommentRequestDTO.reportComment request, Long treehouseId, Long postId, Long commentId){ @@ -161,6 +166,14 @@ public CommentResponseDTO.CommentIdResponseDto createComment(User user, Long tre Comment comment = CommentMapper.toComment(writer, post, request.getContext(), CommentType.PARENT, -1L); Long commentId = commentCommandAdapter.createComment(comment).getId(); + + //알림 생성 + NotificationRequestDTO.createNotification notificationRequest = new NotificationRequestDTO.createNotification(); + notificationRequest.setReceiverId(post.getWriter().getId()); // 여기서 receiver 설정 (예시) + notificationRequest.setTargetId(post.getId()); + notificationRequest.setType(NotificationType.COMMENT); // 알림 타입 설정 (예시) + notificationService.createNotification(user, treehouseId, notificationRequest, null); + return CommentMapper.toIdResponseDto(commentId); } @@ -175,6 +188,13 @@ public CommentResponseDTO.CommentIdResponseDto createReply(User user, Long treeh Comment comment = CommentMapper.toComment(writer, post, request.getContext(), CommentType.CHILD, parentId); Long replyId = commentCommandAdapter.createComment(comment).getId(); + //알림 생성 + NotificationRequestDTO.createNotification notificationRequest = new NotificationRequestDTO.createNotification(); + notificationRequest.setReceiverId(comment.getWriter().getId()); // 여기서 receiver 설정 (예시) + notificationRequest.setTargetId(comment.getId()); + notificationRequest.setType(NotificationType.REPLY); // 알림 타입 설정 (예시) + notificationService.createNotification(user, treehouseId, notificationRequest, null); + return CommentMapper.toIdResponseDto(replyId); } @@ -208,6 +228,13 @@ public String reactToComment(User user, Long treehouseId, Long commentId, Commen member.addReaction(savedReaction); + //알림 생성 + NotificationRequestDTO.createNotification notificationRequest = new NotificationRequestDTO.createNotification(); + notificationRequest.setReceiverId(comment.getWriter().getId()); // 여기서 receiver 설정 (예시) + notificationRequest.setTargetId(comment.getId()); + notificationRequest.setType(NotificationType.COMMENT_REACTION); // 알림 타입 설정 (예시) + notificationService.createNotification(user, treehouseId, notificationRequest, savedReaction.getReactionName()); + return request.getReactionName() + " is saved"; } } diff --git a/src/main/java/treehouse/server/api/invitation/business/InvitationService.java b/src/main/java/treehouse/server/api/invitation/business/InvitationService.java index 11fb296..86c4584 100644 --- a/src/main/java/treehouse/server/api/invitation/business/InvitationService.java +++ b/src/main/java/treehouse/server/api/invitation/business/InvitationService.java @@ -9,11 +9,14 @@ import treehouse.server.api.invitation.presentation.dto.InvitationRequestDTO; import treehouse.server.api.invitation.presentation.dto.InvitationResponseDTO; import treehouse.server.api.member.implementation.MemberQueryAdapter; +import treehouse.server.api.notification.business.NotificationService; +import treehouse.server.api.notification.presentation.dto.NotificationRequestDTO; import treehouse.server.api.treehouse.implementation.TreehouseQueryAdapter; import treehouse.server.api.user.implement.UserQueryAdapter; import treehouse.server.global.entity.Invitation.Invitation; import treehouse.server.global.entity.User.User; import treehouse.server.global.entity.member.Member; +import treehouse.server.global.entity.notification.NotificationType; import treehouse.server.global.entity.treeHouse.TreeHouse; import treehouse.server.global.exception.GlobalErrorCode; import treehouse.server.global.exception.ThrowClass.InvitationException; @@ -38,6 +41,8 @@ public class InvitationService { private final UserQueryAdapter userQueryAdapter; + private final NotificationService notificationService; + private static final Integer treeMemberRandomProfileSize = 3; @@ -84,6 +89,13 @@ public InvitationResponseDTO.createInvitation createInvitation(User user, Invita Invitation invitation = invitationCommandAdapter.saveInvitation(InvitationMapper.toInvitation(request.getPhoneNumber(), sender, receiverUser, treehouse)); + //알림 생성 + NotificationRequestDTO.createNotification notificationRequest = new NotificationRequestDTO.createNotification(); + notificationRequest.setReceiverId(receiverUser.getId()); // 여기서 receiver 설정 (예시) + notificationRequest.setTargetId(invitation.getId()); + notificationRequest.setType(NotificationType.INVITATION); // 알림 타입 설정 (예시) + notificationService.createNotification(user, invitation.getTreeHouse().getId(), notificationRequest, null); + // 리턴하기 return InvitationMapper.toCreateInvitationDTO(invitation); diff --git a/src/main/java/treehouse/server/api/notification/business/NotificationMapper.java b/src/main/java/treehouse/server/api/notification/business/NotificationMapper.java index 221c70b..9c5e4ce 100644 --- a/src/main/java/treehouse/server/api/notification/business/NotificationMapper.java +++ b/src/main/java/treehouse/server/api/notification/business/NotificationMapper.java @@ -2,21 +2,41 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; +import treehouse.server.api.notification.presentation.dto.NotificationRequestDTO; import treehouse.server.api.notification.presentation.dto.NotificationResponseDTO; +import treehouse.server.global.common.util.TimeFormatter; import treehouse.server.global.entity.User.User; +import treehouse.server.global.entity.member.Member; import treehouse.server.global.entity.notification.Notification; +import treehouse.server.global.entity.notification.NotificationType; +import java.time.LocalDateTime; import java.util.List; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class NotificationMapper { + public static Notification toNotification(Member sender, Member receiver, NotificationRequestDTO.createNotification request, String reactionName) { + + return Notification.builder() + .sender(sender) + .receiver(receiver) + .type(request.getType()) + .targetId(request.getTargetId()) + .title(NotificationUtil.getTitle(request.getType())) + .body(NotificationUtil.getBody(request.getType(), sender, request.getTargetId(), reactionName)) + .receivedTime(LocalDateTime.now()) + .build(); + } + public static NotificationResponseDTO.getNotification toGetNotification(Notification notification, User user) { return NotificationResponseDTO.getNotification.builder() .type(notification.getType()) .profileImageUrl(notification.getSender().getProfileImageUrl()) .userName(notification.getSender().getName()) - .receivedTime(String.valueOf(notification.getReceivedTime())) + .title(notification.getTitle()) + .body(notification.getBody()) + .receivedTime(TimeFormatter.format(notification.getReceivedTime())) .treehouseId(notification.getSender().getTreeHouse().getId()) .treehouseName(notification.getSender().getTreeHouse().getName()) .isChecked(notification.isChecked()) @@ -29,4 +49,10 @@ public static NotificationResponseDTO.getNotifications toGetNotifications(List titleMap = new EnumMap<>(NotificationType.class); + private static final Map> bodyMap = new EnumMap<>(NotificationType.class); + + static { + titleMap.put(NotificationType.COMMENT, "댓글 알림"); + titleMap.put(NotificationType.REPLY, "대댓글 알림"); + titleMap.put(NotificationType.INVITATION, "초대 알림"); + titleMap.put(NotificationType.POST_REACTION, "게시글 반응 알림"); + titleMap.put(NotificationType.COMMENT_REACTION, "댓글 반응 알림"); + + bodyMap.put(NotificationType.COMMENT, (sender, targetId, reactionName) -> sender.getName() + " 님이 댓글을 남겼습니다."); + bodyMap.put(NotificationType.REPLY, (sender, targetId, reactionName) -> sender.getName() + " 님이 답글을 남겼습니다."); + bodyMap.put(NotificationType.INVITATION, (sender, targetId, reactionName) -> sender.getName() + " 님이 트리하우스에 초대했습니다."); + bodyMap.put(NotificationType.POST_REACTION, (sender, targetId, reactionName) -> sender.getName() + " 님이 게시글에 " + reactionName + "을(를) 눌렀습니다."); + bodyMap.put(NotificationType.COMMENT_REACTION, (sender, targetId, reactionName) -> sender.getName() + " 님이 댓글에 " + reactionName + "을(를) 눌렀습니다."); + } + + public static String getTitle(NotificationType type) { + return titleMap.getOrDefault(type, "Notification"); + } + + public static String getBody(NotificationType type, Member sender, Long targetId, String reactionName) { + return bodyMap.getOrDefault(type, (s, t, r) -> "알림이 있습니다.").apply(sender, targetId, reactionName); + } +} diff --git a/src/main/java/treehouse/server/api/notification/implement/NotificationCommandAdapter.java b/src/main/java/treehouse/server/api/notification/implement/NotificationCommandAdapter.java index 80df459..0e8ea8f 100644 --- a/src/main/java/treehouse/server/api/notification/implement/NotificationCommandAdapter.java +++ b/src/main/java/treehouse/server/api/notification/implement/NotificationCommandAdapter.java @@ -2,10 +2,23 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import treehouse.server.api.notification.persistence.NotificationRepository; import treehouse.server.global.annotations.Adapter; +import treehouse.server.global.entity.notification.Notification; @Adapter @RequiredArgsConstructor @Slf4j public class NotificationCommandAdapter { + + private final NotificationRepository notificationRepository; + + public void createNotification(Notification notification){ + notificationRepository.save(notification); + } + + public void readNotification(Notification notification) { + notification.setChecked(true); + notificationRepository.save(notification); + } } diff --git a/src/main/java/treehouse/server/api/notification/implement/NotificationQueryAdapter.java b/src/main/java/treehouse/server/api/notification/implement/NotificationQueryAdapter.java index a4083de..9470247 100644 --- a/src/main/java/treehouse/server/api/notification/implement/NotificationQueryAdapter.java +++ b/src/main/java/treehouse/server/api/notification/implement/NotificationQueryAdapter.java @@ -1,9 +1,19 @@ package treehouse.server.api.notification.implement; import lombok.RequiredArgsConstructor; +import treehouse.server.api.notification.persistence.NotificationRepository; import treehouse.server.global.annotations.Adapter; +import treehouse.server.global.entity.notification.Notification; +import treehouse.server.global.exception.GlobalErrorCode; +import treehouse.server.global.exception.ThrowClass.NotificationException; @Adapter @RequiredArgsConstructor public class NotificationQueryAdapter { + + private final NotificationRepository notificationRepository; + public Notification findById(Long notificationId) { + return notificationRepository.findById(notificationId) + .orElseThrow(() -> new NotificationException(GlobalErrorCode.NOTIFICATION_NOT_FOUND)); + } } diff --git a/src/main/java/treehouse/server/api/notification/presentation/NotificationApi.java b/src/main/java/treehouse/server/api/notification/presentation/NotificationApi.java index a32e3ac..bcca2e9 100644 --- a/src/main/java/treehouse/server/api/notification/presentation/NotificationApi.java +++ b/src/main/java/treehouse/server/api/notification/presentation/NotificationApi.java @@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import treehouse.server.api.notification.business.NotificationService; import treehouse.server.api.notification.presentation.dto.NotificationResponseDTO; @@ -30,4 +32,13 @@ public CommonResponse getNotifications ){ return CommonResponse.onSuccess(notificationService.getNotifications(user)); } + + @PostMapping("/users/notifications/{notificationId}") + @Operation(summary = "알림 확인 🔑 ✅", description = "사용자의 알림을 확인하여 '읽음' 상태로 변경합니다.") + public CommonResponse readNotification( + @AuthMember @Parameter(hidden = true) User user, + @PathVariable Long notificationId + ){ + return CommonResponse.onSuccess(notificationService.readNotification(user, notificationId)); + } } diff --git a/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationRequestDTO.java b/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationRequestDTO.java new file mode 100644 index 0000000..707dcd4 --- /dev/null +++ b/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationRequestDTO.java @@ -0,0 +1,23 @@ +package treehouse.server.api.notification.presentation.dto; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import treehouse.server.api.notification.business.NotificationService; +import treehouse.server.global.entity.notification.NotificationType; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class NotificationRequestDTO { + + @Getter + @Setter + public static class createNotification { + + private String title; + private String body; + private NotificationType type; + private Long targetId; + private Long receiverId; + } +} diff --git a/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationResponseDTO.java b/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationResponseDTO.java index 68887d9..9567acf 100644 --- a/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationResponseDTO.java +++ b/src/main/java/treehouse/server/api/notification/presentation/dto/NotificationResponseDTO.java @@ -14,6 +14,8 @@ public class NotificationResponseDTO { @AllArgsConstructor public static class getNotification { private NotificationType type; + private String title; + private String body; private String profileImageUrl; private String userName; private String receivedTime; @@ -30,4 +32,12 @@ public static class getNotification { public static class getNotifications { List notifications; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class readNotification { + private Long notificationId; + } } diff --git a/src/main/java/treehouse/server/api/post/business/PostService.java b/src/main/java/treehouse/server/api/post/business/PostService.java index 27cd099..42491fa 100644 --- a/src/main/java/treehouse/server/api/post/business/PostService.java +++ b/src/main/java/treehouse/server/api/post/business/PostService.java @@ -11,6 +11,8 @@ import org.springframework.transaction.annotation.Transactional; import treehouse.server.api.branch.implementation.BranchQueryAdapter; import treehouse.server.api.member.implementation.MemberQueryAdapter; +import treehouse.server.api.notification.business.NotificationService; +import treehouse.server.api.notification.presentation.dto.NotificationRequestDTO; import treehouse.server.api.post.implement.PostCommandAdapter; import treehouse.server.api.post.implement.PostImageCommandAdapter; import treehouse.server.api.post.implement.PostQueryAdapter; @@ -28,6 +30,7 @@ import treehouse.server.global.entity.User.User; import treehouse.server.global.entity.branch.Branch; import treehouse.server.global.entity.member.Member; +import treehouse.server.global.entity.notification.NotificationType; import treehouse.server.global.entity.post.Post; import treehouse.server.global.entity.post.PostImage; import treehouse.server.global.entity.reaction.Reaction; @@ -70,6 +73,8 @@ public class PostService { private final BranchQueryAdapter branchQueryAdapter; + private final NotificationService notificationService; + /** * 게시글 상세조회 * @@ -300,6 +305,13 @@ public String reactToPost(User user, Long treehouseId, Long postId, PostRequestD member.addReaction(savedReaction); + //알림 생성 + NotificationRequestDTO.createNotification notificationRequest = new NotificationRequestDTO.createNotification(); + notificationRequest.setReceiverId(post.getWriter().getId()); // 여기서 receiver 설정 (예시) + notificationRequest.setTargetId(post.getId()); + notificationRequest.setType(NotificationType.POST_REACTION); // 알림 타입 설정 (예시) + notificationService.createNotification(user, treehouseId, notificationRequest, savedReaction.getReactionName()); + return request.getReactionName() + " is saved"; } } diff --git a/src/main/java/treehouse/server/global/entity/notification/Notification.java b/src/main/java/treehouse/server/global/entity/notification/Notification.java index 85e75c5..29f3c55 100644 --- a/src/main/java/treehouse/server/global/entity/notification/Notification.java +++ b/src/main/java/treehouse/server/global/entity/notification/Notification.java @@ -1,10 +1,7 @@ package treehouse.server.global.entity.notification; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import treehouse.server.global.entity.common.BaseDateTimeEntity; import treehouse.server.global.entity.member.Member; @@ -23,6 +20,8 @@ public class Notification extends BaseDateTimeEntity { private String title; private String body; + + @Setter private boolean isChecked; @Enumerated(EnumType.STRING) diff --git a/src/main/java/treehouse/server/global/exception/ThrowClass/NotificationException.java b/src/main/java/treehouse/server/global/exception/ThrowClass/NotificationException.java new file mode 100644 index 0000000..ab235e4 --- /dev/null +++ b/src/main/java/treehouse/server/global/exception/ThrowClass/NotificationException.java @@ -0,0 +1,9 @@ +package treehouse.server.global.exception.ThrowClass; + +import treehouse.server.global.exception.BaseErrorCode; + +public class NotificationException extends GeneralException{ + public NotificationException(BaseErrorCode errorCode) { + super(errorCode); + } +}