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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package org.websoso.WSSServer.application;

import static org.websoso.WSSServer.exception.error.CustomAvatarError.AVATAR_NOT_FOUND;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
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.Avatar;
import org.websoso.WSSServer.domain.Genre;
import org.websoso.WSSServer.domain.GenrePreference;
import org.websoso.WSSServer.domain.common.FeedGetOption;
import org.websoso.WSSServer.dto.feed.FeedGetResponse;
import org.websoso.WSSServer.dto.feed.FeedInfo;
import org.websoso.WSSServer.dto.feed.FeedsGetResponse;
import org.websoso.WSSServer.dto.feed.InterestFeedGetResponse;
import org.websoso.WSSServer.dto.feed.InterestFeedsGetResponse;
import org.websoso.WSSServer.dto.popularFeed.PopularFeedGetResponse;
import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse;
import org.websoso.WSSServer.dto.user.UserBasicInfo;
import org.websoso.WSSServer.exception.exception.CustomAvatarException;
import org.websoso.WSSServer.feed.domain.Feed;
import org.websoso.WSSServer.feed.domain.FeedImage;
import org.websoso.WSSServer.feed.domain.PopularFeed;
import org.websoso.WSSServer.feed.repository.LikeRepository;
import org.websoso.WSSServer.feed.service.FeedServiceImpl;
import org.websoso.WSSServer.library.domain.UserNovel;
import org.websoso.WSSServer.library.repository.UserNovelRepository;
import org.websoso.WSSServer.novel.domain.Novel;
import org.websoso.WSSServer.novel.service.NovelServiceImpl;
import org.websoso.WSSServer.repository.AvatarRepository;
import org.websoso.WSSServer.repository.GenrePreferenceRepository;
import org.websoso.WSSServer.user.domain.User;

