diff --git a/src/main/java/org/websoso/WSSServer/controller/FeedController.java b/src/main/java/org/websoso/WSSServer/controller/FeedController.java index 5abffe18..6abd670d 100644 --- a/src/main/java/org/websoso/WSSServer/controller/FeedController.java +++ b/src/main/java/org/websoso/WSSServer/controller/FeedController.java @@ -29,8 +29,7 @@ import org.websoso.WSSServer.dto.feed.FeedsGetResponse; import org.websoso.WSSServer.dto.feed.InterestFeedsGetResponse; import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse; -import org.websoso.WSSServer.service.FeedService; -import org.websoso.WSSServer.service.PopularFeedService; +import org.websoso.WSSServer.facade.FeedFacade; import org.websoso.WSSServer.service.UserService; @RequestMapping("/feeds") @@ -38,19 +37,15 @@ @RequiredArgsConstructor public class FeedController { - private final FeedService feedService; + private final FeedFacade feedFacade; private final UserService userService; - private final PopularFeedService popularFeedService; @PostMapping public ResponseEntity createFeed(Principal principal, - @Valid @RequestBody FeedCreateRequest request) { + @Valid @RequestBody FeedCreateRequest request) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.createFeed(user, request); - - return ResponseEntity - .status(CREATED) - .build(); + feedFacade.createFeed(user, request); + return ResponseEntity.status(CREATED).build(); } @PutMapping("/{feedId}") @@ -58,7 +53,7 @@ public ResponseEntity updateFeed(Principal principal, @PathVariable("feedId") Long feedId, @Valid @RequestBody FeedUpdateRequest request) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.updateFeed(user, feedId, request); + feedFacade.updateFeed(user, feedId, request); return ResponseEntity .status(NO_CONTENT) @@ -69,7 +64,7 @@ public ResponseEntity updateFeed(Principal principal, public ResponseEntity deleteFeed(Principal principal, @PathVariable("feedId") Long feedId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.deleteFeed(user, feedId); + feedFacade.deleteFeed(user, feedId); return ResponseEntity .status(NO_CONTENT) @@ -80,7 +75,7 @@ public ResponseEntity deleteFeed(Principal principal, public ResponseEntity likeFeed(Principal principal, @PathVariable("feedId") Long feedId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.likeFeed(user, feedId); + feedFacade.likeFeed(user, feedId); return ResponseEntity .status(NO_CONTENT) @@ -91,7 +86,7 @@ public ResponseEntity likeFeed(Principal principal, public ResponseEntity unLikeFeed(Principal principal, @PathVariable("feedId") Long feedId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.unLikeFeed(user, feedId); + feedFacade.unlikeFeed(user, feedId); return ResponseEntity .status(NO_CONTENT) @@ -105,7 +100,7 @@ public ResponseEntity getFeed(Principal principal, return ResponseEntity .status(OK) - .body(feedService.getFeedById(user, feedId)); + .body(feedFacade.getFeed(user, feedId)); } @GetMapping("/popular") @@ -115,7 +110,7 @@ public ResponseEntity getPopularFeeds(Principal princip userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) - .body(popularFeedService.getPopularFeeds(user)); + .body(feedFacade.getPopularFeeds(user)); } @GetMapping @@ -127,7 +122,7 @@ public ResponseEntity getFeeds(Principal principal, return ResponseEntity .status(OK) - .body(feedService.getFeeds(user, category, lastFeedId, size)); + .body(feedFacade.getFeeds(user, category, lastFeedId, size)); } @GetMapping("/interest") @@ -137,7 +132,7 @@ public ResponseEntity getInterestFeeds(Principal princ : userService.getUserOrException(Long.valueOf(principal.getName())); return ResponseEntity .status(OK) - .body(feedService.getInterestFeeds(user)); + .body(feedFacade.getInterestFeeds(user)); } @PostMapping("/{feedId}/comments") @@ -145,7 +140,7 @@ public ResponseEntity createComment(Principal principal, @PathVariable("feedId") Long feedId, @Valid @RequestBody CommentCreateRequest request) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.createComment(user, feedId, request); + feedFacade.createComment(user, feedId, request); return ResponseEntity .status(NO_CONTENT) @@ -158,7 +153,7 @@ public ResponseEntity updateComment(Principal principal, @PathVariable("commentId") Long commentId, @Valid @RequestBody CommentUpdateRequest request) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.updateComment(user, feedId, commentId, request); + feedFacade.updateComment(user, feedId, commentId, request); return ResponseEntity .status(NO_CONTENT) @@ -170,7 +165,7 @@ public ResponseEntity deleteComment(Principal principal, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.deleteComment(user, feedId, commentId); + feedFacade.deleteComment(user, feedId, commentId); return ResponseEntity .status(NO_CONTENT) @@ -184,14 +179,14 @@ public ResponseEntity getComments(Principal principal, return ResponseEntity .status(OK) - .body(feedService.getComments(user, feedId)); + .body(feedFacade.getComments(user, feedId)); } @PostMapping("/{feedId}/spoiler") public ResponseEntity reportFeedSpoiler(Principal principal, @PathVariable("feedId") Long feedId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.reportFeed(user, feedId, SPOILER); + feedFacade.reportFeed(user, feedId, SPOILER); return ResponseEntity .status(CREATED) @@ -202,7 +197,7 @@ public ResponseEntity reportFeedSpoiler(Principal principal, public ResponseEntity reportedFeedImpertinence(Principal principal, @PathVariable("feedId") Long feedId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.reportFeed(user, feedId, IMPERTINENCE); + feedFacade.reportFeed(user, feedId, IMPERTINENCE); return ResponseEntity .status(CREATED) @@ -214,7 +209,7 @@ public ResponseEntity reportCommentSpoiler(Principal principal, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.reportComment(user, feedId, commentId, SPOILER); + feedFacade.reportComment(user, feedId, commentId, SPOILER); return ResponseEntity .status(CREATED) @@ -226,7 +221,7 @@ public ResponseEntity reportCommentImpertinence(Principal principal, @PathVariable("feedId") Long feedId, @PathVariable("commentId") Long commentId) { User user = userService.getUserOrException(Long.valueOf(principal.getName())); - feedService.reportComment(user, feedId, commentId, IMPERTINENCE); + feedFacade.reportComment(user, feedId, commentId, IMPERTINENCE); return ResponseEntity .status(CREATED) diff --git a/src/main/java/org/websoso/WSSServer/domain/Feed.java b/src/main/java/org/websoso/WSSServer/domain/Feed.java index 2d535526..104b7e61 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Feed.java +++ b/src/main/java/org/websoso/WSSServer/domain/Feed.java @@ -101,8 +101,12 @@ public boolean isNovelChanged(Long novelId) { return !Objects.equals(this.novelId, novelId); } - public void hideFeed() { + public void hide() { this.isHidden = true; } + public void spoiler() { + this.isSpoiler = true; + } + } diff --git a/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java b/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java index 96a0989f..ad8fe77d 100644 --- a/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java +++ b/src/main/java/org/websoso/WSSServer/exception/error/CustomFeedError.java @@ -19,9 +19,10 @@ public enum CustomFeedError implements ICustomError { LIKE_USER_NOT_FOUND("FEED-003", "해당 사용자가 이 피드에 좋아요를 누르지 않았습니다.", NOT_FOUND), INVALID_LIKE_COUNT("FEED-004", "좋아요 수가 유효하지 않습니다.", BAD_REQUEST), HIDDEN_FEED_ACCESS("FEED-005", "이 피드는 숨겨져 있어 접근할 수 없습니다.", FORBIDDEN), - BLOCKED_USER_ACCESS("FEED-006", "해당 사용자와 피드 작성자가 차단 상태이므로 이 피드에 접근할 수 없습니다.", FORBIDDEN), + BLOCKED_USER_ACCESS("FEED-006", "차단한 사용자의 피드에 접근할 수 없습니다.", FORBIDDEN), SELF_REPORT_NOT_ALLOWED("FEED-007", "자신의 피드를 신고할 수 없습니다.", BAD_REQUEST), - ALREADY_REPORTED_FEED("FEED-008", "이미 사용자가 신고한 피드입니다.", CONFLICT); + ALREADY_REPORTED_FEED("FEED-008", "이미 사용자가 신고한 피드입니다.", CONFLICT), + NOT_LIKED("FEED-009", "사용자가 해당 피드에 좋아요를 누르지 않았습니다.", BAD_REQUEST); private final String code; private final String description; diff --git a/src/main/java/org/websoso/WSSServer/facade/FeedFacade.java b/src/main/java/org/websoso/WSSServer/facade/FeedFacade.java new file mode 100644 index 00000000..55b8d64f --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/facade/FeedFacade.java @@ -0,0 +1,187 @@ +package org.websoso.WSSServer.facade; + +import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.REPORT; +import static org.websoso.WSSServer.exception.error.CustomFeedError.BLOCKED_USER_ACCESS; +import static org.websoso.WSSServer.exception.error.CustomFeedError.HIDDEN_FEED_ACCESS; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.Comment; +import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.common.DiscordWebhookMessage; +import org.websoso.WSSServer.domain.common.ReportedType; +import org.websoso.WSSServer.dto.comment.CommentCreateRequest; +import org.websoso.WSSServer.dto.comment.CommentUpdateRequest; +import org.websoso.WSSServer.dto.comment.CommentsGetResponse; +import org.websoso.WSSServer.dto.feed.FeedCreateRequest; +import org.websoso.WSSServer.dto.feed.FeedGetResponse; +import org.websoso.WSSServer.dto.feed.FeedUpdateRequest; +import org.websoso.WSSServer.dto.feed.FeedsGetResponse; +import org.websoso.WSSServer.dto.feed.InterestFeedsGetResponse; +import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.service.BlockService; +import org.websoso.WSSServer.service.CommentService; +import org.websoso.WSSServer.service.FeedService; +import org.websoso.WSSServer.service.LikeService; +import org.websoso.WSSServer.service.MessageFormatter; +import org.websoso.WSSServer.service.MessageService; +import org.websoso.WSSServer.service.NotificationService; +import org.websoso.WSSServer.service.PopularFeedService; +import org.websoso.WSSServer.service.ReportService; +import org.websoso.WSSServer.service.UserService; + +@Component +@RequiredArgsConstructor +public class FeedFacade { + private static final int POPULAR_FEED_LIKE_THRESHOLD = 5; + private final FeedService feedService; + private final PopularFeedService popularFeedService; + private final LikeService likeService; + private final CommentService commentService; + private final ReportService reportService; + private final MessageService messageService; + private final NotificationService notificationService; + private final UserService userService; + private final BlockService blockService; + + public void createFeed(User user, FeedCreateRequest request) { + feedService.createFeed(user, request); + } + + @Transactional(readOnly = true) + public FeedGetResponse getFeed(User user, Long feedId) { + return feedService.getFeedById(user, feedId); + } + + @Transactional(readOnly = true) + public FeedsGetResponse getFeeds(User user, String category, Long lastFeedId, int size) { + return feedService.getFeeds(user, category, lastFeedId, size); + } + + public void updateFeed(User user, Long feedId, FeedUpdateRequest request) { + feedService.updateFeed(user, feedId, request); + } + + public void deleteFeed(User user, Long feedId) { + feedService.deleteFeed(user, feedId); + } + + public void reportFeed(User user, Long feedId, ReportedType reportedType) { + Feed feed = getFeedOrException(feedId); + + validate(feed, user.getUserId(), feed.getUser().getUserId()); + + int reportedCount = reportService.reportFeed(user, feed, reportedType); + messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( + MessageFormatter.formatFeedReportMessage(user, feed, reportedType, reportedCount), REPORT)); + } + + public void reportComment(User reporteCreatedUser, Long feedId, Long commentId, ReportedType reportedType) { + Feed feed = getFeedOrException(feedId); + Comment comment = commentService.getCommentOrException(commentId); + + validate(feed, reporteCreatedUser.getUserId(), comment.getUserId()); + + User commentCreatedUser = userService.getUserOrException(comment.getUserId()); + int reportedCount = reportService.reportComment(reporteCreatedUser, getFeedOrException(feedId), comment, + commentCreatedUser, reportedType); + messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( + MessageFormatter.formatCommentReportMessage(reporteCreatedUser, feed, comment, reportedType, + commentCreatedUser, reportedCount), REPORT)); + } + + @Transactional(readOnly = true) + public PopularFeedsGetResponse getPopularFeeds(User user) { + return popularFeedService.getPopularFeeds(user); + } + + @Transactional(readOnly = true) + public InterestFeedsGetResponse getInterestFeeds(User user) { + return feedService.getInterestFeeds(user); + } + + public void likeFeed(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + validate(feed, feed.getUser().getUserId(), user.getUserId()); + + likeService.createLike(user, feed); + + if (feed.getLikes().size() == POPULAR_FEED_LIKE_THRESHOLD) { + popularFeedService.createPopularFeed(feed); + notificationService.sendPopularFeedPushMessage(feed); + } + + if (blockService.isBlocked(user.getUserId(), feed.getFeedId())) { + return; + } + notificationService.sendLikePushMessage(user, feed); + } + + public void unlikeFeed(User user, Long feedId) { + likeService.deleteLike(user, getFeedOrException(feedId)); + } + + public void createComment(User user, Long feedId, CommentCreateRequest request) { + Feed feed = getFeedOrException(feedId); + validate(feed, user.getUserId(), feed.getUser().getUserId()); + commentService.createComment(user, getFeedOrException(feedId), request.commentContent()); + sendCommentPushMessage(user, feed); + } + + @Transactional(readOnly = true) + public CommentsGetResponse getComments(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + validate(feed, user.getUserId(), feed.getUser().getUserId()); + return commentService.getComments(user, getFeedOrException(feedId)); + } + + public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { + Feed feed = getFeedOrException(feedId); + validate(feed, user.getUserId(), feed.getUser().getUserId()); + commentService.updateComment(user.getUserId(), getFeedOrException(feedId), commentId, request.commentContent()); + } + + public void deleteComment(User user, Long feedId, Long commentId) { + Feed feed = getFeedOrException(feedId); + validate(feed, user.getUserId(), feed.getUser().getUserId()); + commentService.deleteComment(user.getUserId(), getFeedOrException(feedId), commentId); + } + + private Feed getFeedOrException(Long feedId) { + return feedService.getFeedOrException(feedId); + } + + private void validate(Feed feed, Long blockingId, Long blockedId) { + checkHiddenFeed(feed); + checkBlocked(blockingId, blockedId); + } + + private void checkHiddenFeed(Feed feed) { + if (feed.getIsHidden()) { + throw new CustomFeedException(HIDDEN_FEED_ACCESS, "Cannot access hidden feed."); + } + } + + private void checkBlocked(Long blockingId, Long blockedId) { + if (blockService.isBlocked(blockingId, blockedId)) { + throw new CustomFeedException(BLOCKED_USER_ACCESS, + "cannot access this feed because either you or the feed author has blocked the other."); + } + } + + private void sendCommentPushMessage(User user, Feed feed) { + User feedOwner = feed.getUser(); + notificationService.sendCommentPushMessageToCommenters(user, feed); + if (isUserCommentOwner(user, feedOwner) || blockService.isBlocked(feedOwner.getUserId(), user.getUserId())) { + return; + } + notificationService.sendCommentPushMessageToFeedOwner(user, feed); + } + + private boolean isUserCommentOwner(User createdUser, User user) { + return createdUser.equals(user); + } +} diff --git a/src/main/java/org/websoso/WSSServer/service/CommentService.java b/src/main/java/org/websoso/WSSServer/service/CommentService.java index 8886721d..389fd007 100644 --- a/src/main/java/org/websoso/WSSServer/service/CommentService.java +++ b/src/main/java/org/websoso/WSSServer/service/CommentService.java @@ -1,13 +1,8 @@ package org.websoso.WSSServer.service; -import static java.lang.Boolean.TRUE; import static org.websoso.WSSServer.domain.common.Action.DELETE; import static org.websoso.WSSServer.domain.common.Action.UPDATE; -import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.REPORT; -import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE; -import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_FOUND; -import static org.websoso.WSSServer.exception.error.CustomCommentError.SELF_REPORT_NOT_ALLOWED; import java.util.AbstractMap; import java.util.List; @@ -16,169 +11,25 @@ import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Comment; import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Notification; -import org.websoso.WSSServer.domain.NotificationType; -import org.websoso.WSSServer.domain.Novel; import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; -import org.websoso.WSSServer.domain.common.DiscordWebhookMessage; -import org.websoso.WSSServer.domain.common.ReportedType; import org.websoso.WSSServer.dto.comment.CommentGetResponse; import org.websoso.WSSServer.dto.comment.CommentsGetResponse; import org.websoso.WSSServer.dto.user.UserBasicInfo; import org.websoso.WSSServer.exception.exception.CustomCommentException; -import org.websoso.WSSServer.notification.FCMService; -import org.websoso.WSSServer.notification.dto.FCMMessageRequest; import org.websoso.WSSServer.repository.CommentRepository; -import org.websoso.WSSServer.repository.NotificationRepository; -import org.websoso.WSSServer.repository.NotificationTypeRepository; @Service @RequiredArgsConstructor @Transactional public class CommentService { - private final CommentRepository commentRepository; private final UserService userService; private final AvatarService avatarService; private final BlockService blockService; - private final ReportedCommentService reportedCommentService; - private final MessageService messageService; - private final FCMService fcmService; - private final NovelService novelService; - private final NotificationTypeRepository notificationTypeRepository; - private final NotificationRepository notificationRepository; + private final CommentRepository commentRepository; public void createComment(User user, Feed feed, String commentContent) { commentRepository.save(Comment.create(user.getUserId(), feed, commentContent)); - sendCommentPushMessageToFeedOwner(user, feed); - sendCommentPushMessageToCommenters(user, feed); - } - - private void sendCommentPushMessageToFeedOwner(User user, Feed feed) { - User feedOwner = feed.getUser(); - if (isUserCommentOwner(user, feedOwner) || blockService.isBlocked(feedOwner.getUserId(), user.getUserId())) { - return; - } - - NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("댓글"); - - String notificationTitle = createNotificationTitle(feed); - String notificationBody = String.format("%s님이 내 수다글에 댓글을 남겼어요.", user.getNickname()); - Long feedId = feed.getFeedId(); - - Notification notification = Notification.create( - notificationTitle, - notificationBody, - null, - feedOwner.getUserId(), - feedId, - notificationTypeComment - ); - notificationRepository.save(notification); - - if (!TRUE.equals(feedOwner.getIsPushEnabled())) { - return; - } - - List feedOwnerDevices = feedOwner.getUserDevices(); - if (feedOwnerDevices.isEmpty()) { - return; - } - - FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( - notificationTitle, - notificationBody, - String.valueOf(feedId), - "feedDetail", - String.valueOf(notification.getNotificationId()) - ); - - List targetFCMTokens = feedOwnerDevices - .stream() - .map(UserDevice::getFcmToken) - .toList(); - - fcmService.sendMulticastPushMessage( - targetFCMTokens, - fcmMessageRequest - ); - } - - private String createNotificationTitle(Feed feed) { - if (feed.getNovelId() == null) { - String feedContent = feed.getFeedContent(); - feedContent = feedContent.length() <= 12 - ? feedContent - : feedContent.substring(0, 12); - return "'" + feedContent + "...'"; - } - Novel novel = novelService.getNovelOrException(feed.getNovelId()); - return novel.getTitle(); - } - - private void sendCommentPushMessageToCommenters(User user, Feed feed) { - User feedOwner = feed.getUser(); - - List commenters = feed.getComments() - .stream() - .map(Comment::getUserId) - .filter(userId -> !userId.equals(user.getUserId())) - .filter(userId -> !userId.equals(feedOwner.getUserId())) - .filter(userId -> !blockService.isBlocked(userId, user.getUserId()) - && !blockService.isBlocked(userId, feed.getUser().getUserId())) - .distinct() - .map(userService::getUserOrException) - .toList(); - - if (commenters.isEmpty()) { - return; - } - - NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("댓글"); - - String notificationTitle = createNotificationTitle(feed); - String notificationBody = "내가 댓글 단 수다글에 또 다른 댓글이 달렸어요."; - Long feedId = feed.getFeedId(); - - commenters.forEach(commenter -> { - Notification notification = Notification.create( - notificationTitle, - notificationBody, - null, - commenter.getUserId(), - feedId, - notificationTypeComment - ); - notificationRepository.save(notification); - - if (!TRUE.equals(commenter.getIsPushEnabled())) { - return; - } - - List commenterDevices = commenter.getUserDevices(); - if (commenterDevices.isEmpty()) { - return; - } - - List targetFCMTokens = commenterDevices - .stream() - .map(UserDevice::getFcmToken) - .distinct() - .toList(); - - FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( - notificationTitle, - notificationBody, - String.valueOf(feedId), - "feedDetail", - String.valueOf(notification.getNotificationId()) - ); - fcmService.sendMulticastPushMessage( - targetFCMTokens, - fcmMessageRequest - ); - }); } public void updateComment(Long userId, Feed feed, Long commentId, String commentContent) { @@ -213,37 +64,7 @@ public CommentsGetResponse getComments(User user, Feed feed) { return CommentsGetResponse.of(responses); } - public void createReportedComment(Feed feed, Long commentId, User user, ReportedType reportedType) { - Comment comment = getCommentOrException(commentId); - - comment.validateFeedAssociation(feed); - - User commentCreatedUser = userService.getUserOrException(comment.getUserId()); - - if (isUserCommentOwner(commentCreatedUser, user)) { - throw new CustomCommentException(SELF_REPORT_NOT_ALLOWED, "cannot report own comment"); - } - - reportedCommentService.createReportedComment(comment, user, reportedType); - - int reportedCount = reportedCommentService.getReportedCountByReportedType(comment, reportedType); - boolean shouldHide = reportedType.isExceedingLimit(reportedCount); - - if (shouldHide) { - if (reportedType.equals(SPOILER)) { - comment.spoiler(); - } else if (reportedType.equals(IMPERTINENCE)) { - comment.hideComment(); - } - } - - messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( - MessageFormatter.formatCommentReportMessage(user, feed, comment, reportedType, - commentCreatedUser, reportedCount, - shouldHide), REPORT)); - } - - private Comment getCommentOrException(Long commentId) { + public Comment getCommentOrException(Long commentId) { return commentRepository.findById(commentId).orElseThrow(() -> new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); } diff --git a/src/main/java/org/websoso/WSSServer/service/FeedService.java b/src/main/java/org/websoso/WSSServer/service/FeedService.java index 140570c9..c7d4d499 100644 --- a/src/main/java/org/websoso/WSSServer/service/FeedService.java +++ b/src/main/java/org/websoso/WSSServer/service/FeedService.java @@ -1,13 +1,10 @@ package org.websoso.WSSServer.service; -import static java.lang.Boolean.TRUE; import static org.websoso.WSSServer.domain.common.Action.DELETE; import static org.websoso.WSSServer.domain.common.Action.UPDATE; -import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.REPORT; import static org.websoso.WSSServer.exception.error.CustomFeedError.BLOCKED_USER_ACCESS; import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomFeedError.HIDDEN_FEED_ACCESS; -import static org.websoso.WSSServer.exception.error.CustomFeedError.SELF_REPORT_NOT_ALLOWED; import static org.websoso.WSSServer.exception.error.CustomUserError.PRIVATE_PROFILE_STATUS; import java.util.ArrayList; @@ -24,17 +21,9 @@ import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Avatar; import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Notification; -import org.websoso.WSSServer.domain.NotificationType; import org.websoso.WSSServer.domain.Novel; import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.common.DiscordWebhookMessage; -import org.websoso.WSSServer.domain.common.ReportedType; -import org.websoso.WSSServer.dto.comment.CommentCreateRequest; -import org.websoso.WSSServer.dto.comment.CommentUpdateRequest; -import org.websoso.WSSServer.dto.comment.CommentsGetResponse; import org.websoso.WSSServer.dto.feed.FeedCreateRequest; import org.websoso.WSSServer.dto.feed.FeedGetResponse; import org.websoso.WSSServer.dto.feed.FeedInfo; @@ -48,12 +37,8 @@ import org.websoso.WSSServer.dto.user.UserBasicInfo; import org.websoso.WSSServer.exception.exception.CustomFeedException; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.notification.FCMService; -import org.websoso.WSSServer.notification.dto.FCMMessageRequest; import org.websoso.WSSServer.repository.AvatarRepository; import org.websoso.WSSServer.repository.FeedRepository; -import org.websoso.WSSServer.repository.NotificationRepository; -import org.websoso.WSSServer.repository.NotificationTypeRepository; import org.websoso.WSSServer.repository.NovelRepository; import org.websoso.WSSServer.repository.UserNovelRepository; @@ -64,24 +49,22 @@ public class FeedService { private static final String DEFAULT_CATEGORY = "all"; private static final int DEFAULT_PAGE_NUMBER = 0; - private static final int POPULAR_FEED_LIKE_THRESHOLD = 5; - private final FeedRepository feedRepository; private final FeedCategoryService feedCategoryService; private final NovelService novelService; private final AvatarService avatarService; private final BlockService blockService; private final LikeService likeService; - private final PopularFeedService popularFeedService; + private final FeedRepository feedRepository; private final UserNovelRepository userNovelRepository; private final AvatarRepository avatarRepository; - private final CommentService commentService; - private final ReportedFeedService reportedFeedService; - private final MessageService messageService; private final UserService userService; private final NovelRepository novelRepository; - private final FCMService fcmService; - private final NotificationTypeRepository notificationTypeRepository; - private final NotificationRepository notificationRepository; + + @Transactional(readOnly = true) + public Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId).orElseThrow(() -> + new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } public void createFeed(User user, FeedCreateRequest request) { if (request.novelId() != null) { @@ -97,105 +80,6 @@ public void createFeed(User user, FeedCreateRequest request) { feedCategoryService.createFeedCategory(feed, request.relevantCategories()); } - public void updateFeed(User user, Long feedId, FeedUpdateRequest request) { - Feed feed = getFeedOrException(feedId); - feed.validateUserAuthorization(user, UPDATE); - - if (request.novelId() != null && feed.isNovelChanged(request.novelId())) { - novelService.getNovelOrException(request.novelId()); - } - feed.updateFeed(request.feedContent(), request.isSpoiler(), request.novelId()); - feedCategoryService.updateFeedCategory(feed, request.relevantCategories()); - } - - public void deleteFeed(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - feed.validateUserAuthorization(user, DELETE); - feedRepository.delete(feed); - } - - public void likeFeed(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - - likeService.createLike(user, feed); - - if (feed.getLikes().size() == POPULAR_FEED_LIKE_THRESHOLD) { - popularFeedService.createPopularFeed(feed); - } - - sendLikePushMessage(user, feed); - } - - private void sendLikePushMessage(User liker, Feed feed) { - User feedOwner = feed.getUser(); - if (liker.equals(feedOwner) || blockService.isBlocked(feedOwner.getUserId(), liker.getUserId())) { - return; - } - - NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("좋아요"); - - String notificationTitle = createNotificationTitle(feed); - String notificationBody = String.format("%s님이 내 수다글을 좋아해요.", liker.getNickname()); - Long feedId = feed.getFeedId(); - - Notification notification = Notification.create( - notificationTitle, - notificationBody, - null, - feedOwner.getUserId(), - feedId, - notificationTypeComment - ); - notificationRepository.save(notification); - - if (!TRUE.equals(feedOwner.getIsPushEnabled())) { - return; - } - - List feedOwnerDevices = feedOwner.getUserDevices(); - if (feedOwnerDevices.isEmpty()) { - return; - } - - FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( - notificationTitle, - notificationBody, - String.valueOf(feedId), - "feedDetail", - String.valueOf(notification.getNotificationId()) - ); - - List targetFCMTokens = feedOwnerDevices - .stream() - .map(UserDevice::getFcmToken) - .toList(); - fcmService.sendMulticastPushMessage( - targetFCMTokens, - fcmMessageRequest - ); - } - - private String createNotificationTitle(Feed feed) { - if (feed.getNovelId() == null) { - String feedContent = feed.getFeedContent(); - feedContent = feedContent.length() <= 12 - ? feedContent - : feedContent.substring(0, 12); - return "'" + feedContent + "...'"; - } - Novel novel = novelService.getNovelOrException(feed.getNovelId()); - return novel.getTitle(); - } - - public void unLikeFeed(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - likeService.deleteLike(user, feed); - } - @Transactional(readOnly = true) public FeedGetResponse getFeedById(User user, Long feedId) { Feed feed = getFeedOrException(feedId); @@ -223,66 +107,21 @@ public FeedsGetResponse getFeeds(User user, String category, Long lastFeedId, in feedGetResponses); } - public void createComment(User user, Long feedId, CommentCreateRequest request) { - Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); - commentService.createComment(user, feed, request.commentContent()); - } - - public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { - Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); - commentService.updateComment(user.getUserId(), feed, commentId, request.commentContent()); - } - - public void deleteComment(User user, Long feedId, Long commentId) { - Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); - commentService.deleteComment(user.getUserId(), feed, commentId); - } - - @Transactional(readOnly = true) - public CommentsGetResponse getComments(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - validateFeedAccess(feed, user); - return commentService.getComments(user, feed); - } - - public void reportFeed(User user, Long feedId, ReportedType reportedType) { + public void updateFeed(User user, Long feedId, FeedUpdateRequest request) { Feed feed = getFeedOrException(feedId); + feed.validateUserAuthorization(user, UPDATE); - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - - if (isUserFeedOwner(feed.getUser(), user)) { - throw new CustomFeedException(SELF_REPORT_NOT_ALLOWED, "cannot report own feed"); - } - - reportedFeedService.createReportedFeed(feed, user, reportedType); - - int reportedCount = reportedFeedService.getReportedCountByReportedType(feed, reportedType); - boolean shouldHide = reportedType.isExceedingLimit(reportedCount); - - if (shouldHide) { - feed.hideFeed(); + if (request.novelId() != null && feed.isNovelChanged(request.novelId())) { + novelService.getNovelOrException(request.novelId()); } - - messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( - MessageFormatter.formatFeedReportMessage(user, feed, reportedType, reportedCount, shouldHide), REPORT)); + feed.updateFeed(request.feedContent(), request.isSpoiler(), request.novelId()); + feedCategoryService.updateFeedCategory(feed, request.relevantCategories()); } - public void reportComment(User user, Long feedId, Long commentId, ReportedType reportedType) { + public void deleteFeed(User user, Long feedId) { Feed feed = getFeedOrException(feedId); - - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - - commentService.createReportedComment(feed, commentId, user, reportedType); - } - - private Feed getFeedOrException(Long feedId) { - return feedRepository.findById(feedId).orElseThrow(() -> - new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + feed.validateUserAuthorization(user, DELETE); + feedRepository.delete(feed); } private void checkHiddenFeed(Feed feed) { @@ -390,14 +229,6 @@ public NovelGetResponseFeedTab getFeedsByNovel(User user, Long novelId, Long las return NovelGetResponseFeedTab.of(feeds.hasNext(), feedGetResponses); } - private void validateFeedAccess(Feed feed, User user) { - if (feed.getUser().equals(user)) { - return; - } - checkHiddenFeed(feed); - checkBlocked(feed.getUser(), user); - } - @Transactional(readOnly = true) public UserFeedsGetResponse getUserFeeds(User visitor, Long ownerId, Long lastFeedId, int size) { User owner = userService.getUserOrException(ownerId); diff --git a/src/main/java/org/websoso/WSSServer/service/LikeService.java b/src/main/java/org/websoso/WSSServer/service/LikeService.java index d728dc53..509b7393 100644 --- a/src/main/java/org/websoso/WSSServer/service/LikeService.java +++ b/src/main/java/org/websoso/WSSServer/service/LikeService.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.service; import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_LIKED; - +import static org.websoso.WSSServer.exception.error.CustomFeedError.NOT_LIKED; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +26,9 @@ public void createLike(User user, Feed feed) { } public void deleteLike(User user, Feed feed) { + if (!isUserLikedFeed(user, feed)) { + throw new CustomFeedException(NOT_LIKED, "user has not liked that feed"); + } likeRepository.deleteByUserIdAndFeed(user.getUserId(), feed); } diff --git a/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java b/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java index 35403fdb..be452ce3 100644 --- a/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java +++ b/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java @@ -16,9 +16,8 @@ public class MessageFormatter { - public static String formatFeedReportMessage(User user, Feed feed, ReportedType reportedType, int reportedCount, - boolean isHidden) { - String hiddenMessage = isHidden + public static String formatFeedReportMessage(User user, Feed feed, ReportedType reportedType, int reportedCount) { + String hiddenMessage = reportedCount >= 3 ? "해당 수다는 숨김 처리되었습니다." : "해당 수다는 숨김 처리되지 않았습니다."; return String.format( @@ -37,9 +36,9 @@ public static String formatFeedReportMessage(User user, Feed feed, ReportedType public static String formatCommentReportMessage(User user, Feed feed, Comment comment, ReportedType reportedType, User commentCreatedUser, - int reportedCount, boolean isHidden) { + int reportedCount) { String hiddenMessage = "해당 댓글은 현재 숨김 처리되지 않은 상태입니다."; - if (isHidden) { + if (reportedCount >= 3) { if (reportedType.equals(SPOILER)) { hiddenMessage = "해당 댓글은 스포일러 댓글로 지정되었습니다."; } else if (reportedType.equals(IMPERTINENCE)) { diff --git a/src/main/java/org/websoso/WSSServer/service/NotificationService.java b/src/main/java/org/websoso/WSSServer/service/NotificationService.java index a8d617b8..d2b5ba0d 100644 --- a/src/main/java/org/websoso/WSSServer/service/NotificationService.java +++ b/src/main/java/org/websoso/WSSServer/service/NotificationService.java @@ -1,13 +1,39 @@ package org.websoso.WSSServer.service; +import static java.lang.Boolean.TRUE; +import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.FEED; +import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.NOTICE; +import static org.websoso.WSSServer.domain.common.Role.ADMIN; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_ADMIN_ONLY; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_ALREADY_READ; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_NOT_NOTICE_TYPE; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_READ_FORBIDDEN; +import static org.websoso.WSSServer.exception.error.CustomNotificationError.NOTIFICATION_TYPE_INVALID; +import static org.websoso.WSSServer.exception.error.CustomNotificationTypeError.NOTIFICATION_TYPE_NOT_FOUND; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.*; +import org.websoso.WSSServer.domain.Comment; +import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.NotificationType; +import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.domain.ReadNotification; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.UserDevice; import org.websoso.WSSServer.domain.common.NotificationTypeGroup; -import org.websoso.WSSServer.dto.notification.*; +import org.websoso.WSSServer.dto.notification.NotificationCreateRequest; +import org.websoso.WSSServer.dto.notification.NotificationGetResponse; +import org.websoso.WSSServer.dto.notification.NotificationInfo; +import org.websoso.WSSServer.dto.notification.NotificationsGetResponse; +import org.websoso.WSSServer.dto.notification.NotificationsReadStatusGetResponse; import org.websoso.WSSServer.exception.exception.CustomNotificationException; import org.websoso.WSSServer.exception.exception.CustomNotificationTypeException; import org.websoso.WSSServer.notification.FCMService; @@ -17,28 +43,20 @@ import org.websoso.WSSServer.repository.ReadNotificationRepository; import org.websoso.WSSServer.repository.UserRepository; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.FEED; -import static org.websoso.WSSServer.domain.common.NotificationTypeGroup.NOTICE; -import static org.websoso.WSSServer.domain.common.Role.ADMIN; -import static org.websoso.WSSServer.exception.error.CustomNotificationError.*; -import static org.websoso.WSSServer.exception.error.CustomNotificationTypeError.NOTIFICATION_TYPE_NOT_FOUND; - @Service @RequiredArgsConstructor @Transactional public class NotificationService { private static final int DEFAULT_PAGE_NUMBER = 0; + private final FCMService fcmService; + private final UserService userService; + private final NovelService novelService; + private final BlockService blockService; private final NotificationRepository notificationRepository; private final ReadNotificationRepository readNotificationRepository; private final NotificationTypeRepository notificationTypeRepository; private final UserRepository userRepository; - private final FCMService fcmService; - private final UserService userService; @Transactional(readOnly = true) public NotificationsReadStatusGetResponse checkNotificationsReadStatus(User user) { @@ -77,6 +95,228 @@ public void createNotificationAsRead(User user, Long notificationId) { readNotificationRepository.save(ReadNotification.create(notification, user)); } + public void createNoticeNotification(User user, NotificationCreateRequest request) { + validateAdminPrivilege(user); + validateNoticeType(request.notificationTypeName()); + + Notification notification = notificationRepository.save(Notification.create( + request.notificationTitle(), + request.notificationBody(), + request.notificationDetail(), + request.userId(), + null, + getNotificationTypeOrException(request.notificationTypeName())) + ); + + sendNoticePushMessage(request.userId(), notification); + } + + public void sendLikePushMessage(User liker, Feed feed) { + User feedOwner = feed.getUser(); + if (liker.equals(feedOwner)) { + return; + } + + NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("좋아요"); + + String notificationTitle = createNotificationTitle(feed); + String notificationBody = String.format("%s님이 내 수다글을 좋아해요.", liker.getNickname()); + Long feedId = feed.getFeedId(); + + Notification notification = Notification.create( + notificationTitle, + notificationBody, + null, + feedOwner.getUserId(), + feedId, + notificationTypeComment + ); + notificationRepository.save(notification); + + if (!TRUE.equals(feedOwner.getIsPushEnabled())) { + return; + } + + List feedOwnerDevices = feedOwner.getUserDevices(); + if (feedOwnerDevices.isEmpty()) { + return; + } + + FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( + notificationTitle, + notificationBody, + String.valueOf(feedId), + "feedDetail", + String.valueOf(notification.getNotificationId()) + ); + + List targetFCMTokens = feedOwnerDevices + .stream() + .map(UserDevice::getFcmToken) + .distinct() + .toList(); + fcmService.sendMulticastPushMessage( + targetFCMTokens, + fcmMessageRequest + ); + } + + public void sendCommentPushMessageToFeedOwner(User user, Feed feed) { + NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("댓글"); + User feedOwner = feed.getUser(); + + String notificationTitle = createNotificationTitle(feed); + String notificationBody = String.format("%s님이 내 수다글에 댓글을 남겼어요.", user.getNickname()); + Long feedId = feed.getFeedId(); + + Notification notification = Notification.create( + notificationTitle, + notificationBody, + null, + feedOwner.getUserId(), + feedId, + notificationTypeComment + ); + notificationRepository.save(notification); + + if (!TRUE.equals(feedOwner.getIsPushEnabled())) { + return; + } + + List feedOwnerDevices = feedOwner.getUserDevices(); + if (feedOwnerDevices.isEmpty()) { + return; + } + + FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( + notificationTitle, + notificationBody, + String.valueOf(feedId), + "feedDetail", + String.valueOf(notification.getNotificationId()) + ); + + List targetFCMTokens = feedOwnerDevices + .stream() + .map(UserDevice::getFcmToken) + .distinct() + .toList(); + + fcmService.sendMulticastPushMessage( + targetFCMTokens, + fcmMessageRequest + ); + } + + public void sendCommentPushMessageToCommenters(User user, Feed feed) { + User feedOwner = feed.getUser(); + List commenters = feed.getComments() + .stream() + .map(Comment::getUserId) + .filter(userId -> !userId.equals(user.getUserId())) + .filter(userId -> !userId.equals(feedOwner.getUserId())) + .filter(userId -> !blockService.isBlocked(userId, user.getUserId()) + && !blockService.isBlocked(userId, feed.getUser().getUserId())) + .distinct() + .map(userService::getUserOrException) + .toList(); + + if (commenters.isEmpty()) { + return; + } + + NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("댓글"); + + String notificationTitle = createNotificationTitle(feed); + String notificationBody = "내가 댓글 단 수다글에 또 다른 댓글이 달렸어요."; + Long feedId = feed.getFeedId(); + + commenters.forEach(commenter -> { + Notification notification = Notification.create( + notificationTitle, + notificationBody, + null, + commenter.getUserId(), + feedId, + notificationTypeComment + ); + notificationRepository.save(notification); + + if (!TRUE.equals(commenter.getIsPushEnabled())) { + return; + } + + List commenterDevices = commenter.getUserDevices(); + if (commenterDevices.isEmpty()) { + return; + } + + List targetFCMTokens = commenterDevices + .stream() + .map(UserDevice::getFcmToken) + .distinct() + .toList(); + + FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( + notificationTitle, + notificationBody, + String.valueOf(feedId), + "feedDetail", + String.valueOf(notification.getNotificationId()) + ); + fcmService.sendMulticastPushMessage( + targetFCMTokens, + fcmMessageRequest + ); + }); + } + + public void sendPopularFeedPushMessage(Feed feed) { + NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("지금뜨는수다글"); + + User feedOwner = feed.getUser(); + Long feedId = feed.getFeedId(); + String notificationTitle = "지금 뜨는 수다글 등극\uD83D\uDE4C"; + String notificationBody = createNotificationBody(feed); + + Notification notification = Notification.create( + notificationTitle, + notificationBody, + null, + feedOwner.getUserId(), + feedId, + notificationTypeComment + ); + notificationRepository.save(notification); + + if (!TRUE.equals(feedOwner.getIsPushEnabled())) { + return; + } + + List feedOwnerDevices = feedOwner.getUserDevices(); + if (feedOwnerDevices.isEmpty()) { + return; + } + + FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( + notificationTitle, + notificationBody, + String.valueOf(feedId), + "feedDetail", + String.valueOf(notification.getNotificationId()) + ); + + List targetFCMTokens = feedOwnerDevices + .stream() + .map(UserDevice::getFcmToken) + .distinct() + .toList(); + fcmService.sendMulticastPushMessage( + targetFCMTokens, + fcmMessageRequest + ); + } + private Notification getAndValidateNotification(User user, Long notificationId, NotificationTypeGroup notificationTypeGroup) { Notification notification = getNotificationOrException(notificationId); @@ -113,22 +353,6 @@ private void checkIfNotificationAlreadyRead(User user, Notification notification } } - public void createNoticeNotification(User user, NotificationCreateRequest request) { - validateAdminPrivilege(user); - validateNoticeType(request.notificationTypeName()); - - Notification notification = notificationRepository.save(Notification.create( - request.notificationTitle(), - request.notificationBody(), - request.notificationDetail(), - request.userId(), - null, - getNotificationTypeOrException(request.notificationTypeName())) - ); - - sendNoticePushMessage(request.userId(), notification); - } - private void validateAdminPrivilege(User user) { if (user.getRole() != ADMIN) { throw new CustomNotificationException(NOTIFICATION_ADMIN_ONLY, @@ -180,4 +404,32 @@ private List getTargetFCMTokens(Long userId) { .distinct() .toList(); } + + private String createNotificationBody(Feed feed) { + return String.format("내가 남긴 %s 글이 관심 받고 있어요!", generateNotificationBodyFragment(feed)); + } + + private String generateNotificationBodyFragment(Feed feed) { + if (feed.getNovelId() == null) { + return truncateFeedContent(feed); + } + Novel novel = novelService.getNovelOrException(feed.getNovelId()); + return String.format("<%s>", novel.getTitle()); + } + + private String createNotificationTitle(Feed feed) { + if (feed.getNovelId() == null) { + return truncateFeedContent(feed); + } + Novel novel = novelService.getNovelOrException(feed.getNovelId()); + return novel.getTitle(); + } + + private String truncateFeedContent(Feed feed) { + String feedContent = feed.getFeedContent(); + feedContent = feedContent.length() <= 12 + ? feedContent + : feedContent.substring(0, 12); + return "'" + feedContent + "...'"; + } } diff --git a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java b/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java index 648ce0d1..1498eb64 100644 --- a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java +++ b/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java @@ -1,24 +1,14 @@ package org.websoso.WSSServer.service; -import static java.lang.Boolean.TRUE; - import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Notification; -import org.websoso.WSSServer.domain.NotificationType; -import org.websoso.WSSServer.domain.Novel; import org.websoso.WSSServer.domain.PopularFeed; import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; import org.websoso.WSSServer.dto.popularFeed.PopularFeedGetResponse; import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse; -import org.websoso.WSSServer.notification.FCMService; -import org.websoso.WSSServer.notification.dto.FCMMessageRequest; -import org.websoso.WSSServer.repository.NotificationRepository; -import org.websoso.WSSServer.repository.NotificationTypeRepository; import org.websoso.WSSServer.repository.PopularFeedRepository; @Service @@ -27,78 +17,11 @@ public class PopularFeedService { private final PopularFeedRepository popularFeedRepository; - private final NovelService novelService; - private final FCMService fcmService; - private final NotificationTypeRepository notificationTypeRepository; - private final NotificationRepository notificationRepository; public void createPopularFeed(Feed feed) { if (!popularFeedRepository.existsByFeed(feed)) { popularFeedRepository.save(PopularFeed.create(feed)); - - sendPopularFeedPushMessage(feed); - } - } - - private void sendPopularFeedPushMessage(Feed feed) { - NotificationType notificationTypeComment = notificationTypeRepository.findByNotificationTypeName("지금뜨는수다글"); - - User feedOwner = feed.getUser(); - Long feedId = feed.getFeedId(); - String notificationTitle = "지금 뜨는 수다글 등극\uD83D\uDE4C"; - String notificationBody = createNotificationBody(feed); - - Notification notification = Notification.create( - notificationTitle, - notificationBody, - null, - feedOwner.getUserId(), - feedId, - notificationTypeComment - ); - notificationRepository.save(notification); - - if (!TRUE.equals(feedOwner.getIsPushEnabled())) { - return; - } - - List feedOwnerDevices = feedOwner.getUserDevices(); - if (feedOwnerDevices.isEmpty()) { - return; - } - - FCMMessageRequest fcmMessageRequest = FCMMessageRequest.of( - notificationTitle, - notificationBody, - String.valueOf(feedId), - "feedDetail", - String.valueOf(notification.getNotificationId()) - ); - - List targetFCMTokens = feedOwnerDevices - .stream() - .map(UserDevice::getFcmToken) - .toList(); - fcmService.sendMulticastPushMessage( - targetFCMTokens, - fcmMessageRequest - ); - } - - private String createNotificationBody(Feed feed) { - return String.format("내가 남긴 %s 글이 관심 받고 있어요!", generateNotificationBodyFragment(feed)); - } - - private String generateNotificationBodyFragment(Feed feed) { - if (feed.getNovelId() == null) { - String feedContent = feed.getFeedContent(); - feedContent = feedContent.length() <= 12 - ? feedContent - : feedContent.substring(0, 12); - return "'" + feedContent + "...'"; } - Novel novel = novelService.getNovelOrException(feed.getNovelId()); - return String.format("<%s>", novel.getTitle()); } @Transactional(readOnly = true) diff --git a/src/main/java/org/websoso/WSSServer/service/ReportService.java b/src/main/java/org/websoso/WSSServer/service/ReportService.java new file mode 100644 index 00000000..aeaea41e --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/service/ReportService.java @@ -0,0 +1,107 @@ +package org.websoso.WSSServer.service; + +import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE; +import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; +import static org.websoso.WSSServer.exception.error.CustomCommentError.ALREADY_REPORTED_COMMENT; +import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_REPORTED_FEED; +import static org.websoso.WSSServer.exception.error.CustomFeedError.SELF_REPORT_NOT_ALLOWED; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.websoso.WSSServer.domain.Comment; +import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.domain.ReportedComment; +import org.websoso.WSSServer.domain.ReportedFeed; +import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.common.ReportedType; +import org.websoso.WSSServer.exception.error.CustomCommentError; +import org.websoso.WSSServer.exception.exception.CustomCommentException; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.repository.ReportedCommentRepository; +import org.websoso.WSSServer.repository.ReportedFeedRepository; + +@Service +@RequiredArgsConstructor +@Transactional +public class ReportService { + + private final ReportedFeedRepository reportedFeedRepository; + private final ReportedCommentRepository reportedCommentRepository; + + public int reportFeed(User reportingUser, Feed feed, ReportedType reportedType) { + if (isUserFeedOwner(feed.getUser(), reportingUser)) { + throw new CustomFeedException(SELF_REPORT_NOT_ALLOWED, "cannot report own feed"); + } + + createReportedFeed(feed, reportingUser, reportedType); + + int reportedCount = getReportedCountByReportedType(feed, reportedType); + if (reportedType.isExceedingLimit(reportedCount)) { + if (reportedType.isExceedingLimit(reportedCount)) { + if (reportedType.equals(SPOILER)) { + feed.spoiler(); + } else if (reportedType.equals(IMPERTINENCE)) { + feed.hide(); + } + } + } + + return reportedCount; + } + + public int reportComment(User reportingUser, Feed feed, Comment comment, User commentCreatedUser, + ReportedType reportedType) { + comment.validateFeedAssociation(feed); + + if (isUserCommentOwner(reportingUser, commentCreatedUser)) { + throw new CustomCommentException(CustomCommentError.SELF_REPORT_NOT_ALLOWED, "cannot report own comment"); + } + + createReportedComment(comment, reportingUser, reportedType); + + int reportedCount = getReportedCountByReportedType(comment, reportedType); + if (reportedType.isExceedingLimit(reportedCount)) { + if (reportedType.equals(SPOILER)) { + comment.spoiler(); + } else if (reportedType.equals(IMPERTINENCE)) { + comment.hideComment(); + } + } + + return reportedCount; + } + + private Boolean isUserFeedOwner(User createdUser, User user) { + return createdUser.equals(user); + } + + private Boolean isUserCommentOwner(User createdUser, User user) { + return createdUser.equals(user); + } + + private void createReportedFeed(Feed feed, User user, ReportedType reportedType) { + if (reportedFeedRepository.existsByFeedAndUserAndReportedType(feed, user, reportedType)) { + throw new CustomFeedException(ALREADY_REPORTED_FEED, "feed has already been reported by the user"); + } + + reportedFeedRepository.save(ReportedFeed.create(feed, user, reportedType)); + } + + private void createReportedComment(Comment comment, User user, ReportedType reportedType) { + if (reportedCommentRepository.existsByCommentAndUserAndReportedType(comment, user, reportedType)) { + throw new CustomCommentException(ALREADY_REPORTED_COMMENT, "comment has already been reported by the user"); + } + + reportedCommentRepository.save(ReportedComment.create(comment, user, reportedType)); + } + + private int getReportedCountByReportedType(Feed feed, ReportedType reportedType) { + return reportedFeedRepository.countByFeedAndReportedType(feed, reportedType); + } + + private int getReportedCountByReportedType(Comment comment, ReportedType reportedType) { + return reportedCommentRepository.countByCommentAndReportedType(comment, reportedType); + } + +}