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); + } +}