@Service
@RequiredArgsConstructor
public class FeedFindApplication {

private final FeedServiceImpl feedServiceImpl;
private final NovelServiceImpl novelServiceImpl;

private static final String DEFAULT_CATEGORY = "all";
private static final int DEFAULT_PAGE_NUMBER = 0;

//ToDo : 의존성 제거 필요 부분
private final AvatarRepository avatarRepository;
private final LikeRepository likeRepository;
private final GenrePreferenceRepository genrePreferenceRepository;
private final UserNovelRepository userNovelRepository;

@Transactional(readOnly = true)
public FeedGetResponse getFeedById(User user, Long feedId) {
Feed feed = feedServiceImpl.getFeedOrException(feedId);
UserBasicInfo feedUserBasicInfo = getUserBasicInfo(feed.getUser());
Novel novel = getLinkedNovelOrNull(feed.getNovelId());
Boolean isLiked = isUserLikedFeed(user, feed);
List<String> relevantCategories = feed.getFeedCategories().stream()
.map(feedCategory -> feedCategory.getCategory().getCategoryName().getLabel())
.collect(Collectors.toList());
Boolean isMyFeed = isUserFeedOwner(feed.getUser(), user);

return FeedGetResponse.of(feed, feedUserBasicInfo, novel, isLiked, relevantCategories, isMyFeed);
}

private UserBasicInfo getUserBasicInfo(User user) {
return user.getUserBasicInfo(
avatarRepository.findById(user.getAvatarId()).orElseThrow(() ->
new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found"))
.getAvatarImage());
}

private Novel getLinkedNovelOrNull(Long linkedNovelId) {
if (linkedNovelId == null) {
return null;
}
return novelServiceImpl.getNovelOrException(linkedNovelId);
}

private Boolean isUserLikedFeed(User user, Feed feed) {
return likeRepository.existsByUserIdAndFeed(user.getUserId(), feed);
}

private Boolean isUserFeedOwner(User createdUser, User user) {
return createdUser.equals(user);
}

@Transactional(readOnly = true)
public FeedsGetResponse getFeeds(User user, String category, Long lastFeedId, int size,
FeedGetOption feedGetOption) {
Long userIdOrNull = Optional.ofNullable(user).map(User::getUserId).orElse(null);

List<Genre> genres = getPreferenceGenres(user);

Slice<Feed> feeds = findFeedsByCategoryLabel(getChosenCategoryOrDefault(category), lastFeedId, userIdOrNull,
PageRequest.of(DEFAULT_PAGE_NUMBER, size), feedGetOption, genres);

List<FeedInfo> feedGetResponses = feeds.getContent().stream().filter(feed -> feed.isVisibleTo(userIdOrNull))
.map(feed -> createFeedInfo(feed, user)).toList();

return FeedsGetResponse.of(getChosenCategoryOrDefault(category), feeds.hasNext(), feedGetResponses);
}

private List<Genre> getPreferenceGenres(User user) {
if (user == null) {
return null;
}
return genrePreferenceRepository.findByUser(user).stream().map(GenrePreference::getGenre).toList();
}

private static String getChosenCategoryOrDefault(String category) {
return Optional.ofNullable(category).orElse(DEFAULT_CATEGORY);
}

private Slice<Feed> findFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, PageRequest pageRequest,
FeedGetOption feedGetOption, List<Genre> genres) {
return feedServiceImpl.findFeedsByCategoryLabel(category, lastFeedId, userId, pageRequest, feedGetOption,
genres);
}

private FeedInfo createFeedInfo(Feed feed, User user) {
UserBasicInfo userBasicInfo = getUserBasicInfo(feed.getUser());
Novel novel = getLinkedNovelOrNull(feed.getNovelId());
Boolean isLiked = user != null && isUserLikedFeed(user, feed);
List<String> relevantCategories = feed.getFeedCategories().stream()
.map(feedCategory -> feedCategory.getCategory().getCategoryName().getLabel())
.collect(Collectors.toList());
Boolean isMyFeed = user != null && isUserFeedOwner(feed.getUser(), user);
Integer imageCount = feedServiceImpl.countByFeedId(feed.getFeedId());
Optional<FeedImage> thumbnailImage = feedServiceImpl.findThumbnailFeedImageByFeedId(feed.getFeedId());
String thumbnailUrl = thumbnailImage.map(FeedImage::getUrl).orElse(null);

return FeedInfo.of(feed, userBasicInfo, novel, isLiked, relevantCategories, isMyFeed, thumbnailUrl, imageCount,
user);
}

@Transactional(readOnly = true)
public PopularFeedsGetResponse getPopularFeeds(User user) {
Long currentUserId = Optional.ofNullable(user).map(User::getUserId).orElse(null);

List<PopularFeed> popularFeeds = Optional.ofNullable(user).map(u -> findPopularFeedsWithUser(u.getUserId()))
.orElseGet(this::findPopularFeedsWithoutUser);

List<PopularFeedGetResponse> popularFeedGetResponses = mapToPopularFeedGetResponseList(popularFeeds,
currentUserId);

return new PopularFeedsGetResponse(popularFeedGetResponses);
}

private List<PopularFeed> findPopularFeedsWithUser(Long userId) {
return feedServiceImpl.findPopularFeedsWithUser(userId);
}

private List<PopularFeed> findPopularFeedsWithoutUser() {
return feedServiceImpl.findPopularFeedsWithoutUser();
}

private static List<PopularFeedGetResponse> mapToPopularFeedGetResponseList(List<PopularFeed> popularFeeds,
Long currentUserId) {
return popularFeeds.stream().filter(pf -> pf.getFeed().isVisibleTo(currentUserId))
.map(PopularFeedGetResponse::of).toList();
}

@Transactional(readOnly = true)
public InterestFeedsGetResponse getInterestFeeds(User user) {
List<Novel> interestNovels = userNovelRepository.findByUserAndIsInterestTrue(user).stream()
.map(UserNovel::getNovel).toList();

if (interestNovels.isEmpty()) {
return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_INTEREST_NOVELS");
}

Map<Long, Novel> novelMap = interestNovels.stream()
.collect(Collectors.toMap(Novel::getNovelId, novel -> novel));
List<Long> interestNovelIds = new ArrayList<>(novelMap.keySet());

List<Feed> interestFeeds = feedServiceImpl.findInterestFeeds(interestNovelIds);

if (interestFeeds.isEmpty()) {
return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_ASSOCIATED_FEEDS");
}

Set<Byte> avatarIds = interestFeeds.stream().map(feed -> feed.getUser().getAvatarId())
.collect(Collectors.toSet());
Map<Byte, Avatar> avatarMap = avatarRepository.findAllById(avatarIds).stream()
.collect(Collectors.toMap(Avatar::getAvatarId, avatar -> avatar));

List<InterestFeedGetResponse> interestFeedGetResponses = interestFeeds.stream()
.filter(feed -> feed.isVisibleTo(user.getUserId())).map(feed -> {
Novel novel = novelMap.get(feed.getNovelId());
Avatar avatar = avatarMap.get(feed.getUser().getAvatarId());
return InterestFeedGetResponse.of(novel, feed.getUser(), feed, avatar);
}).toList();
return InterestFeedsGetResponse.of(interestFeedGetResponses, "");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.websoso.WSSServer.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class FeedManagementApplication {
}
105 changes: 105 additions & 0 deletions src/main/java/org/websoso/WSSServer/application/ReportApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.websoso.WSSServer.application;

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.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 static org.websoso.WSSServer.exception.error.CustomUserError.USER_NOT_FOUND;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.websoso.WSSServer.domain.common.DiscordWebhookMessage;
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.exception.exception.CustomUserException;
import org.websoso.WSSServer.feed.domain.Comment;
import org.websoso.WSSServer.feed.domain.Feed;
import org.websoso.WSSServer.feed.service.CommentServiceImpl;
import org.websoso.WSSServer.feed.service.FeedServiceImpl;
import org.websoso.WSSServer.feed.service.ReportServiceImpl;
import org.websoso.WSSServer.service.DiscordMessageClient;
import org.websoso.WSSServer.service.MessageFormatter;
import org.websoso.WSSServer.user.domain.User;
import org.websoso.WSSServer.user.repository.UserRepository;

@Service
@RequiredArgsConstructor
public class ReportApplication {

private final FeedServiceImpl feedServiceImpl;
private final CommentServiceImpl commentServiceImpl;
private final ReportServiceImpl reportServiceImpl;
private final DiscordMessageClient discordMessageClient;

//ToDo : 의존성 제거 필요 부분
private final UserRepository userRepository;

@Transactional
public void reportComment(User user, Long feedId, Long commentId, ReportedType reportedType) {
Feed feed = feedServiceImpl.getFeedOrException(feedId);
Comment comment = commentServiceImpl.findComment(commentId);
comment.validateFeedAssociation(feed);

User commentCreatedUser = userRepository.findById(comment.getUserId())
.orElseThrow(() -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found"));

if (commentCreatedUser.equals(user)) {
throw new CustomCommentException(CustomCommentError.SELF_REPORT_NOT_ALLOWED, "cannot report own comment");
}

if (reportServiceImpl.isExistsByCommentAndUserAndReportedType(comment, user, reportedType)) {
throw new CustomCommentException(ALREADY_REPORTED_COMMENT, "comment has already been reported by the user");
}

reportServiceImpl.saveReportedComment(comment, user, reportedType);

int reportedCount = reportServiceImpl.countByCommentAndReportedType(comment, reportedType);
boolean shouldHide = reportedType.isExceedingLimit(reportedCount);

if (shouldHide) {
if (reportedType.equals(SPOILER)) {
comment.spoiler();
} else if (reportedType.equals(IMPERTINENCE)) {
comment.hideComment();
}
}

discordMessageClient.sendDiscordWebhookMessage(DiscordWebhookMessage.of(
MessageFormatter.formatCommentReportMessage(user, feed, comment, reportedType, commentCreatedUser,
reportedCount, shouldHide), REPORT));
}

@Transactional
public void reportFeed(User user, Long feedId, ReportedType reportedType) {
Feed feed = feedServiceImpl.getFeedOrException(feedId);

if (isUserFeedOwner(feed.getUser(), user)) {
throw new CustomFeedException(SELF_REPORT_NOT_ALLOWED, "cannot report own feed");
}

if (reportServiceImpl.isExistsByFeedAndUserAndReportedType(feed, user, reportedType)) {
throw new CustomFeedException(ALREADY_REPORTED_FEED, "feed has already been reported by the user");
}

reportServiceImpl.saveReportedFeed(feed, user, reportedType);

int reportedCount = reportServiceImpl.countByFeedAndReportedType(feed, reportedType);
boolean shouldHide = reportedType.isExceedingLimit(reportedCount);

if (shouldHide) {
feed.hideFeed();
}

discordMessageClient.sendDiscordWebhookMessage(DiscordWebhookMessage.of(
MessageFormatter.formatFeedReportMessage(user, feed, reportedType, reportedCount, shouldHide), REPORT));
}

private Boolean isUserFeedOwner(User createdUser, User user) {
return createdUser.equals(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,26 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.websoso.WSSServer.user.domain.User;
import org.websoso.WSSServer.application.CommentFindApplication;
import org.websoso.WSSServer.application.CommentManagementApplication;
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.feed.service.CommentService;
import org.websoso.WSSServer.user.domain.User;

@RequestMapping("/feeds")
@RestController
@RequiredArgsConstructor
public class CommentController {

private final CommentService commentService;
private final CommentFindApplication commentFindApplication;
private final CommentManagementApplication commentManagementApplication;

@PostMapping("/{feedId}/comments")
@PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)")
public ResponseEntity<Void> createComment(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId,
@Valid @RequestBody CommentCreateRequest request) {
commentService.createComment(user, feedId, request);
commentManagementApplication.createComment(user, feedId, request);
return ResponseEntity.status(NO_CONTENT).build();
}

Expand All @@ -43,7 +45,7 @@ public ResponseEntity<CommentsGetResponse> getComments(@AuthenticationPrincipal
@PathVariable("feedId") Long feedId) {
return ResponseEntity
.status(OK)
.body(commentService.getComments(user, feedId));
.body(commentFindApplication.getComments(user, feedId));
}

@PutMapping("/{feedId}/comments/{commentId}")
Expand All @@ -53,7 +55,7 @@ public ResponseEntity<Void> updateComment(@AuthenticationPrincipal User user,
@PathVariable("feedId") Long feedId,
@PathVariable("commentId") Long commentId,
@Valid @RequestBody CommentUpdateRequest request) {
commentService.updateComment(user, feedId, commentId, request);
commentManagementApplication.updateComment(user, feedId, commentId, request);
return ResponseEntity
.status(NO_CONTENT)
.build();
Expand All @@ -65,7 +67,7 @@ public ResponseEntity<Void> updateComment(@AuthenticationPrincipal User user,
public ResponseEntity<Void> deleteComment(@AuthenticationPrincipal User user,
@PathVariable("feedId") Long feedId,
@PathVariable("commentId") Long commentId) {
commentService.deleteComment(user, feedId, commentId);
commentManagementApplication.deleteComment(user, feedId, commentId);
return ResponseEntity
.status(NO_CONTENT)
.build();
Expand Down
Loading