diff --git a/src/main/java/org/websoso/WSSServer/application/AccountApplication.java b/src/main/java/org/websoso/WSSServer/application/AccountApplication.java new file mode 100644 index 000000000..9e71e1c75 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/AccountApplication.java @@ -0,0 +1,67 @@ +package org.websoso.WSSServer.application; + +import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.WITHDRAW; + +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.dto.user.WithdrawalRequest; +import org.websoso.WSSServer.feed.repository.CommentRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.oauth2.service.AppleService; +import org.websoso.WSSServer.oauth2.service.KakaoService; +import org.websoso.WSSServer.repository.RefreshTokenRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; +import org.websoso.WSSServer.service.MessageFormatter; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.WithdrawalReason; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.user.repository.WithdrawalReasonRepository; + +@Service +@RequiredArgsConstructor +@Transactional +public class AccountApplication { + private static final String KAKAO_PREFIX = "kakao"; + private static final String APPLE_PREFIX = "apple"; + + private final WithdrawalReasonRepository withdrawalReasonRepository; + private final DiscordMessageClient discordMessageClient; + private final AppleService appleService; + private final FeedRepository feedRepository; + private final UserRepository userRepository; + private final CommentRepository commentRepository; + private final RefreshTokenRepository refreshTokenRepository; + private final KakaoService kakaoService; + + public void withdrawUser(User user, WithdrawalRequest withdrawalRequest) { + unlinkSocialAccount(user); + + String messageContent = MessageFormatter.formatUserWithdrawMessage(user.getUserId(), user.getNickname(), + withdrawalRequest.reason()); + + cleanupUserData(user.getUserId()); + + discordMessageClient.sendDiscordWebhookMessage( + DiscordWebhookMessage.of(messageContent, WITHDRAW)); + + withdrawalReasonRepository.save(WithdrawalReason.create(withdrawalRequest.reason())); + } + + private void unlinkSocialAccount(User user) { + if (user.getSocialId().startsWith(KAKAO_PREFIX)) { + kakaoService.unlinkFromKakao(user); + } else if (user.getSocialId().startsWith(APPLE_PREFIX)) { + appleService.unlinkFromApple(user); + } + } + + private void cleanupUserData(Long userId) { + refreshTokenRepository.deleteAll(refreshTokenRepository.findAllByUserId(userId)); + feedRepository.updateUserToUnknown(userId); + commentRepository.updateUserToUnknown(userId); + userRepository.deleteById(userId); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/service/AuthService.java b/src/main/java/org/websoso/WSSServer/application/AuthApplication.java similarity index 53% rename from src/main/java/org/websoso/WSSServer/service/AuthService.java rename to src/main/java/org/websoso/WSSServer/application/AuthApplication.java index 4cf640385..cc685c1ae 100644 --- a/src/main/java/org/websoso/WSSServer/service/AuthService.java +++ b/src/main/java/org/websoso/WSSServer/application/AuthApplication.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.service; +package org.websoso.WSSServer.application; import static org.websoso.WSSServer.exception.error.CustomAuthError.INVALID_TOKEN; @@ -9,19 +9,28 @@ import org.websoso.WSSServer.config.jwt.JWTUtil; import org.websoso.WSSServer.config.jwt.JwtProvider; import org.websoso.WSSServer.config.jwt.JwtValidationType; -import org.websoso.WSSServer.domain.RefreshToken; +import org.websoso.WSSServer.dto.auth.LogoutRequest; import org.websoso.WSSServer.dto.auth.ReissueResponse; +import org.websoso.WSSServer.dto.user.LoginResponse; import org.websoso.WSSServer.exception.exception.CustomAuthException; +import org.websoso.WSSServer.oauth2.service.KakaoService; import org.websoso.WSSServer.repository.RefreshTokenRepository; +import org.websoso.WSSServer.user.domain.RefreshToken; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.repository.UserDeviceRepository; +import org.websoso.WSSServer.user.service.UserService; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) -public class AuthService { - +public class AuthApplication { private final JwtProvider jwtProvider; private final JWTUtil jwtUtil; private final RefreshTokenRepository refreshTokenRepository; + private final UserDeviceRepository userDeviceRepository; + private final UserService userService; + private final KakaoService kakaoService; + private static final String KAKAO_PREFIX = "kakao"; + private static final String APPLE_PREFIX = "apple"; public ReissueResponse reissue(String refreshToken) { RefreshToken storedRefreshToken = refreshTokenRepository.findByRefreshToken(refreshToken) @@ -41,4 +50,27 @@ public ReissueResponse reissue(String refreshToken) { return ReissueResponse.of(newAccessToken, newRefreshToken); } + + // TODO: getUserOrException -> existUserOrException 변경 + @Transactional(readOnly = true) + public LoginResponse login(Long userId) { + User user = userService.getUserOrException(userId); + + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, + null); + String token = jwtProvider.generateAccessToken(customAuthenticationToken); + + return LoginResponse.of(token); + } + + public void logout(User user, LogoutRequest request) { + refreshTokenRepository.findByRefreshToken(request.refreshToken()) + .ifPresent(refreshTokenRepository::delete); + + userDeviceRepository.deleteByUserAndDeviceIdentifier(user, request.deviceIdentifier()); + + if (user.getSocialId().startsWith(KAKAO_PREFIX)) { + kakaoService.kakaoLogout(user); + } + } } diff --git a/src/main/java/org/websoso/WSSServer/application/CommentFindApplication.java b/src/main/java/org/websoso/WSSServer/application/CommentFindApplication.java new file mode 100644 index 000000000..51947216b --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/CommentFindApplication.java @@ -0,0 +1,62 @@ +package org.websoso.WSSServer.application; + +import static org.websoso.WSSServer.exception.error.CustomAvatarError.AVATAR_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomUserError.USER_NOT_FOUND; + +import java.util.AbstractMap; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.repository.AvatarProfileRepository; +import org.websoso.WSSServer.user.domain.User; +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.CustomAvatarException; +import org.websoso.WSSServer.exception.exception.CustomUserException; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.service.FeedServiceImpl; +import org.websoso.WSSServer.repository.BlockRepository; +import org.websoso.WSSServer.user.repository.UserRepository; + +@Service +@RequiredArgsConstructor +public class CommentFindApplication { + + private final FeedServiceImpl feedServiceImpl; + + //ToDo : 의존성 제거 필요 부분 + private final UserRepository userRepository; + private final BlockRepository blockRepository; + private final AvatarProfileRepository avatarProfileRepository; + + @Transactional(readOnly = true) + public CommentsGetResponse getComments(User user, Long feedId) { + Feed feed = feedServiceImpl.getFeedOrException(feedId); + List responses = feed.getComments().stream() + .map(comment -> new AbstractMap.SimpleEntry<>(comment, userRepository.findById(comment.getUserId()) + .orElseThrow( + () -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found")))) + .map(entry -> CommentGetResponse.of(getUserBasicInfo(entry.getValue()), entry.getKey(), + isUserCommentOwner(entry.getValue(), user), entry.getKey().getIsSpoiler(), + isBlocked(user, entry.getValue()), entry.getKey().getIsHidden())).toList(); + + return CommentsGetResponse.of(responses); + } + + private Boolean isUserCommentOwner(User createdUser, User user) { + return createdUser.equals(user); + } + + private Boolean isBlocked(User user, User createdFeedUser) { + return blockRepository.existsByBlockingIdAndBlockedId(user.getUserId(), createdFeedUser.getUserId()); + } + + private UserBasicInfo getUserBasicInfo(User user) { + return user.getUserBasicInfo( + avatarProfileRepository.findById(user.getAvatarProfileId()).orElseThrow(() -> + new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")) + .getAvatarProfileImage()); + } +} diff --git a/src/main/java/org/websoso/WSSServer/application/CommentManagementApplication.java b/src/main/java/org/websoso/WSSServer/application/CommentManagementApplication.java new file mode 100644 index 000000000..307bdbeda --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/CommentManagementApplication.java @@ -0,0 +1,167 @@ +package org.websoso.WSSServer.application; + +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.exception.error.CustomUserError.USER_NOT_FOUND; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.NotificationType; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; +import org.websoso.WSSServer.dto.comment.CommentCreateRequest; +import org.websoso.WSSServer.dto.comment.CommentUpdateRequest; +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.notification.FCMClient; +import org.websoso.WSSServer.notification.dto.FCMMessageRequest; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.service.NovelServiceImpl; +import org.websoso.WSSServer.repository.BlockRepository; +import org.websoso.WSSServer.repository.NotificationRepository; +import org.websoso.WSSServer.repository.NotificationTypeRepository; +import org.websoso.WSSServer.user.repository.UserRepository; + +@Service +@RequiredArgsConstructor +public class CommentManagementApplication { + + private final CommentServiceImpl commentServiceImpl; + private final FeedServiceImpl feedServiceImpl; + private final NovelServiceImpl novelServiceImpl; + private final FCMClient fcmClient; + + private static final int NOTIFICATION_TITLE_MAX_LENGTH = 12; + private static final int NOTIFICATION_TITLE_MIN_LENGTH = 0; + + //ToDo : 의존성 제거 필요 부분 + private final BlockRepository blockRepository; + private final NotificationRepository notificationRepository; + private final NotificationTypeRepository notificationTypeRepository; + private final UserRepository userRepository; + + @Transactional + public void createComment(User user, Long feedId, CommentCreateRequest request) { + Feed feed = feedServiceImpl.getFeedOrException(feedId); + commentServiceImpl.createComment(user, feed, request); + sendCommentPushMessageToFeedOwner(user, feed); + sendCommentPushMessageToCommenters(user, feed); + } + + private void sendCommentPushMessageToFeedOwner(User user, Feed feed) { + User feedOwner = feed.getUser(); + if (user.equals(feedOwner) || blockRepository.existsByBlockingIdAndBlockedId(feedOwner.getUserId(), + user.getUserId())) { + return; + } + + // ToDo : 해당 로직 NotificationSerivce에서 처리하도록 수정 + 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(); + + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + } + + 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 -> !blockRepository.existsByBlockingIdAndBlockedId(userId, user.getUserId()) + && !blockRepository.existsByBlockingIdAndBlockedId(userId, feed.getUser().getUserId())) + .distinct().map(userId -> userRepository.findById(userId).orElseThrow( + () -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found"))) + .toList(); + + if (commenters.isEmpty()) { + return; + } + + // ToDo : 해당 로직 NotificationSerivce에서 처리하도록 수정 + 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())); + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + }); + } + + + private String createNotificationTitle(Feed feed) { + if (feed.getNovelId() == null) { + String feedContent = feed.getFeedContent(); + feedContent = feedContent.length() <= NOTIFICATION_TITLE_MAX_LENGTH ? feedContent + : feedContent.substring(NOTIFICATION_TITLE_MIN_LENGTH, NOTIFICATION_TITLE_MAX_LENGTH); + return "'" + feedContent + "...'"; + } + Novel novel = novelServiceImpl.getNovelOrException(feed.getNovelId()); + return novel.getTitle(); + } + + @Transactional + public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { + Feed feed = feedServiceImpl.getFeedOrException(feedId); + Comment comment = commentServiceImpl.findComment(commentId); + comment.validateFeedAssociation(feed); + comment.validateUserAuthorization(user.getUserId(), UPDATE); + commentServiceImpl.updateComment(comment, request); + } + + @Transactional + public void deleteComment(User user, Long feedId, Long commentId) { + Feed feed = feedServiceImpl.getFeedOrException(feedId); + Comment comment = commentServiceImpl.findComment(commentId); + comment.validateFeedAssociation(feed); + comment.validateUserAuthorization(user.getUserId(), DELETE); + commentServiceImpl.deleteComment(comment); + } +} diff --git a/src/main/java/org/websoso/WSSServer/application/FeedFindApplication.java b/src/main/java/org/websoso/WSSServer/application/FeedFindApplication.java new file mode 100644 index 000000000..077977314 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/FeedFindApplication.java @@ -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.AvatarProfile; +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.AvatarProfileRepository; +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 AvatarProfileRepository 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 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.getAvatarProfileId()).orElseThrow(() -> + new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")) + .getAvatarProfileImage()); + } + + 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 genres = getPreferenceGenres(user); + + Slice feeds = findFeedsByCategoryLabel(getChosenCategoryOrDefault(category), lastFeedId, userIdOrNull, + PageRequest.of(DEFAULT_PAGE_NUMBER, size), feedGetOption, genres); + + List 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 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 findFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, PageRequest pageRequest, + FeedGetOption feedGetOption, List 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 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 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 popularFeeds = Optional.ofNullable(user).map(u -> findPopularFeedsWithUser(u.getUserId())) + .orElseGet(this::findPopularFeedsWithoutUser); + + List popularFeedGetResponses = mapToPopularFeedGetResponseList(popularFeeds, + currentUserId); + + return new PopularFeedsGetResponse(popularFeedGetResponses); + } + + private List findPopularFeedsWithUser(Long userId) { + return feedServiceImpl.findPopularFeedsWithUser(userId); + } + + private List findPopularFeedsWithoutUser() { + return feedServiceImpl.findPopularFeedsWithoutUser(); + } + + private static List mapToPopularFeedGetResponseList(List 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 interestNovels = userNovelRepository.findByUserAndIsInterestTrue(user).stream() + .map(UserNovel::getNovel).toList(); + + if (interestNovels.isEmpty()) { + return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_INTEREST_NOVELS"); + } + + Map novelMap = interestNovels.stream() + .collect(Collectors.toMap(Novel::getNovelId, novel -> novel)); + List interestNovelIds = new ArrayList<>(novelMap.keySet()); + + List interestFeeds = feedServiceImpl.findInterestFeeds(interestNovelIds); + + if (interestFeeds.isEmpty()) { + return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_ASSOCIATED_FEEDS"); + } + + Set avatarProfileIds = interestFeeds.stream().map(feed -> feed.getUser().getAvatarProfileId()) + .collect(Collectors.toSet()); + Map avatarMap = avatarRepository.findAllById(avatarProfileIds).stream() + .collect(Collectors.toMap(AvatarProfile::getAvatarProfileId, avatar -> avatar)); + + List interestFeedGetResponses = interestFeeds.stream() + .filter(feed -> feed.isVisibleTo(user.getUserId())).map(feed -> { + Novel novel = novelMap.get(feed.getNovelId()); + AvatarProfile avatar = avatarMap.get(feed.getUser().getAvatarProfileId()); + return InterestFeedGetResponse.of(novel, feed.getUser(), feed, avatar); + }).toList(); + return InterestFeedsGetResponse.of(interestFeedGetResponses, ""); + } +} diff --git a/src/main/java/org/websoso/WSSServer/application/FeedManagementApplication.java b/src/main/java/org/websoso/WSSServer/application/FeedManagementApplication.java new file mode 100644 index 000000000..18d0ec50b --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/FeedManagementApplication.java @@ -0,0 +1,9 @@ +package org.websoso.WSSServer.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class FeedManagementApplication { +} diff --git a/src/main/java/org/websoso/WSSServer/application/LibraryEvaluationApplication.java b/src/main/java/org/websoso/WSSServer/application/LibraryEvaluationApplication.java new file mode 100644 index 000000000..bd46de953 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/LibraryEvaluationApplication.java @@ -0,0 +1,218 @@ +package org.websoso.WSSServer.application; + +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.NOT_EVALUATED; +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_ALREADY_EXISTS; + +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.dto.keyword.KeywordGetResponse; +import org.websoso.WSSServer.dto.userNovel.UserNovelCreateRequest; +import org.websoso.WSSServer.dto.userNovel.UserNovelGetResponse; +import org.websoso.WSSServer.dto.userNovel.UserNovelUpdateRequest; +import org.websoso.WSSServer.exception.exception.CustomUserNovelException; +import org.websoso.WSSServer.library.domain.AttractivePoint; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelAttractivePoint; +import org.websoso.WSSServer.library.domain.UserNovelKeyword; +import org.websoso.WSSServer.library.service.AttractivePointService; +import org.websoso.WSSServer.library.service.KeywordService; +import org.websoso.WSSServer.library.service.LibraryService; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.service.NovelServiceImpl; + +/** + * 서재 평가는 서재와 매력 포인트, 키워드가 핵심 도메인이다. + */ +@Service +@RequiredArgsConstructor +public class LibraryEvaluationApplication { + + private final NovelServiceImpl novelService; + private final LibraryService libraryService; + private final AttractivePointService attractivePointService; + private final KeywordService keywordService; + + /** + * 서재 평가를 생성한다. + * + * @param user 사용자 객체 + * @param request UserNovelCreateRequest + */ + @Transactional + public void createEvaluation(User user, UserNovelCreateRequest request) { + Novel novel = novelService.getNovelOrException(request.novelId()); + + try { + UserNovel userNovel = libraryService.createLibrary(request.status(), request.userNovelRating(), + request.startDate(), request.endDate(), user, novel); + + attractivePointService.createUserNovelAttractivePoints(userNovel, request.attractivePoints()); + keywordService.createNovelKeywords(userNovel, request.keywordIds()); + } catch (DataIntegrityViolationException e) { + throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); + } + } + + /** + * 서재 평가를 불러온다. + * + * @param user 사용자 객체 + * @param novelId 소설 ID + * @return UserNovelGetResponse + */ + public UserNovelGetResponse getEvaluation(User user, Long novelId) { + Novel novel = novelService.getNovelOrException(novelId); + UserNovel userNovel = libraryService.getLibraryOrNull(user, novel); + + if (userNovel == null) { + return UserNovelGetResponse.of(novel, null, Collections.emptyList(), Collections.emptyList()); + } + + List attractivePoints = getStringAttractivePoints(userNovel); + List keywords = getKeywordGetResponses(userNovel); + + return UserNovelGetResponse.of(novel, userNovel, attractivePoints, keywords); + } + + /** + * 서재 평가를 업데이트한다. + * + * @param user 사용자 객체 + * @param novelId 소설 ID + * @param request UserNovelUpdateRequest + */ + @Transactional + public void updateEvaluation(User user, Long novelId, UserNovelUpdateRequest request) { + UserNovel userNovel = libraryService.getLibraryOrException(user, novelId); + + userNovel.updateUserNovel(request.userNovelRating(), request.status(), request.startDate(), request.endDate()); + + updateAttractivePoints(userNovel, request.attractivePoints()); + + updateKeywords(userNovel, request.keywordIds()); + } + + /** + * 서재 평가를 삭제한다. + * + * @param user 사용자 객체 + * @param novelId 소설 ID + */ + public void deleteEvaluation(User user, Long novelId) { + UserNovel userNovel = libraryService.getLibraryOrException(user, novelId); + + if (userNovel.getStatus() == null) { + throw new CustomUserNovelException(NOT_EVALUATED, "this novel has not been evaluated by the user"); + } + + if (userNovel.getIsInterest()) { + userNovel.deleteEvaluation(); + + attractivePointService.deleteUserNovelAttractivePoints(userNovel.getUserNovelAttractivePoints()); + keywordService.deleteUserNovelKeywords(userNovel.getUserNovelKeywords()); + } else { + libraryService.delete(userNovel); + } + } + + // TODO: 리팩토링 대상 Fetch Lazy 수정 + private List getStringAttractivePoints(UserNovel userNovel) { + return userNovel.getUserNovelAttractivePoints().stream() + .map(attractivePoint -> attractivePoint.getAttractivePoint().getAttractivePointName()) + .toList(); + } + + // TODO: 리팩토링 대상 Fetch Lazy 수정 + private List getKeywordGetResponses(UserNovel userNovel) { + return userNovel.getUserNovelKeywords().stream() + .map(userNovelKeyword -> KeywordGetResponse.of(userNovelKeyword.getKeyword())) + .toList(); + } + + private void updateAttractivePoints(UserNovel userNovel, List attractivePoints) { + Map currentPointMap = userNovel.getUserNovelAttractivePoints() + .stream() + .collect(Collectors.toMap(UserNovelAttractivePoint::getAttractivePoint, it -> it)); + + Set requestedPoints = attractivePoints.stream() + .map(attractivePointService::getAttractivePointByString) + .collect(Collectors.toSet()); + + addUserNovelAttractivePoints(userNovel, currentPointMap, requestedPoints); + deleteUserNovelAttractivePoints(userNovel, currentPointMap, requestedPoints); + } + + private void updateKeywords(UserNovel userNovel, List keywordIds) { + Map currentKeywordMap = userNovel.getUserNovelKeywords() + .stream() + .collect(Collectors.toMap(UserNovelKeyword::getKeyword, it -> it)); + + Set requestedKeywords = keywordIds.stream() + .map(keywordService::getKeywordOrException) + .collect(Collectors.toSet()); + + addUserNovelKeywords(userNovel, currentKeywordMap, requestedKeywords); + deleteUserNovelKeywords(userNovel, currentKeywordMap, requestedKeywords); + } + + private void addUserNovelKeywords(UserNovel userNovel, + Map currentKeywordMap, + Set requestedKeywords) { + for (Keyword requested : requestedKeywords) { + if (!currentKeywordMap.containsKey(requested)) { + keywordService.createNovelKeyword(userNovel, requested); + } + } + } + + private void deleteUserNovelKeywords(UserNovel userNovel, + Map currentKeywordMap, + Set requestedKeywords) { + List toDelete = new ArrayList<>(); + for (Map.Entry entry : currentKeywordMap.entrySet()) { + if (!requestedKeywords.contains(entry.getKey())) { + toDelete.add(entry.getValue()); + } + } + if (!toDelete.isEmpty()) { + userNovel.getUserNovelKeywords().removeAll(toDelete); + userNovel.touch(); + } + } + + private void addUserNovelAttractivePoints(UserNovel userNovel, + Map currentPointMap, + Set requestedPoints) { + for (AttractivePoint requested : requestedPoints) { + if (!currentPointMap.containsKey(requested)) { + attractivePointService.createUserNovelAttractivePoint(userNovel, requested); + } + } + } + + private void deleteUserNovelAttractivePoints(UserNovel userNovel, + Map currentPointMap, + Set requestedPoints) { + List toDelete = new ArrayList<>(); + for (Map.Entry entry : currentPointMap.entrySet()) { + if (!requestedPoints.contains(entry.getKey())) { + toDelete.add(entry.getValue()); + } + } + if (!toDelete.isEmpty()) { + userNovel.getUserNovelAttractivePoints().removeAll(toDelete); + userNovel.touch(); + } + } + +} diff --git a/src/main/java/org/websoso/WSSServer/application/LibraryInterestApplication.java b/src/main/java/org/websoso/WSSServer/application/LibraryInterestApplication.java new file mode 100644 index 000000000..492604268 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/LibraryInterestApplication.java @@ -0,0 +1,78 @@ +package org.websoso.WSSServer.application; + +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.ALREADY_INTERESTED; +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.NOT_INTERESTED; +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_ALREADY_EXISTS; + +import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.exception.exception.CustomUserNovelException; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.service.LibraryService; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.service.NovelServiceImpl; + +@Service +@RequiredArgsConstructor +public class LibraryInterestApplication { + + private final NovelServiceImpl novelService; + private final LibraryService libraryService; + + /** + * 관심있어요를 남긴다. + * + * @param user 사용자 객체 + * @param novelId 소설 ID + */ + public void registerAsInterest(User user, Long novelId) { + Novel novel = novelService.getNovelOrException(novelId); + + UserNovel userNovel = user == null ? null : libraryService.getLibraryOrNull(user, novel); + + if (userNovel != null && userNovel.getIsInterest()) { + throw new CustomUserNovelException(ALREADY_INTERESTED, "already registered as interested"); + } + + if (userNovel == null) { + try { + userNovel = createUserNovelByInterest(user, novel); + } catch (DataIntegrityViolationException e) { + userNovel = libraryService.getLibraryOrException(user, novelId); + } + } + + userNovel.setIsInterest(true); + } + + /** + * 관심있어요를 삭제한다. + * + * @param user 사용자 객체 + * @param novelId 소설 ID + */ + public void unregisterAsInterest(User user, Long novelId) { + UserNovel userNovel = libraryService.getLibraryOrException(user, novelId); + + if (!userNovel.getIsInterest()) { + throw new CustomUserNovelException(NOT_INTERESTED, "not registered as interest"); + } + + userNovel.setIsInterest(false); + + if (userNovel.getStatus() == null) { + libraryService.delete(userNovel); + } + + } + + private UserNovel createUserNovelByInterest(User user, Novel novel) { + if (libraryService.getLibraryOrNull(user, novel) != null) { + throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); + } + + return libraryService.createLibrary(null, 0.0f, null, null, user, novel); + } +} diff --git a/src/main/java/org/websoso/WSSServer/scheduler/NovelScheduler.java b/src/main/java/org/websoso/WSSServer/application/PopularNovelScheduler.java similarity index 51% rename from src/main/java/org/websoso/WSSServer/scheduler/NovelScheduler.java rename to src/main/java/org/websoso/WSSServer/application/PopularNovelScheduler.java index 4bc404709..622742c70 100644 --- a/src/main/java/org/websoso/WSSServer/scheduler/NovelScheduler.java +++ b/src/main/java/org/websoso/WSSServer/application/PopularNovelScheduler.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.scheduler; +package org.websoso.WSSServer.application; import java.util.List; import java.util.stream.Collectors; @@ -7,28 +7,29 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.PopularNovel; -import org.websoso.WSSServer.repository.PopularNovelRepository; -import org.websoso.WSSServer.repository.UserNovelRepository; +import org.websoso.WSSServer.library.service.LibraryService; +import org.websoso.WSSServer.novel.domain.PopularNovel; +import org.websoso.WSSServer.novel.service.PopularNovelService; @Service @Transactional @RequiredArgsConstructor -public class NovelScheduler { +public class PopularNovelScheduler { - private final UserNovelRepository userNovelRepository; - private final PopularNovelRepository popularNovelRepository; + private final PopularNovelService popularNovelService; + private final LibraryService libraryService; @Scheduled(cron = "0 0 0 * * ?") public void updatePopularNovels() { - List yesterdayScheduledPopularNovels = popularNovelRepository.findAll(); + List yesterdayScheduledPopularNovels = popularNovelService.getPopularNovels(); + + List topNovelIds = libraryService.getTodayPopularNovelIds(PageRequest.of(0, 30)); - List topNovelIds = userNovelRepository.findTodayPopularNovelsId(PageRequest.of(0, 30)); List popularNovels = topNovelIds.stream() .map(PopularNovel::from) .collect(Collectors.toList()); - popularNovelRepository.saveAll(popularNovels); - popularNovelRepository.deleteAll(yesterdayScheduledPopularNovels); + popularNovelService.saveAll(popularNovels); + popularNovelService.deleteAll(yesterdayScheduledPopularNovels); } } diff --git a/src/main/java/org/websoso/WSSServer/application/ReportApplication.java b/src/main/java/org/websoso/WSSServer/application/ReportApplication.java new file mode 100644 index 000000000..bcea077e5 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/ReportApplication.java @@ -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); + } +} diff --git a/src/main/java/org/websoso/WSSServer/application/SearchNovelApplication.java b/src/main/java/org/websoso/WSSServer/application/SearchNovelApplication.java new file mode 100644 index 000000000..e79af13cc --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/application/SearchNovelApplication.java @@ -0,0 +1,278 @@ +package org.websoso.WSSServer.application; + +import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.domain.Genre; +import org.websoso.WSSServer.domain.GenrePreference; +import org.websoso.WSSServer.library.service.AttractivePointService; +import org.websoso.WSSServer.library.service.KeywordService; +import org.websoso.WSSServer.repository.AvatarProfileRepository; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.domain.common.GenreName; +import org.websoso.WSSServer.dto.novel.FilteredNovelsGetResponse; +import org.websoso.WSSServer.dto.novel.NovelGetResponseBasic; +import org.websoso.WSSServer.dto.novel.NovelGetResponseInfoTab; +import org.websoso.WSSServer.dto.novel.NovelGetResponsePreview; +import org.websoso.WSSServer.dto.novel.SearchedNovelsGetResponse; +import org.websoso.WSSServer.dto.popularNovel.PopularNovelsGetResponse; +import org.websoso.WSSServer.dto.userNovel.TasteNovelGetResponse; +import org.websoso.WSSServer.dto.userNovel.TasteNovelsGetResponse; +import org.websoso.WSSServer.exception.exception.CustomGenreException; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.service.LibraryService; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.NovelGenre; +import org.websoso.WSSServer.novel.service.GenreServiceImpl; +import org.websoso.WSSServer.novel.service.KeywordServiceImpl; +import org.websoso.WSSServer.novel.service.NovelServiceImpl; +import org.websoso.WSSServer.novel.service.PopularNovelService; +import org.websoso.WSSServer.repository.GenrePreferenceRepository; + +@Service +@RequiredArgsConstructor +public class SearchNovelApplication { + + private final NovelServiceImpl novelService; + private final PopularNovelService popularNovelService; + private final GenreServiceImpl genreService; + private final AttractivePointService libraryAttractivePointService; + private final KeywordService libraryKeywordService; + private final KeywordServiceImpl keywordService; + private final LibraryService libraryService; + + // TODO: 삭제될 레포지토리 의존성 + private final FeedRepository feedRepository; + private final GenrePreferenceRepository genrePreferenceRepository; + private final AvatarProfileRepository avatarProfileRepository; + + /** + * 검색어(소셜명, 작가명)에 해당하는 소설 찾기 + * + * @param query 검색할 작품명 or 작가명 + * @param page 페이지 네이션 페이지 + * @param size 페이지 네이션 사이즈 + * @return SearchedNovelsGetResponse + */ + @Transactional(readOnly = true) + public SearchedNovelsGetResponse searchNovels(String query, int page, int size) { + PageRequest pageRequest = PageRequest.of(page, size); + String searchQuery = query.replaceAll("\\s+", "").replaceAll("[^a-zA-Z0-9가-힣]", ""); + + if (searchQuery.isBlank()) { + return SearchedNovelsGetResponse.of(0L, false, Collections.emptyList()); + } + + Page novels = novelService.searchNovels(pageRequest, searchQuery); + + List novelGetResponsePreviews = novels.stream() + .map(this::convertToDTO) + .toList(); + + return SearchedNovelsGetResponse.of(novels.getTotalElements(), novels.hasNext(), novelGetResponsePreviews); + } + + @Transactional(readOnly = true) + public FilteredNovelsGetResponse getFilteredNovels(List genreNames, Boolean isCompleted, Float novelRating, + List keywordIds, int page, int size) { + PageRequest pageRequest = PageRequest.of(page, size); + List genres = getGenres(genreNames); + List keywords = getKeywords(keywordIds); + + Page novels = novelService.findFilteredNovels(pageRequest, genres, keywords, isCompleted, novelRating); + + List novelGetResponsePreviews = novels.stream() + .map(this::convertToDTO) + .toList(); + + return FilteredNovelsGetResponse.of(novels.getTotalElements(), novels.hasNext(), novelGetResponsePreviews); + } + + @Transactional(readOnly = true) + public NovelGetResponseBasic getNovelInfoBasic(User user, Long novelId) { + Novel novel = novelService.getNovelOrException(novelId); + + // TODO: Novel에 List 있는데 굳이? + List novelGenres = novelService.getGenresByNovel(novel); + + int novelRatingCount = libraryService.getRatingCount(novel); + + Float novelRating = novelRatingCount == 0 ? 0.0f + : Math.round(libraryService.getRatingSum(novel) / novelRatingCount * 10.0f) / 10.0f; + return NovelGetResponseBasic.of( + novel, + libraryService.getLibraryOrNull(user, novel), + getNovelGenreNames(novelGenres), + getRandomNovelGenreImage(novelGenres), + libraryService.getInterestCount(novel), + novelRating, + novelRatingCount, + feedRepository.countByNovelId(novelId) + ); + } + + @Transactional(readOnly = true) + public NovelGetResponseInfoTab getNovelInfoInfoTab(Long novelId) { + Novel novel = novelService.getNovelOrException(novelId); + + return NovelGetResponseInfoTab.of( + novel, + novelService.getPlatforms(novel), + libraryAttractivePointService.getAttractivePoints(novel), + libraryKeywordService.getKeywordNameAndCount(novel), + libraryService.getWatchingCount(novel), + libraryService.getWatchedCount(novel), + libraryService.getQuitCount(novel) + ); + } + + @Transactional(readOnly = true) + public TasteNovelsGetResponse getTasteNovels(User user) { + // TODO: 선호하는 장르 리스트는 유저에서 가져오도록 해야함 + List preferGenres = genrePreferenceRepository.findByUser(user) + .stream() + .map(GenrePreference::getGenre) + .toList(); + + List tasteNovels = libraryService.getTasteNovels(preferGenres); + + List tasteNovelGetResponses = tasteNovels.stream() + .map(TasteNovelGetResponse::of) + .toList(); + + return TasteNovelsGetResponse.of(tasteNovelGetResponses); + } + + @Transactional(readOnly = true) + public PopularNovelsGetResponse getTodayPopularNovels() { + List novelIdsFromPopularNovel = popularNovelService.getNovelIdsFromPopularNovel(); + List selectedNovelIdsFromPopularNovel = getSelectedNovelIdsFromPopularNovel(novelIdsFromPopularNovel); + List popularNovels = novelService.getSelectedPopularNovels(selectedNovelIdsFromPopularNovel); + List popularFeedsFromPopularNovels = feedRepository.findPopularFeedsByNovelIds(selectedNovelIdsFromPopularNovel); + + Map feedMap = createFeedMap(popularFeedsFromPopularNovels); + Map avatarMap = createAvatarMap(feedMap); + + return PopularNovelsGetResponse.create(popularNovels, feedMap, avatarMap); + } + + // TODO: DTO로 이전할 명분이 충분한 메서드 + private NovelGetResponsePreview convertToDTO(Novel novel) { + // TODO: UserNovel 리스트 개수를 세는것이 아닌, 개수를 세는 쿼리가 필요 + List userNovels = novel.getUserNovels(); + + long interestCount = userNovels.stream() + .filter(UserNovel::getIsInterest) + .count(); + long novelRatingCount = userNovels.stream() + .filter(un -> un.getUserNovelRating() != 0.0f) + .count(); + double novelRatingSum = userNovels.stream() + .filter(un -> un.getUserNovelRating() != 0.0f) + .mapToDouble(UserNovel::getUserNovelRating) + .sum(); + + Float novelRatingAverage = novelRatingCount == 0 + ? 0.0f + : Math.round((float) (novelRatingSum / novelRatingCount) * 10.0f) / 10.0f; + + return NovelGetResponsePreview.of( + novel, + interestCount, + novelRatingAverage, + novelRatingCount + ); + } + + // TODO: for 문으로 장르를 데이터베이스에서 읽어오는 부분 개선해야함 + private List getGenres(List genreNames) { + genreNames = genreNames == null + ? Collections.emptyList() + : genreNames; + + List genres = new ArrayList<>(); + if (!genreNames.isEmpty()) { + for (String genreName : genreNames) { + genres.add(genreService.getGenreOrException(genreName)); + } + } + + return genres; + } + + // TODO: for 문으로 키워드를 데이터베이스에서 읽어오는 부분 개선해야함 + private List getKeywords(List keywordIds) { + keywordIds = keywordIds == null + ? Collections.emptyList() + : keywordIds; + + List keywords = new ArrayList<>(); + if (!keywordIds.isEmpty()) { + for (Integer keywordId : keywordIds) { + keywords.add(keywordService.getKeywordOrException(keywordId)); + } + } + + return keywords; + } + + private String getNovelGenreNames(List novelGenres) { + return novelGenres.stream() + .map(novelGenre -> getKoreanGenreName(novelGenre.getGenre().getGenreName())) + .collect(Collectors.joining("/")); + } + + private String getKoreanGenreName(String englishGenreName) { + return Arrays.stream(GenreName.values()) + .filter(genreName -> genreName.getLabel().equalsIgnoreCase(englishGenreName)) + .findFirst() + .map(GenreName::getKoreanLabel) + .orElseThrow( + () -> new CustomGenreException(GENRE_NOT_FOUND, "Genre with the given genreName is not found")); + } + + private String getRandomNovelGenreImage(List novelGenres) { + Random random = new Random(); + return novelGenres.get(random.nextInt(novelGenres.size())).getGenre().getGenreImage(); + } + + private List getSelectedNovelIdsFromPopularNovel(List popularNovelIds) { + Collections.shuffle(popularNovelIds); + return popularNovelIds.size() > 10 + ? popularNovelIds.subList(0, 10) + : popularNovelIds; + } + + private Map createFeedMap(List popularFeedsFromPopularNovels) { + return popularFeedsFromPopularNovels.stream() + .collect(Collectors.toMap(Feed::getNovelId, feed -> feed)); + } + + private Map createAvatarMap(Map feedMap) { + Set avatarIds = feedMap.values() + .stream() + .map(feed -> feed.getUser().getAvatarProfileId()) + .collect(Collectors.toSet()); + + List avatars = avatarProfileRepository.findAllById(avatarIds); + return avatars.stream() + .collect(Collectors.toMap(AvatarProfile::getAvatarProfileId, avatar -> avatar)); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java b/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java index 82a7cf024..c90a5a1c8 100644 --- a/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java +++ b/src/main/java/org/websoso/WSSServer/auth/AuthorizationService.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; @Service @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java b/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java index 27e9bf22e..d082e6eac 100644 --- a/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java +++ b/src/main/java/org/websoso/WSSServer/auth/CustomUserArgumentResolver.java @@ -11,8 +11,8 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.service.UserService; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.service.UserService; @Component @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java b/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java index 4fc7c1bbb..2a3ce98cb 100644 --- a/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java +++ b/src/main/java/org/websoso/WSSServer/auth/ResourceAuthorizationHandler.java @@ -9,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.websoso.WSSServer.auth.validator.ResourceAuthorizationValidator; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomAuthorizationException; @Component diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java index 0daba4aac..2712d9d11 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/BlockAuthorizationValidator.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.websoso.WSSServer.domain.Block; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomBlockException; import org.websoso.WSSServer.repository.BlockRepository; diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/CommentAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/CommentAuthorizationValidator.java index c03f3ad7c..60cd7f303 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/CommentAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/CommentAuthorizationValidator.java @@ -5,11 +5,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.websoso.WSSServer.domain.Comment; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomCommentException; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.repository.CommentRepository; +import org.websoso.WSSServer.feed.repository.CommentRepository; @Component @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java index 49f9a9012..1c4c4dc0b 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAccessValidator.java @@ -6,10 +6,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomFeedException; -import org.websoso.WSSServer.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; import org.websoso.WSSServer.service.BlockService; @Component diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java index 593576414..30872df52 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/FeedAuthorizationValidator.java @@ -5,11 +5,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomFeedException; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; @Component @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java index e21383e68..02d4ff994 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/NotificationAuthorizationValidator.java @@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.websoso.WSSServer.domain.Notification; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomNotificationException; import org.websoso.WSSServer.repository.NotificationRepository; diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java index 0882b01a9..f32d651f7 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/NovelAuthorizationValidator.java @@ -4,10 +4,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.exception.exception.CustomNovelException; -import org.websoso.WSSServer.repository.NovelRepository; +import org.websoso.WSSServer.novel.repository.NovelRepository; @Component @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java index acda77266..2f130fa66 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/ResourceAuthorizationValidator.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.auth.validator; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public interface ResourceAuthorizationValidator { diff --git a/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java b/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java index 46145cc8b..7f925fa45 100644 --- a/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java +++ b/src/main/java/org/websoso/WSSServer/auth/validator/UserNovelAuthorizationValidator.java @@ -5,13 +5,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.exception.exception.CustomNovelException; import org.websoso.WSSServer.exception.exception.CustomUserNovelException; -import org.websoso.WSSServer.repository.NovelRepository; -import org.websoso.WSSServer.repository.UserNovelRepository; +import org.websoso.WSSServer.novel.repository.NovelRepository; +import org.websoso.WSSServer.library.repository.UserNovelRepository; @Component @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/controller/AuthController.java b/src/main/java/org/websoso/WSSServer/controller/AuthController.java index 5685e723b..9293bf14c 100644 --- a/src/main/java/org/websoso/WSSServer/controller/AuthController.java +++ b/src/main/java/org/websoso/WSSServer/controller/AuthController.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.auth.AppleLoginRequest; import org.websoso.WSSServer.dto.auth.AuthResponse; import org.websoso.WSSServer.dto.auth.LogoutRequest; @@ -21,23 +21,23 @@ import org.websoso.WSSServer.dto.user.WithdrawalRequest; import org.websoso.WSSServer.oauth2.service.AppleService; import org.websoso.WSSServer.oauth2.service.KakaoService; -import org.websoso.WSSServer.service.AuthService; -import org.websoso.WSSServer.service.UserService; +import org.websoso.WSSServer.application.AccountApplication; +import org.websoso.WSSServer.application.AuthApplication; @RestController @RequiredArgsConstructor public class AuthController { - private final AuthService authService; + private final AuthApplication authApplication; private final KakaoService kakaoService; private final AppleService appleService; - private final UserService userService; + private final AccountApplication accountApplication; @PostMapping("/reissue") public ResponseEntity reissue(@RequestBody ReissueRequest reissueRequest) { return ResponseEntity .status(OK) - .body(authService.reissue(reissueRequest.refreshToken())); + .body(authApplication.reissue(reissueRequest.refreshToken())); } @PostMapping("/auth/login/kakao") @@ -58,7 +58,7 @@ public ResponseEntity loginByApple(@Valid @RequestBody AppleLoginR @PreAuthorize("isAuthenticated()") public ResponseEntity logout(@AuthenticationPrincipal User user, @Valid @RequestBody LogoutRequest request) { - userService.logout(user, request); + authApplication.logout(user, request); return ResponseEntity .status(NO_CONTENT) .build(); @@ -68,7 +68,7 @@ public ResponseEntity logout(@AuthenticationPrincipal User user, @PreAuthorize("isAuthenticated()") public ResponseEntity withdrawUser(@AuthenticationPrincipal User user, @Valid @RequestBody WithdrawalRequest withdrawalRequest) { - userService.withdrawUser(user, withdrawalRequest); + accountApplication.withdrawUser(user, withdrawalRequest); return ResponseEntity .status(NO_CONTENT) .build(); diff --git a/src/main/java/org/websoso/WSSServer/controller/AvatarController.java b/src/main/java/org/websoso/WSSServer/controller/AvatarController.java index 20e224b02..9d8ab07fe 100644 --- a/src/main/java/org/websoso/WSSServer/controller/AvatarController.java +++ b/src/main/java/org/websoso/WSSServer/controller/AvatarController.java @@ -9,22 +9,31 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.dto.avatar.AvatarProfilesGetResponse; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.avatar.AvatarsGetResponse; import org.websoso.WSSServer.service.AvatarService; -@RequestMapping("/avatars") +@RequestMapping @RestController @RequiredArgsConstructor public class AvatarController { private final AvatarService avatarService; - @GetMapping + @GetMapping("/avatars") @PreAuthorize("isAuthenticated()") public ResponseEntity getAvatarList(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) .body(avatarService.getAvatarList(user)); } + + @GetMapping("/avatar-profiles") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getAvatarProfileList(@AuthenticationPrincipal User user) { + return ResponseEntity + .status(OK) + .body(avatarService.getAvatarProfileList(user)); + } } diff --git a/src/main/java/org/websoso/WSSServer/controller/BlockController.java b/src/main/java/org/websoso/WSSServer/controller/BlockController.java index dc5876784..03ea35658 100644 --- a/src/main/java/org/websoso/WSSServer/controller/BlockController.java +++ b/src/main/java/org/websoso/WSSServer/controller/BlockController.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.block.BlocksGetResponse; import org.websoso.WSSServer.service.BlockService; import org.websoso.WSSServer.validation.BlockIdConstraint; diff --git a/src/main/java/org/websoso/WSSServer/controller/FeedController.java b/src/main/java/org/websoso/WSSServer/controller/FeedController.java deleted file mode 100644 index 943ef316e..000000000 --- a/src/main/java/org/websoso/WSSServer/controller/FeedController.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.websoso.WSSServer.controller; - -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NO_CONTENT; -import static org.springframework.http.HttpStatus.OK; -import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE; -import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.common.FeedGetOption; -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.FeedCreateResponse; -import org.websoso.WSSServer.dto.feed.FeedImageUpdateRequest; -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.FeedImageCreateRequest; -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; - -@RequestMapping("/feeds") -@RestController -@RequiredArgsConstructor -public class FeedController { - - private final FeedService feedService; - private final PopularFeedService popularFeedService; - - @PostMapping - @PreAuthorize("isAuthenticated()") - public ResponseEntity createFeed(@AuthenticationPrincipal User user, - @Valid @RequestPart("feed") FeedCreateRequest request, - @Valid @ModelAttribute FeedImageCreateRequest requestImage) { - return ResponseEntity - .status(CREATED) - .body(feedService.createFeed(user, request, requestImage)); - } - - @GetMapping("/{feedId}") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity getFeed(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - return ResponseEntity - .status(OK) - .body(feedService.getFeedById(user, feedId)); - } - - @GetMapping - public ResponseEntity getFeeds(@AuthenticationPrincipal User user, - @RequestParam(value = "category", required = false) String category, - @RequestParam("lastFeedId") Long lastFeedId, - @RequestParam("size") int size, - @RequestParam(value = "feedsOption", required = false) FeedGetOption feedGetOption) { - return ResponseEntity - .status(OK) - .body(feedService.getFeeds(user, category, lastFeedId, size, feedGetOption)); - } - - @PutMapping("/{feedId}") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") - public ResponseEntity updateFeed(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @Valid @RequestPart("feed") FeedUpdateRequest request, - @Valid @ModelAttribute FeedImageUpdateRequest requestImage) { - return ResponseEntity - .status(OK) - .body(feedService.updateFeed(feedId, request, requestImage)); - } - - @DeleteMapping("/{feedId}") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.domain.Feed))") - public ResponseEntity deleteFeed(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - feedService.deleteFeed(feedId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @PostMapping("/{feedId}/likes") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity likeFeed(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - feedService.likeFeed(user, feedId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @DeleteMapping("/{feedId}/likes") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity unLikeFeed(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - feedService.unLikeFeed(user, feedId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @GetMapping("/popular") - public ResponseEntity getPopularFeeds(@AuthenticationPrincipal User user) { - return ResponseEntity - .status(OK) - .body(popularFeedService.getPopularFeeds(user)); - } - - @GetMapping("/interest") - @PreAuthorize("isAuthenticated()") - public ResponseEntity getInterestFeeds(@AuthenticationPrincipal User user) { - return ResponseEntity - .status(OK) - .body(feedService.getInterestFeeds(user)); - } - - @PostMapping("/{feedId}/comments") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity createComment(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @Valid @RequestBody CommentCreateRequest request) { - feedService.createComment(user, feedId, request); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @GetMapping("/{feedId}/comments") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity getComments(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - return ResponseEntity - .status(OK) - .body(feedService.getComments(user, feedId)); - } - - @PutMapping("/{feedId}/comments/{commentId}") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user) " - + "and @authorizationService.validate(#commentId, #user, T(org.websoso.WSSServer.domain.Comment))") - public ResponseEntity updateComment(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @PathVariable("commentId") Long commentId, - @Valid @RequestBody CommentUpdateRequest request) { - feedService.updateComment(user, feedId, commentId, request); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @DeleteMapping("/{feedId}/comments/{commentId}") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user) " - + "and @authorizationService.validate(#commentId, #user, T(org.websoso.WSSServer.domain.Comment))") - public ResponseEntity deleteComment(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @PathVariable("commentId") Long commentId) { - feedService.deleteComment(user, feedId, commentId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @PostMapping("/{feedId}/spoiler") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity reportFeedSpoiler(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - feedService.reportFeed(user, feedId, SPOILER); - return ResponseEntity - .status(CREATED) - .build(); - } - - @PostMapping("/{feedId}/impertinence") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity reportedFeedImpertinence(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId) { - feedService.reportFeed(user, feedId, IMPERTINENCE); - return ResponseEntity - .status(CREATED) - .build(); - } - - @PostMapping("/{feedId}/comments/{commentId}/spoiler") - @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") - public ResponseEntity reportCommentSpoiler(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @PathVariable("commentId") Long commentId) { - feedService.reportComment(user, feedId, commentId, SPOILER); - return ResponseEntity - .status(CREATED) - .build(); - } - - @PostMapping("/{feedId}/comments/{commentId}/impertinence") - @PreAuthorize("isAuthenticated()") - public ResponseEntity reportCommentImpertinence(@AuthenticationPrincipal User user, - @PathVariable("feedId") Long feedId, - @PathVariable("commentId") Long commentId) { - feedService.reportComment(user, feedId, commentId, IMPERTINENCE); - return ResponseEntity - .status(CREATED) - .build(); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/controller/KeywordController.java b/src/main/java/org/websoso/WSSServer/controller/KeywordController.java index c47d63e3d..1a4ddfb48 100644 --- a/src/main/java/org/websoso/WSSServer/controller/KeywordController.java +++ b/src/main/java/org/websoso/WSSServer/controller/KeywordController.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.websoso.WSSServer.dto.keyword.KeywordByCategoryGetResponse; -import org.websoso.WSSServer.service.KeywordService; +import org.websoso.WSSServer.library.service.KeywordService; @RestController @RequestMapping("/keywords") diff --git a/src/main/java/org/websoso/WSSServer/controller/LibraryController.java b/src/main/java/org/websoso/WSSServer/controller/LibraryController.java new file mode 100644 index 000000000..e90aac235 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/controller/LibraryController.java @@ -0,0 +1,138 @@ +package org.websoso.WSSServer.controller; + +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.NO_CONTENT; +import static org.springframework.http.HttpStatus.OK; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +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.application.LibraryEvaluationApplication; +import org.websoso.WSSServer.application.LibraryInterestApplication; +import org.websoso.WSSServer.dto.userNovel.UserNovelCreateRequest; +import org.websoso.WSSServer.dto.userNovel.UserNovelGetResponse; +import org.websoso.WSSServer.dto.userNovel.UserNovelUpdateRequest; +import org.websoso.WSSServer.user.domain.User; + +@RestController +@RequestMapping +@RequiredArgsConstructor +public class LibraryController { + + private final LibraryInterestApplication libraryInterestApplication; + private final LibraryEvaluationApplication libraryEvaluationApplication; + + /** + * 관심있어요 등록 + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @return 204 NO_CONTENT + */ + @PostMapping("/novels/{novelId}/is-interest") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.novel.domain.Novel))") + public ResponseEntity registerAsInterest(@AuthenticationPrincipal User user, + @PathVariable("novelId") Long novelId) { + libraryInterestApplication.registerAsInterest(user, novelId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + /** + * 관심있어요 삭제 + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @return 204 NO_CONTENT + */ + @DeleteMapping("/novels/{novelId}/is-interest") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.library.domain.UserNovel))") + public ResponseEntity unregisterAsInterest(@AuthenticationPrincipal User user, + @PathVariable("novelId") Long novelId) { + libraryInterestApplication.unregisterAsInterest(user, novelId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + /** + * 작품 평가하기 + * + * @param user 로그인 유저 객체 + * @param request 서재 (작품 평가) 생성 객체 + * @return 201 CREATED + */ + @PostMapping("/user-novels") + @PreAuthorize("isAuthenticated()") + public ResponseEntity createEvaluation(@AuthenticationPrincipal User user, + @Valid @RequestBody UserNovelCreateRequest request) { + libraryEvaluationApplication.createEvaluation(user, request); + return ResponseEntity + .status(CREATED) + .build(); + } + + /** + * 작품 평가 불러오기 + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @return UserNovelGetResponse + */ + @GetMapping("/user-novels/{novelId}") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId) { + return ResponseEntity + .status(OK) + .body(libraryEvaluationApplication.getEvaluation(user, novelId)); + } + + /** + * 작품 평가 업데이트하기 + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @param request 서재 (작품 평가) 업데이트 객체 + * @return 204 NO_CONTENT + */ + @PutMapping("/user-novels/{novelId}") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.library.domain.UserNovel))") + public ResponseEntity updateEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId, + @Valid @RequestBody UserNovelUpdateRequest request) { + libraryEvaluationApplication.updateEvaluation(user, novelId, request); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + /** + * 작품 평가 삭제하기 + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @return 204 NO_CONTENT + */ + @DeleteMapping("/user-novels/{novelId}") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.library.domain.UserNovel))") + public ResponseEntity deleteEvaluation(@AuthenticationPrincipal User user, + @PathVariable Long novelId) { + libraryEvaluationApplication.deleteEvaluation(user, novelId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/controller/NotificationController.java b/src/main/java/org/websoso/WSSServer/controller/NotificationController.java index c2f31a61b..17059b336 100644 --- a/src/main/java/org/websoso/WSSServer/controller/NotificationController.java +++ b/src/main/java/org/websoso/WSSServer/controller/NotificationController.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.notification.NotificationCreateRequest; import org.websoso.WSSServer.dto.notification.NotificationGetResponse; import org.websoso.WSSServer.dto.notification.NotificationsGetResponse; diff --git a/src/main/java/org/websoso/WSSServer/controller/NovelController.java b/src/main/java/org/websoso/WSSServer/controller/NovelController.java index d341ad310..ab943873a 100644 --- a/src/main/java/org/websoso/WSSServer/controller/NovelController.java +++ b/src/main/java/org/websoso/WSSServer/controller/NovelController.java @@ -1,6 +1,5 @@ package org.websoso.WSSServer.controller; -import static org.springframework.http.HttpStatus.NO_CONTENT; import static org.springframework.http.HttpStatus.OK; import java.util.List; @@ -8,14 +7,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; 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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.application.SearchNovelApplication; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.novel.FilteredNovelsGetResponse; import org.websoso.WSSServer.dto.novel.NovelGetResponseBasic; import org.websoso.WSSServer.dto.novel.NovelGetResponseFeedTab; @@ -23,26 +21,44 @@ import org.websoso.WSSServer.dto.novel.SearchedNovelsGetResponse; import org.websoso.WSSServer.dto.popularNovel.PopularNovelsGetResponse; import org.websoso.WSSServer.dto.userNovel.TasteNovelsGetResponse; -import org.websoso.WSSServer.service.FeedService; -import org.websoso.WSSServer.service.NovelService; +import org.websoso.WSSServer.feed.service.FeedService; @RestController @RequestMapping("/novels") @RequiredArgsConstructor public class NovelController { - private final NovelService novelService; private final FeedService feedService; + private final SearchNovelApplication searchNovelApplication; + /** + * 검색어를 사용해서 소설을 찾는다. + * + * @param query 검색할 작품명 or 작가명 + * @param page 페이지 네이션 페이지 + * @param size 페이지 네이션 사이즈 + * @return SearchedNovelsGetResponse + */ @GetMapping public ResponseEntity searchNovels(@RequestParam(required = false) String query, @RequestParam int page, @RequestParam int size) { return ResponseEntity .status(OK) - .body(novelService.searchNovels(query, page, size)); + .body(searchNovelApplication.searchNovels(query, page, size)); } + /** + * 상세 탐색 API + * + * @param genres 장르 리스트 + * @param isCompleted 완결 여부 + * @param novelRating 소설 평점 + * @param keywordIds 키워드 리스트 + * @param page 페이지 + * @param size 사이즈 + * @return FilteredNovelsGetResponse + */ @GetMapping("/filtered") @PreAuthorize("isAuthenticated()") public ResponseEntity getFilteredNovels( @@ -54,40 +70,67 @@ public ResponseEntity getFilteredNovels( @RequestParam int size) { return ResponseEntity .status(OK) - .body(novelService.getFilteredNovels(genres, isCompleted, novelRating, keywordIds, page, size)); + .body(searchNovelApplication.getFilteredNovels(genres, isCompleted, novelRating, keywordIds, page, + size)); } - @GetMapping("/popular") - public ResponseEntity getTodayPopularNovels(@AuthenticationPrincipal User user) { - //TODO 차단 관계에 있는 유저의 피드글 처리 + /** + * 작품정보 조회 API (기본) + * + * @param user 로그인 유저 객체 + * @param novelId 소설 ID + * @return NovelGetResponseBasic + */ + @GetMapping("/{novelId}") + public ResponseEntity getNovelInfoBasic(@AuthenticationPrincipal User user, + @PathVariable Long novelId) { return ResponseEntity .status(OK) - .body(novelService.getTodayPopularNovels()); + .body(searchNovelApplication.getNovelInfoBasic(user, novelId)); } - @GetMapping("/taste") - @PreAuthorize("isAuthenticated()") - public ResponseEntity getTasteNovels(@AuthenticationPrincipal User user) { + /** + * 작품정보 조회 API (정보탭) + * + * @param novelId 소설 ID + * @return NovelGetResponseInfoTab + */ + @GetMapping("/{novelId}/info") + public ResponseEntity getNovelInfoInfoTab(@PathVariable Long novelId) { return ResponseEntity .status(OK) - .body(novelService.getTasteNovels(user)); + .body(searchNovelApplication.getNovelInfoInfoTab(novelId)); } - @GetMapping("/{novelId}") - public ResponseEntity getNovelInfoBasic(@AuthenticationPrincipal User user, - @PathVariable Long novelId) { + /** + * 인기있는 소설 조회 API (오늘의 발견) + * + * @param user 로그인 유저 객체 + * @return PopularNovelsGetResponse + */ + @GetMapping("/popular") + public ResponseEntity getTodayPopularNovels(@AuthenticationPrincipal User user) { + //TODO 차단 관계에 있는 유저의 피드글 처리 return ResponseEntity .status(OK) - .body(novelService.getNovelInfoBasic(user, novelId)); + .body(searchNovelApplication.getTodayPopularNovels()); } - @GetMapping("/{novelId}/info") - public ResponseEntity getNovelInfoInfoTab(@PathVariable Long novelId) { + /** + * 취향에 해당하는 웹소설 조회 API (취향 추천 작품 조회) + * + * @param user 로그인 유저 객체 + * @return TasteNovelsGetResponse + */ + @GetMapping("/taste") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getTasteNovels(@AuthenticationPrincipal User user) { return ResponseEntity .status(OK) - .body(novelService.getNovelInfoInfoTab(novelId)); + .body(searchNovelApplication.getTasteNovels(user)); } + // TODO: Feed Controller로 이동해야함 @GetMapping("/{novelId}/feeds") public ResponseEntity getFeedsByNovel(@AuthenticationPrincipal User user, @PathVariable Long novelId, @@ -97,24 +140,4 @@ public ResponseEntity getFeedsByNovel(@AuthenticationPr .status(OK) .body(feedService.getFeedsByNovel(user, novelId, lastFeedId, size)); } - - @PostMapping("/{novelId}/is-interest") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.Novel))") - public ResponseEntity registerAsInterest(@AuthenticationPrincipal User user, - @PathVariable("novelId") Long novelId) { - novelService.registerAsInterest(user, novelId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @DeleteMapping("/{novelId}/is-interest") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") - public ResponseEntity unregisterAsInterest(@AuthenticationPrincipal User user, - @PathVariable("novelId") Long novelId) { - novelService.unregisterAsInterest(user, novelId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } } diff --git a/src/main/java/org/websoso/WSSServer/controller/UserController.java b/src/main/java/org/websoso/WSSServer/controller/UserController.java index 1538a5852..4bc386ba5 100644 --- a/src/main/java/org/websoso/WSSServer/controller/UserController.java +++ b/src/main/java/org/websoso/WSSServer/controller/UserController.java @@ -21,7 +21,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.application.AuthApplication; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.SortCriteria; import org.websoso.WSSServer.dto.feed.UserFeedsGetResponse; import org.websoso.WSSServer.dto.notification.PushSettingGetResponse; @@ -45,9 +46,9 @@ import org.websoso.WSSServer.dto.userNovel.UserNovelAndNovelsGetResponse; import org.websoso.WSSServer.dto.userNovel.UserNovelAndNovelsGetResponseLegacy; import org.websoso.WSSServer.dto.userNovel.UserTasteAttractivePointPreferencesAndKeywordsGetResponse; -import org.websoso.WSSServer.service.FeedService; -import org.websoso.WSSServer.service.UserNovelService; -import org.websoso.WSSServer.service.UserService; +import org.websoso.WSSServer.feed.service.FeedService; +import org.websoso.WSSServer.library.service.UserNovelService; +import org.websoso.WSSServer.user.service.UserService; import org.websoso.WSSServer.validation.NicknameConstraint; @RestController @@ -58,11 +59,12 @@ public class UserController { private final UserService userService; private final UserNovelService userNovelService; + private final AuthApplication authApplication; private final FeedService feedService; @PostMapping("/login") public ResponseEntity login(@RequestBody String userId) { - LoginResponse response = userService.login(Long.valueOf(userId)); + LoginResponse response = authApplication.login(Long.valueOf(userId)); return ResponseEntity .status(OK) .body(response); diff --git a/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java b/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java deleted file mode 100644 index 09b353b2d..000000000 --- a/src/main/java/org/websoso/WSSServer/controller/UserNovelController.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.websoso.WSSServer.controller; - -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.NO_CONTENT; -import static org.springframework.http.HttpStatus.OK; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.DeleteMapping; -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.PutMapping; -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.domain.Novel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.dto.userNovel.UserNovelCreateRequest; -import org.websoso.WSSServer.dto.userNovel.UserNovelGetResponse; -import org.websoso.WSSServer.dto.userNovel.UserNovelUpdateRequest; -import org.websoso.WSSServer.service.NovelService; -import org.websoso.WSSServer.service.UserNovelService; - -@RequestMapping("/user-novels") -@RestController -@RequiredArgsConstructor -public class UserNovelController { - - private final NovelService novelService; - private final UserNovelService userNovelService; - - @PostMapping - @PreAuthorize("isAuthenticated()") - public ResponseEntity createEvaluation(@AuthenticationPrincipal User user, - @Valid @RequestBody UserNovelCreateRequest request) { - userNovelService.createEvaluation(user, request); - return ResponseEntity - .status(CREATED) - .build(); - } - - @GetMapping("/{novelId}") - @PreAuthorize("isAuthenticated()") - public ResponseEntity getEvaluation(@AuthenticationPrincipal User user, - @PathVariable Long novelId) { - Novel novel = novelService.getNovelOrException(novelId); - return ResponseEntity - .status(OK) - .body(userNovelService.getEvaluation(user, novel)); - } - - @PutMapping("/{novelId}") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") - public ResponseEntity updateEvaluation(@AuthenticationPrincipal User user, - @PathVariable Long novelId, - @Valid @RequestBody UserNovelUpdateRequest request) { - userNovelService.updateEvaluation(user, novelId, request); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } - - @DeleteMapping("/{novelId}") - @PreAuthorize("isAuthenticated() and @authorizationService.validate(#novelId, #user, T(org.websoso.WSSServer.domain.UserNovel))") - public ResponseEntity deleteEvaluation(@AuthenticationPrincipal User user, - @PathVariable Long novelId) { - userNovelService.deleteEvaluation(user, novelId); - return ResponseEntity - .status(NO_CONTENT) - .build(); - } -} diff --git a/src/main/java/org/websoso/WSSServer/domain/Avatar.java b/src/main/java/org/websoso/WSSServer/domain/Avatar.java index e3f31394a..fea2ced61 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Avatar.java +++ b/src/main/java/org/websoso/WSSServer/domain/Avatar.java @@ -7,6 +7,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import java.util.List; import lombok.AccessLevel; @@ -31,4 +32,9 @@ public class Avatar { @OneToMany(mappedBy = "avatar", cascade = ALL, orphanRemoval = true) private List avatarLines; + + @OneToMany(cascade = ALL, orphanRemoval = true) + @JoinColumn(name = "avatar_id") + private List avatarProfiles; + } diff --git a/src/main/java/org/websoso/WSSServer/domain/AvatarProfile.java b/src/main/java/org/websoso/WSSServer/domain/AvatarProfile.java new file mode 100644 index 000000000..9be4085fa --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/domain/AvatarProfile.java @@ -0,0 +1,43 @@ +package org.websoso.WSSServer.domain; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AvatarProfile { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(nullable = false) + private Long avatarProfileId; + + @Column(columnDefinition = "varchar(30)", nullable = false) + private String avatarProfileName; + + @Column(columnDefinition = "varchar(100)", nullable = false) + private String avatarProfileImage; + + @Column(columnDefinition = "varchar(100)", nullable = false) + private String avatarCharacterImage; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "avatar_profile_id") + private List avatarLines; + + public boolean isSameAvatarProfile(Long avatarProfileId) { + return this.avatarProfileId.equals(avatarProfileId); + } +} diff --git a/src/main/java/org/websoso/WSSServer/domain/AvatarProfileLine.java b/src/main/java/org/websoso/WSSServer/domain/AvatarProfileLine.java new file mode 100644 index 000000000..f8bcb6d8a --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/domain/AvatarProfileLine.java @@ -0,0 +1,26 @@ +package org.websoso.WSSServer.domain; + +import static jakarta.persistence.GenerationType.IDENTITY; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AvatarProfileLine { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(nullable = false) + private Long avatarProfileLineId; + + @Column(columnDefinition = "varchar(50)", nullable = false) + private String avatarLine; + +} diff --git a/src/main/java/org/websoso/WSSServer/domain/GenrePreference.java b/src/main/java/org/websoso/WSSServer/domain/GenrePreference.java index 143b72047..3a03582c6 100644 --- a/src/main/java/org/websoso/WSSServer/domain/GenrePreference.java +++ b/src/main/java/org/websoso/WSSServer/domain/GenrePreference.java @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.user.domain.User; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/domain/ReadNotification.java b/src/main/java/org/websoso/WSSServer/domain/ReadNotification.java index 813ece57b..821221f1c 100644 --- a/src/main/java/org/websoso/WSSServer/domain/ReadNotification.java +++ b/src/main/java/org/websoso/WSSServer/domain/ReadNotification.java @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.user.domain.User; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfileGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfileGetResponse.java new file mode 100644 index 000000000..5189df557 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfileGetResponse.java @@ -0,0 +1,27 @@ +package org.websoso.WSSServer.dto.avatar; + +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.domain.AvatarProfileLine; + +public record AvatarProfileGetResponse( + Long avatarProfileId, + String avatarProfileName, + String avatarProfileLine, + String avatarProfileImage, + String avatarCharacterImage, + Boolean isRepresentative +) { + public static AvatarProfileGetResponse of(AvatarProfile avatarProfile, + AvatarProfileLine avatarProfileLine, + Long representativeAvatarProfileId) { + return new AvatarProfileGetResponse( + avatarProfile.getAvatarProfileId(), + avatarProfile.getAvatarProfileName(), + avatarProfileLine.getAvatarLine(), + avatarProfile.getAvatarProfileImage(), + avatarProfile.getAvatarCharacterImage(), + avatarProfile.isSameAvatarProfile(representativeAvatarProfileId) + + ); + } +} diff --git a/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfilesGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfilesGetResponse.java new file mode 100644 index 000000000..c984ba584 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/dto/avatar/AvatarProfilesGetResponse.java @@ -0,0 +1,9 @@ +package org.websoso.WSSServer.dto.avatar; + +import java.util.List; + +public record AvatarProfilesGetResponse( + List avatarProfiles +) { + +} diff --git a/src/main/java/org/websoso/WSSServer/dto/block/BlockGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/block/BlockGetResponse.java index 339d57ff4..e01a47a5b 100644 --- a/src/main/java/org/websoso/WSSServer/dto/block/BlockGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/block/BlockGetResponse.java @@ -1,8 +1,9 @@ package org.websoso.WSSServer.dto.block; import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; import org.websoso.WSSServer.domain.Block; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public record BlockGetResponse( Long blockId, @@ -10,12 +11,12 @@ public record BlockGetResponse( String nickname, String avatarImage ) { - public static BlockGetResponse of(Block block, User blockedUser, Avatar avatarOfBlockedUser) { + public static BlockGetResponse of(Block block, User blockedUser, AvatarProfile avatarOfBlockedUser) { return new BlockGetResponse( block.getBlockId(), blockedUser.getUserId(), blockedUser.getNickname(), - avatarOfBlockedUser.getAvatarImage() + avatarOfBlockedUser.getAvatarProfileImage() ); } } diff --git a/src/main/java/org/websoso/WSSServer/dto/comment/CommentGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/comment/CommentGetResponse.java index 6c74baba9..e59f16b5c 100644 --- a/src/main/java/org/websoso/WSSServer/dto/comment/CommentGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/comment/CommentGetResponse.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDate; -import org.websoso.WSSServer.domain.Comment; +import org.websoso.WSSServer.feed.domain.Comment; import org.websoso.WSSServer.dto.user.UserBasicInfo; public record CommentGetResponse( diff --git a/src/main/java/org/websoso/WSSServer/dto/feed/FeedCreateResponse.java b/src/main/java/org/websoso/WSSServer/dto/feed/FeedCreateResponse.java index 23b833967..93b84aacd 100644 --- a/src/main/java/org/websoso/WSSServer/dto/feed/FeedCreateResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/feed/FeedCreateResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.feed; -import org.websoso.WSSServer.domain.FeedImage; +import org.websoso.WSSServer.feed.domain.FeedImage; import java.util.Comparator; import java.util.List; diff --git a/src/main/java/org/websoso/WSSServer/dto/feed/FeedGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/feed/FeedGetResponse.java index 8a8af1a5e..7fa460efb 100644 --- a/src/main/java/org/websoso/WSSServer/dto/feed/FeedGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/feed/FeedGetResponse.java @@ -2,10 +2,10 @@ import java.time.format.DateTimeFormatter; import java.util.List; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedImage; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.FeedImage; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.dto.user.UserBasicInfo; public record FeedGetResponse( diff --git a/src/main/java/org/websoso/WSSServer/dto/feed/FeedInfo.java b/src/main/java/org/websoso/WSSServer/dto/feed/FeedInfo.java index 26e7be6d6..1adf366eb 100644 --- a/src/main/java/org/websoso/WSSServer/dto/feed/FeedInfo.java +++ b/src/main/java/org/websoso/WSSServer/dto/feed/FeedInfo.java @@ -2,10 +2,10 @@ import java.time.format.DateTimeFormatter; import java.util.List; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.dto.user.UserBasicInfo; public record FeedInfo( diff --git a/src/main/java/org/websoso/WSSServer/dto/feed/InterestFeedGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/feed/InterestFeedGetResponse.java index 3e840803b..d96021449 100644 --- a/src/main/java/org/websoso/WSSServer/dto/feed/InterestFeedGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/feed/InterestFeedGetResponse.java @@ -1,10 +1,11 @@ package org.websoso.WSSServer.dto.feed; import org.websoso.WSSServer.domain.Avatar; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.library.domain.UserNovel; public record InterestFeedGetResponse( Long novelId, @@ -18,7 +19,7 @@ public record InterestFeedGetResponse( String feedContent ) { - public static InterestFeedGetResponse of(Novel novel, User user, Feed feed, Avatar avatar) { + public static InterestFeedGetResponse of(Novel novel, User user, Feed feed, AvatarProfile avatarProfile) { Long novelRatingCount = getNovelRatingCount(novel); Float novelRating = getNovelRating(novel, novelRatingCount); @@ -30,7 +31,7 @@ public static InterestFeedGetResponse of(Novel novel, User user, Feed feed, Avat novelRatingCount, feed.getIsPublic(), user.getNickname(), - avatar.getAvatarImage(), + avatarProfile.getAvatarProfileImage(), feed.getFeedContent() ); } diff --git a/src/main/java/org/websoso/WSSServer/dto/feed/UserFeedGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/feed/UserFeedGetResponse.java index 101d9f58c..521af0795 100644 --- a/src/main/java/org/websoso/WSSServer/dto/feed/UserFeedGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/feed/UserFeedGetResponse.java @@ -2,12 +2,12 @@ import java.time.LocalDate; import java.util.List; -import org.websoso.WSSServer.domain.Category; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedCategory; -import org.websoso.WSSServer.domain.Like; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.feed.domain.Category; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.FeedCategory; +import org.websoso.WSSServer.feed.domain.Like; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; public record UserFeedGetResponse( Long feedId, diff --git a/src/main/java/org/websoso/WSSServer/dto/keyword/CategoryGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/keyword/CategoryGetResponse.java index 2f43cd1f9..a0ef17652 100644 --- a/src/main/java/org/websoso/WSSServer/dto/keyword/CategoryGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/keyword/CategoryGetResponse.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.dto.keyword; import java.util.List; -import org.websoso.WSSServer.domain.KeywordCategory; +import org.websoso.WSSServer.library.domain.KeywordCategory; public record CategoryGetResponse( String categoryName, diff --git a/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordCountGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordCountGetResponse.java index d3107fb6a..f3346485d 100644 --- a/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordCountGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordCountGetResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.keyword; -import org.websoso.WSSServer.domain.Keyword; +import org.websoso.WSSServer.library.domain.Keyword; public record KeywordCountGetResponse( String keywordName, diff --git a/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordGetResponse.java index bf61ce50d..f62189a8a 100644 --- a/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/keyword/KeywordGetResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.keyword; -import org.websoso.WSSServer.domain.Keyword; +import org.websoso.WSSServer.library.domain.Keyword; public record KeywordGetResponse( Integer keywordId, diff --git a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseBasic.java b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseBasic.java index 1c2eef8f1..e89df78ff 100644 --- a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseBasic.java +++ b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseBasic.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.dto.novel; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; public record NovelGetResponseBasic( Long userNovelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseInfoTab.java b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseInfoTab.java index 0cbebd7bb..047c7bfbf 100644 --- a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseInfoTab.java +++ b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponseInfoTab.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.dto.novel; import java.util.List; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.novel.domain.Novel; import org.websoso.WSSServer.dto.keyword.KeywordCountGetResponse; import org.websoso.WSSServer.dto.platform.PlatformGetResponse; diff --git a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponsePreview.java b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponsePreview.java index 4dc183992..b61206965 100644 --- a/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponsePreview.java +++ b/src/main/java/org/websoso/WSSServer/dto/novel/NovelGetResponsePreview.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.novel; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.novel.domain.Novel; public record NovelGetResponsePreview( Long novelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/platform/PlatformGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/platform/PlatformGetResponse.java index 700e2c6a1..9128af04d 100644 --- a/src/main/java/org/websoso/WSSServer/dto/platform/PlatformGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/platform/PlatformGetResponse.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.dto.platform; -import org.websoso.WSSServer.domain.NovelPlatform; -import org.websoso.WSSServer.domain.Platform; +import org.websoso.WSSServer.novel.domain.NovelPlatform; +import org.websoso.WSSServer.novel.domain.Platform; public record PlatformGetResponse( String platformName, diff --git a/src/main/java/org/websoso/WSSServer/dto/popularFeed/PopularFeedGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/popularFeed/PopularFeedGetResponse.java index 7fa90ff6b..600f08645 100644 --- a/src/main/java/org/websoso/WSSServer/dto/popularFeed/PopularFeedGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/popularFeed/PopularFeedGetResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.popularFeed; -import org.websoso.WSSServer.domain.PopularFeed; +import org.websoso.WSSServer.feed.domain.PopularFeed; public record PopularFeedGetResponse( Long feedId, diff --git a/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelGetResponse.java index 0c9833708..aa93769e5 100644 --- a/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelGetResponse.java @@ -1,8 +1,9 @@ package org.websoso.WSSServer.dto.popularNovel; import org.websoso.WSSServer.domain.Avatar; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.novel.domain.Novel; public record PopularNovelGetResponse( Long novelId, @@ -13,8 +14,8 @@ public record PopularNovelGetResponse( String feedContent ) { - public static PopularNovelGetResponse of(Novel novel, Avatar avatar, Feed feed) { - if (avatar == null && feed == null) { + public static PopularNovelGetResponse of(Novel novel, AvatarProfile avatarProfile, Feed feed) { + if (avatarProfile == null && feed == null) { return new PopularNovelGetResponse( novel.getNovelId(), novel.getTitle(), @@ -28,7 +29,7 @@ public static PopularNovelGetResponse of(Novel novel, Avatar avatar, Feed feed) novel.getNovelId(), novel.getTitle(), novel.getNovelImage(), - avatar.getAvatarImage(), + avatarProfile.getAvatarProfileImage(), feed.getUser().getNickname(), feed.getFeedContent() ); diff --git a/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelsGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelsGetResponse.java index 5e7861b60..eaf8096f0 100644 --- a/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelsGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/popularNovel/PopularNovelsGetResponse.java @@ -1,8 +1,28 @@ package org.websoso.WSSServer.dto.popularNovel; import java.util.List; +import java.util.Map; +import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.novel.domain.Novel; public record PopularNovelsGetResponse( List popularNovels ) { + public static PopularNovelsGetResponse create(List popularNovels, + Map feedMap, + Map avatarMap) { + List popularNovelResponses = popularNovels.stream() + .map(novel -> { + Feed feed = feedMap.get(novel.getNovelId()); + if (feed == null) { + return PopularNovelGetResponse.of(novel, null, null); + } + AvatarProfile avatar = avatarMap.get(feed.getUser().getAvatarProfileId()); + return PopularNovelGetResponse.of(novel, avatar, feed); + }) + .toList(); + return new PopularNovelsGetResponse(popularNovelResponses); + } } diff --git a/src/main/java/org/websoso/WSSServer/dto/sosoPick/SosoPickNovelGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/sosoPick/SosoPickNovelGetResponse.java index 6582624c9..832f52032 100644 --- a/src/main/java/org/websoso/WSSServer/dto/sosoPick/SosoPickNovelGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/sosoPick/SosoPickNovelGetResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.sosoPick; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.novel.domain.Novel; public record SosoPickNovelGetResponse( Long novelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/user/MyProfileResponse.java b/src/main/java/org/websoso/WSSServer/dto/user/MyProfileResponse.java index fdc9c9afc..871918c71 100644 --- a/src/main/java/org/websoso/WSSServer/dto/user/MyProfileResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/user/MyProfileResponse.java @@ -3,8 +3,9 @@ import java.util.List; import java.util.stream.Collectors; import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; import org.websoso.WSSServer.domain.GenrePreference; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public record MyProfileResponse( String nickname, @@ -13,11 +14,11 @@ public record MyProfileResponse( List genrePreferences ) { - public static MyProfileResponse of(User user, Avatar avatar, List genrePreferences) { + public static MyProfileResponse of(User user, AvatarProfile avatar, List genrePreferences) { return new MyProfileResponse( user.getNickname(), user.getIntro(), - avatar.getAvatarImage(), + avatar.getAvatarProfileImage(), genrePreferences.stream() .map(gp -> gp.getGenre().getGenreName()) .collect(Collectors.toList())); diff --git a/src/main/java/org/websoso/WSSServer/dto/user/ProfileGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/user/ProfileGetResponse.java index f18c76038..a08e98174 100644 --- a/src/main/java/org/websoso/WSSServer/dto/user/ProfileGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/user/ProfileGetResponse.java @@ -2,9 +2,9 @@ import java.util.List; import java.util.stream.Collectors; -import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; import org.websoso.WSSServer.domain.GenrePreference; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public record ProfileGetResponse( String nickname, @@ -14,7 +14,7 @@ public record ProfileGetResponse( List genrePreferences ) { - public static ProfileGetResponse of(boolean isMyProfile, User user, Avatar avatar, + public static ProfileGetResponse of(boolean isMyProfile, User user, AvatarProfile avatar, List genrePreferences) { boolean isProfilePublic = isMyProfile ? true @@ -22,7 +22,7 @@ public static ProfileGetResponse of(boolean isMyProfile, User user, Avatar avata return new ProfileGetResponse( user.getNickname(), user.getIntro(), - avatar.getAvatarImage(), + avatar.getAvatarProfileImage(), isProfilePublic, genrePreferences.stream() .map(gp -> gp.getGenre().getGenreName()) diff --git a/src/main/java/org/websoso/WSSServer/dto/user/UpdateMyProfileRequest.java b/src/main/java/org/websoso/WSSServer/dto/user/UpdateMyProfileRequest.java index 78ac21782..33a59eac3 100644 --- a/src/main/java/org/websoso/WSSServer/dto/user/UpdateMyProfileRequest.java +++ b/src/main/java/org/websoso/WSSServer/dto/user/UpdateMyProfileRequest.java @@ -6,7 +6,7 @@ import org.websoso.WSSServer.validation.NullAllowedNicknameConstraint; public record UpdateMyProfileRequest( - Byte avatarId, + Long avatarId, @NullAllowedNicknameConstraint String nickname, diff --git a/src/main/java/org/websoso/WSSServer/dto/user/UserIdAndNicknameResponse.java b/src/main/java/org/websoso/WSSServer/dto/user/UserIdAndNicknameResponse.java index c61fae28a..3eac4b7d4 100644 --- a/src/main/java/org/websoso/WSSServer/dto/user/UserIdAndNicknameResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/user/UserIdAndNicknameResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.user; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public record UserIdAndNicknameResponse( Long userId, diff --git a/src/main/java/org/websoso/WSSServer/dto/user/UserInfoGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/user/UserInfoGetResponse.java index a53849750..c6108a8e7 100644 --- a/src/main/java/org/websoso/WSSServer/dto/user/UserInfoGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/user/UserInfoGetResponse.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.user; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; public record UserInfoGetResponse( String email, diff --git a/src/main/java/org/websoso/WSSServer/dto/userNovel/TasteNovelGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/userNovel/TasteNovelGetResponse.java index 8f016052d..90d7de826 100644 --- a/src/main/java/org/websoso/WSSServer/dto/userNovel/TasteNovelGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/userNovel/TasteNovelGetResponse.java @@ -1,7 +1,7 @@ package org.websoso.WSSServer.dto.userNovel; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; public record TasteNovelGetResponse( Long novelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponse.java index 27896cbe3..aef49bd66 100644 --- a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponse.java @@ -2,12 +2,12 @@ import java.time.LocalDate; import java.util.List; -import org.websoso.WSSServer.domain.AttractivePoint; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.UserNovelAttractivePoint; -import org.websoso.WSSServer.domain.UserNovelKeyword; +import org.websoso.WSSServer.library.domain.AttractivePoint; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelAttractivePoint; +import org.websoso.WSSServer.library.domain.UserNovelKeyword; public record UserNovelAndNovelGetResponse( Long userNovelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponseLegacy.java b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponseLegacy.java index adf40f41a..301a783d8 100644 --- a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponseLegacy.java +++ b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelAndNovelGetResponseLegacy.java @@ -1,6 +1,6 @@ package org.websoso.WSSServer.dto.userNovel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovel; public record UserNovelAndNovelGetResponseLegacy( Long userNovelId, diff --git a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelGetResponse.java b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelGetResponse.java index 7758b7412..2af9fcb12 100644 --- a/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelGetResponse.java +++ b/src/main/java/org/websoso/WSSServer/dto/userNovel/UserNovelGetResponse.java @@ -2,8 +2,8 @@ import java.time.LocalDate; import java.util.List; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.dto.keyword.KeywordGetResponse; public record UserNovelGetResponse( diff --git a/src/main/java/org/websoso/WSSServer/feed/controller/CommentController.java b/src/main/java/org/websoso/WSSServer/feed/controller/CommentController.java new file mode 100644 index 000000000..a8f2ad897 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/controller/CommentController.java @@ -0,0 +1,75 @@ +package org.websoso.WSSServer.feed.controller; + +import static org.springframework.http.HttpStatus.NO_CONTENT; +import static org.springframework.http.HttpStatus.OK; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +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.PutMapping; +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.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.user.domain.User; + +@RequestMapping("/feeds") +@RestController +@RequiredArgsConstructor +public class CommentController { + + private final CommentFindApplication commentFindApplication; + private final CommentManagementApplication commentManagementApplication; + + @PostMapping("/{feedId}/comments") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity createComment(@AuthenticationPrincipal User user, @PathVariable("feedId") Long feedId, + @Valid @RequestBody CommentCreateRequest request) { + commentManagementApplication.createComment(user, feedId, request); + return ResponseEntity.status(NO_CONTENT).build(); + } + + @GetMapping("/{feedId}/comments") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity getComments(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + return ResponseEntity + .status(OK) + .body(commentFindApplication.getComments(user, feedId)); + } + + @PutMapping("/{feedId}/comments/{commentId}") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user) " + + "and @authorizationService.validate(#commentId, #user, T(org.websoso.WSSServer.feed.domain.Comment))") + public ResponseEntity updateComment(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId, + @PathVariable("commentId") Long commentId, + @Valid @RequestBody CommentUpdateRequest request) { + commentManagementApplication.updateComment(user, feedId, commentId, request); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + @DeleteMapping("/{feedId}/comments/{commentId}") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user) " + + "and @authorizationService.validate(#commentId, #user, T(org.websoso.WSSServer.feed.domain.Comment))") + public ResponseEntity deleteComment(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId, + @PathVariable("commentId") Long commentId) { + commentManagementApplication.deleteComment(user, feedId, commentId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/controller/FeedController.java b/src/main/java/org/websoso/WSSServer/feed/controller/FeedController.java new file mode 100644 index 000000000..9f7ca44e2 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/controller/FeedController.java @@ -0,0 +1,130 @@ +package org.websoso.WSSServer.feed.controller; + +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.NO_CONTENT; +import static org.springframework.http.HttpStatus.OK; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.websoso.WSSServer.application.FeedFindApplication; +import org.websoso.WSSServer.domain.common.FeedGetOption; +import org.websoso.WSSServer.dto.feed.FeedCreateRequest; +import org.websoso.WSSServer.dto.feed.FeedCreateResponse; +import org.websoso.WSSServer.dto.feed.FeedGetResponse; +import org.websoso.WSSServer.dto.feed.FeedImageCreateRequest; +import org.websoso.WSSServer.dto.feed.FeedImageUpdateRequest; +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.feed.service.FeedService; +import org.websoso.WSSServer.user.domain.User; + +@RequestMapping("/feeds") +@RestController +@RequiredArgsConstructor +public class FeedController { + + private final FeedService feedService; + private final FeedFindApplication feedFindApplication; + + @PostMapping + @PreAuthorize("isAuthenticated()") + public ResponseEntity createFeed(@AuthenticationPrincipal User user, + @Valid @RequestPart("feed") FeedCreateRequest request, + @Valid @ModelAttribute FeedImageCreateRequest requestImage) { + return ResponseEntity + .status(CREATED) + .body(feedService.createFeed(user, request, requestImage)); + } + + @GetMapping("/{feedId}") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity getFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + return ResponseEntity + .status(OK) + .body(feedFindApplication.getFeedById(user, feedId)); + } + + @GetMapping + public ResponseEntity getFeeds(@AuthenticationPrincipal User user, + @RequestParam(value = "category", required = false) String category, + @RequestParam("lastFeedId") Long lastFeedId, + @RequestParam("size") int size, + @RequestParam(value = "feedsOption", required = false) FeedGetOption feedGetOption) { + return ResponseEntity + .status(OK) + .body(feedFindApplication.getFeeds(user, category, lastFeedId, size, feedGetOption)); + } + + @PutMapping("/{feedId}") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.feed.domain.Feed))") + public ResponseEntity updateFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId, + @Valid @RequestPart("feed") FeedUpdateRequest request, + @Valid @ModelAttribute FeedImageUpdateRequest requestImage) { + return ResponseEntity + .status(OK) + .body(feedService.updateFeed(feedId, request, requestImage)); + } + + @DeleteMapping("/{feedId}") + @PreAuthorize("isAuthenticated() and @authorizationService.validate(#feedId, #user, T(org.websoso.WSSServer.feed.domain.Feed))") + public ResponseEntity deleteFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + feedService.deleteFeed(feedId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + @PostMapping("/{feedId}/likes") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity likeFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + feedService.likeFeed(user, feedId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + @DeleteMapping("/{feedId}/likes") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity unLikeFeed(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + feedService.unLikeFeed(user, feedId); + return ResponseEntity + .status(NO_CONTENT) + .build(); + } + + @GetMapping("/popular") + public ResponseEntity getPopularFeeds(@AuthenticationPrincipal User user) { + return ResponseEntity + .status(OK) + .body(feedFindApplication.getPopularFeeds(user)); + } + + @GetMapping("/interest") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getInterestFeeds(@AuthenticationPrincipal User user) { + return ResponseEntity + .status(OK) + .body(feedFindApplication.getInterestFeeds(user)); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/feed/controller/ReportController.java b/src/main/java/org/websoso/WSSServer/feed/controller/ReportController.java new file mode 100644 index 000000000..5810d0ddf --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/controller/ReportController.java @@ -0,0 +1,66 @@ +package org.websoso.WSSServer.feed.controller; + +import static org.springframework.http.HttpStatus.CREATED; +import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE; +import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.websoso.WSSServer.application.ReportApplication; +import org.websoso.WSSServer.user.domain.User; + +@RequestMapping("/feeds") +@RestController +@RequiredArgsConstructor +public class ReportController { + + private final ReportApplication reportApplication; + + @PostMapping("/{feedId}/spoiler") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportFeedSpoiler(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + reportApplication.reportFeed(user, feedId, SPOILER); + return ResponseEntity + .status(CREATED) + .build(); + } + + @PostMapping("/{feedId}/impertinence") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportedFeedImpertinence(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId) { + reportApplication.reportFeed(user, feedId, IMPERTINENCE); + return ResponseEntity + .status(CREATED) + .build(); + } + + @PostMapping("/{feedId}/comments/{commentId}/spoiler") + @PreAuthorize("isAuthenticated() and @feedAccessValidator.canAccess(#feedId, #user)") + public ResponseEntity reportCommentSpoiler(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId, + @PathVariable("commentId") Long commentId) { + reportApplication.reportComment(user, feedId, commentId, SPOILER); + return ResponseEntity + .status(CREATED) + .build(); + } + + @PostMapping("/{feedId}/comments/{commentId}/impertinence") + @PreAuthorize("isAuthenticated()") + public ResponseEntity reportCommentImpertinence(@AuthenticationPrincipal User user, + @PathVariable("feedId") Long feedId, + @PathVariable("commentId") Long commentId) { + reportApplication.reportComment(user, feedId, commentId, IMPERTINENCE); + return ResponseEntity + .status(CREATED) + .build(); + } +} diff --git a/src/main/java/org/websoso/WSSServer/domain/Category.java b/src/main/java/org/websoso/WSSServer/feed/domain/Category.java similarity index 94% rename from src/main/java/org/websoso/WSSServer/domain/Category.java rename to src/main/java/org/websoso/WSSServer/feed/domain/Category.java index 2dbd836b5..01e54ffa3 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Category.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/Category.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/Comment.java b/src/main/java/org/websoso/WSSServer/feed/domain/Comment.java similarity index 98% rename from src/main/java/org/websoso/WSSServer/domain/Comment.java rename to src/main/java/org/websoso/WSSServer/feed/domain/Comment.java index 50d0692d7..c8a34efa5 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Comment.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/Comment.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_BELONG_TO_FEED; diff --git a/src/main/java/org/websoso/WSSServer/domain/Feed.java b/src/main/java/org/websoso/WSSServer/feed/domain/Feed.java similarity index 97% rename from src/main/java/org/websoso/WSSServer/domain/Feed.java rename to src/main/java/org/websoso/WSSServer/feed/domain/Feed.java index f5cb6720a..ec362a356 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Feed.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/Feed.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.GenerationType.IDENTITY; @@ -21,6 +21,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; +import org.websoso.WSSServer.user.domain.User; @Getter @DynamicInsert diff --git a/src/main/java/org/websoso/WSSServer/domain/FeedCategory.java b/src/main/java/org/websoso/WSSServer/feed/domain/FeedCategory.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/FeedCategory.java rename to src/main/java/org/websoso/WSSServer/feed/domain/FeedCategory.java index 46f38cc93..cfc77c087 100644 --- a/src/main/java/org/websoso/WSSServer/domain/FeedCategory.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/FeedCategory.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/FeedImage.java b/src/main/java/org/websoso/WSSServer/feed/domain/FeedImage.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/FeedImage.java rename to src/main/java/org/websoso/WSSServer/feed/domain/FeedImage.java index 4ce4d25ae..7fef910f7 100644 --- a/src/main/java/org/websoso/WSSServer/domain/FeedImage.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/FeedImage.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/Like.java b/src/main/java/org/websoso/WSSServer/feed/domain/Like.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/Like.java rename to src/main/java/org/websoso/WSSServer/feed/domain/Like.java index b52374c74..09234a0d0 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Like.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/Like.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/PopularFeed.java b/src/main/java/org/websoso/WSSServer/feed/domain/PopularFeed.java similarity index 95% rename from src/main/java/org/websoso/WSSServer/domain/PopularFeed.java rename to src/main/java/org/websoso/WSSServer/feed/domain/PopularFeed.java index 3ba7be11e..d9152418c 100644 --- a/src/main/java/org/websoso/WSSServer/domain/PopularFeed.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/PopularFeed.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/ReportedComment.java b/src/main/java/org/websoso/WSSServer/feed/domain/ReportedComment.java similarity index 94% rename from src/main/java/org/websoso/WSSServer/domain/ReportedComment.java rename to src/main/java/org/websoso/WSSServer/feed/domain/ReportedComment.java index 940c65414..1e90cdcba 100644 --- a/src/main/java/org/websoso/WSSServer/domain/ReportedComment.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/ReportedComment.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -14,6 +14,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.ReportedType; @Entity diff --git a/src/main/java/org/websoso/WSSServer/domain/ReportedFeed.java b/src/main/java/org/websoso/WSSServer/feed/domain/ReportedFeed.java similarity index 94% rename from src/main/java/org/websoso/WSSServer/domain/ReportedFeed.java rename to src/main/java/org/websoso/WSSServer/feed/domain/ReportedFeed.java index 2b64a8f02..6639480d8 100644 --- a/src/main/java/org/websoso/WSSServer/domain/ReportedFeed.java +++ b/src/main/java/org/websoso/WSSServer/feed/domain/ReportedFeed.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.feed.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -14,6 +14,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.ReportedType; @Entity diff --git a/src/main/java/org/websoso/WSSServer/repository/CategoryRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/CategoryRepository.java similarity index 78% rename from src/main/java/org/websoso/WSSServer/repository/CategoryRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/CategoryRepository.java index a51646c23..4a404d983 100644 --- a/src/main/java/org/websoso/WSSServer/repository/CategoryRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/CategoryRepository.java @@ -1,9 +1,9 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Category; +import org.websoso.WSSServer.feed.domain.Category; import org.websoso.WSSServer.domain.common.CategoryName; @Repository diff --git a/src/main/java/org/websoso/WSSServer/repository/CommentRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/CommentRepository.java similarity index 89% rename from src/main/java/org/websoso/WSSServer/repository/CommentRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/CommentRepository.java index 6591dab7b..9a888d224 100644 --- a/src/main/java/org/websoso/WSSServer/repository/CommentRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/CommentRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import io.lettuce.core.dynamic.annotation.Param; import java.util.List; @@ -7,7 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Comment; +import org.websoso.WSSServer.feed.domain.Comment; @Repository public interface CommentRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepository.java similarity index 74% rename from src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepository.java index 14130378a..c9cc97dca 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; -import org.websoso.WSSServer.domain.Category; -import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.feed.domain.Category; +import org.websoso.WSSServer.feed.domain.Feed; import org.websoso.WSSServer.domain.Genre; public interface FeedCategoryCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepositoryImpl.java similarity index 87% rename from src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepositoryImpl.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepositoryImpl.java index 7ab5b31db..8bcead9b7 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryCustomRepositoryImpl.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import static org.websoso.WSSServer.domain.QBlock.block; -import static org.websoso.WSSServer.domain.QFeed.feed; -import static org.websoso.WSSServer.domain.QFeedCategory.feedCategory; import static org.websoso.WSSServer.domain.QGenre.genre; -import static org.websoso.WSSServer.domain.QNovel.novel; -import static org.websoso.WSSServer.domain.QNovelGenre.novelGenre; +import static org.websoso.WSSServer.feed.domain.QFeed.feed; +import static org.websoso.WSSServer.feed.domain.QFeedCategory.feedCategory; +import static org.websoso.WSSServer.novel.domain.QNovel.novel; +import static org.websoso.WSSServer.novel.domain.QNovelGenre.novelGenre; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; @@ -16,8 +16,8 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Category; -import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.feed.domain.Category; +import org.websoso.WSSServer.feed.domain.Feed; import org.websoso.WSSServer.domain.Genre; @Repository diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryRepository.java similarity index 84% rename from src/main/java/org/websoso/WSSServer/repository/FeedCategoryRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryRepository.java index 7e9e9da7d..a2fa226f0 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedCategoryRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCategoryRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; import org.springframework.data.domain.PageRequest; @@ -6,9 +6,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Category; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedCategory; +import org.websoso.WSSServer.feed.domain.Category; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.FeedCategory; @Repository public interface FeedCategoryRepository extends JpaRepository, FeedCategoryCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedCustomRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepository.java similarity index 86% rename from src/main/java/org/websoso/WSSServer/repository/FeedCustomRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepository.java index b2fd22382..73380f27c 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepository.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Slice; -import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.feed.domain.Feed; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.SortCriteria; public interface FeedCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepositoryImpl.java similarity index 93% rename from src/main/java/org/websoso/WSSServer/repository/FeedCustomRepositoryImpl.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepositoryImpl.java index 66f00af2f..3889f1592 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedCustomRepositoryImpl.java @@ -1,12 +1,12 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import static org.websoso.WSSServer.domain.QBlock.block; -import static org.websoso.WSSServer.domain.QFeed.feed; -import static org.websoso.WSSServer.domain.QFeedImage.feedImage; import static org.websoso.WSSServer.domain.QGenre.genre; -import static org.websoso.WSSServer.domain.QLike.like; -import static org.websoso.WSSServer.domain.QNovel.novel; -import static org.websoso.WSSServer.domain.QNovelGenre.novelGenre; +import static org.websoso.WSSServer.feed.domain.QFeed.feed; +import static org.websoso.WSSServer.feed.domain.QFeedImage.feedImage; +import static org.websoso.WSSServer.feed.domain.QLike.like; +import static org.websoso.WSSServer.novel.domain.QNovel.novel; +import static org.websoso.WSSServer.novel.domain.QNovelGenre.novelGenre; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; @@ -23,10 +23,10 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedImage; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.FeedImage; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.FeedImageType; import org.websoso.WSSServer.domain.common.SortCriteria; diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedImageCustomRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedImageCustomRepository.java similarity index 59% rename from src/main/java/org/websoso/WSSServer/repository/FeedImageCustomRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedImageCustomRepository.java index 00be12551..e8201d0f3 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedImageCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedImageCustomRepository.java @@ -1,7 +1,7 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.Optional; -import org.websoso.WSSServer.domain.FeedImage; +import org.websoso.WSSServer.feed.domain.FeedImage; public interface FeedImageCustomRepository { Optional findThumbnailFeedImageByFeedId(long feedId); diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedImageRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedImageRepository.java similarity index 65% rename from src/main/java/org/websoso/WSSServer/repository/FeedImageRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedImageRepository.java index 2cb76f7b0..ca49ad466 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedImageRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedImageRepository.java @@ -1,7 +1,7 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import org.springframework.data.jpa.repository.JpaRepository; -import org.websoso.WSSServer.domain.FeedImage; +import org.websoso.WSSServer.feed.domain.FeedImage; public interface FeedImageRepository extends JpaRepository { Integer countByFeedId(Long feedId); diff --git a/src/main/java/org/websoso/WSSServer/repository/FeedRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/FeedRepository.java similarity index 95% rename from src/main/java/org/websoso/WSSServer/repository/FeedRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/FeedRepository.java index a755470d1..8d777c269 100644 --- a/src/main/java/org/websoso/WSSServer/repository/FeedRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/FeedRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; import org.springframework.data.domain.PageRequest; @@ -8,7 +8,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.feed.domain.Feed; @Repository public interface FeedRepository extends JpaRepository, FeedCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/LikeRepository.java similarity index 71% rename from src/main/java/org/websoso/WSSServer/repository/LikeRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/LikeRepository.java index 2cc3f53bb..087acf0c5 100644 --- a/src/main/java/org/websoso/WSSServer/repository/LikeRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/LikeRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Like; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.Like; @Repository public interface LikeRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepository.java similarity index 56% rename from src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepository.java index 8238b4066..646476b38 100644 --- a/src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepository.java @@ -1,7 +1,7 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; -import org.websoso.WSSServer.domain.PopularFeed; +import org.websoso.WSSServer.feed.domain.PopularFeed; public interface PopularFeedCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepositoryImpl.java similarity index 88% rename from src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepositoryImpl.java rename to src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepositoryImpl.java index 7d2d4bc44..50bcfadec 100644 --- a/src/main/java/org/websoso/WSSServer/repository/PopularFeedCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedCustomRepositoryImpl.java @@ -1,14 +1,15 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import static org.websoso.WSSServer.domain.QBlock.block; -import static org.websoso.WSSServer.domain.QPopularFeed.popularFeed; +import static org.websoso.WSSServer.feed.domain.QPopularFeed.popularFeed; + import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.PopularFeed; +import org.websoso.WSSServer.feed.domain.PopularFeed; @Repository @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/repository/PopularFeedRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedRepository.java similarity index 70% rename from src/main/java/org/websoso/WSSServer/repository/PopularFeedRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedRepository.java index 3c8204a09..e2fd2e9c0 100644 --- a/src/main/java/org/websoso/WSSServer/repository/PopularFeedRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/PopularFeedRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.PopularFeed; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.PopularFeed; @Repository public interface PopularFeedRepository extends JpaRepository, PopularFeedCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/ReportedCommentRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/ReportedCommentRepository.java similarity index 80% rename from src/main/java/org/websoso/WSSServer/repository/ReportedCommentRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/ReportedCommentRepository.java index 0b45b6434..71ae2b57a 100644 --- a/src/main/java/org/websoso/WSSServer/repository/ReportedCommentRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/ReportedCommentRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import io.lettuce.core.dynamic.annotation.Param; import java.util.List; @@ -6,9 +6,9 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Comment; -import org.websoso.WSSServer.domain.ReportedComment; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.feed.domain.ReportedComment; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.ReportedType; @Repository diff --git a/src/main/java/org/websoso/WSSServer/repository/ReportedFeedRepository.java b/src/main/java/org/websoso/WSSServer/feed/repository/ReportedFeedRepository.java similarity index 69% rename from src/main/java/org/websoso/WSSServer/repository/ReportedFeedRepository.java rename to src/main/java/org/websoso/WSSServer/feed/repository/ReportedFeedRepository.java index 68dac8e75..5611ee866 100644 --- a/src/main/java/org/websoso/WSSServer/repository/ReportedFeedRepository.java +++ b/src/main/java/org/websoso/WSSServer/feed/repository/ReportedFeedRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.feed.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.ReportedFeed; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.ReportedFeed; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.ReportedType; @Repository diff --git a/src/main/java/org/websoso/WSSServer/feed/service/CommentService.java b/src/main/java/org/websoso/WSSServer/feed/service/CommentService.java new file mode 100644 index 000000000..f9ec11a21 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/CommentService.java @@ -0,0 +1,219 @@ +package org.websoso.WSSServer.feed.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.exception.error.CustomAvatarError.AVATAR_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomUserError.USER_NOT_FOUND; + +import java.util.AbstractMap; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.NotificationType; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; +import org.websoso.WSSServer.dto.comment.CommentCreateRequest; +import org.websoso.WSSServer.dto.comment.CommentGetResponse; +import org.websoso.WSSServer.dto.comment.CommentUpdateRequest; +import org.websoso.WSSServer.dto.comment.CommentsGetResponse; +import org.websoso.WSSServer.dto.user.UserBasicInfo; +import org.websoso.WSSServer.exception.exception.CustomAvatarException; +import org.websoso.WSSServer.exception.exception.CustomCommentException; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.exception.exception.CustomNovelException; +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.repository.CommentRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.ReportedCommentRepository; +import org.websoso.WSSServer.notification.FCMClient; +import org.websoso.WSSServer.notification.dto.FCMMessageRequest; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.repository.NovelRepository; +import org.websoso.WSSServer.repository.AvatarRepository; +import org.websoso.WSSServer.repository.BlockRepository; +import org.websoso.WSSServer.repository.NotificationRepository; +import org.websoso.WSSServer.repository.NotificationTypeRepository; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentService { + + private final CommentRepository commentRepository; + private final FeedRepository feedRepository; + private final NotificationRepository notificationRepository; + private final NotificationTypeRepository notificationTypeRepository; + private final BlockRepository blockRepository; + private final NovelRepository novelRepository; + private final UserRepository userRepository; + private final ReportedCommentRepository reportedCommentRepository; + private final AvatarRepository avatarRepository; + private final DiscordMessageClient discordMessageClient; + private final FCMClient fcmClient; + + private static final int NOTIFICATION_TITLE_MAX_LENGTH = 12; + private static final int NOTIFICATION_TITLE_MIN_LENGTH = 0; + + @Transactional + public void createComment(User user, Long feedId, CommentCreateRequest request) { + Feed feed = getFeedOrException(feedId); + commentRepository.save(Comment.create(user.getUserId(), feed, request.commentContent())); + sendCommentPushMessageToFeedOwner(user, feed); + sendCommentPushMessageToCommenters(user, feed); + } + + private Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId) + .orElseThrow(() -> new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } + + private void sendCommentPushMessageToFeedOwner(User user, Feed feed) { + User feedOwner = feed.getUser(); + if (user.equals(feedOwner) || blockRepository.existsByBlockingIdAndBlockedId(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(); + + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + } + + //ToDo : CommentService와 중복되는 부분 추출 필요 + 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 -> !blockRepository.existsByBlockingIdAndBlockedId(userId, user.getUserId()) + && !blockRepository.existsByBlockingIdAndBlockedId(userId, feed.getUser().getUserId())) + .distinct().map(userId -> userRepository.findById(userId).orElseThrow( + () -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found"))) + .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())); + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + }); + } + + private String createNotificationTitle(Feed feed) { + if (feed.getNovelId() == null) { + String feedContent = feed.getFeedContent(); + feedContent = feedContent.length() <= NOTIFICATION_TITLE_MAX_LENGTH ? feedContent + : feedContent.substring(NOTIFICATION_TITLE_MIN_LENGTH, NOTIFICATION_TITLE_MAX_LENGTH); + return "'" + feedContent + "...'"; + } + Novel novel = novelRepository.findById(feed.getNovelId()) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); + return novel.getTitle(); + } + + @Transactional(readOnly = true) + public CommentsGetResponse getComments(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + List responses = feed.getComments().stream() + .map(comment -> new AbstractMap.SimpleEntry<>(comment, userRepository.findById(comment.getUserId()) + .orElseThrow( + () -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found")))) + .map(entry -> CommentGetResponse.of(getUserBasicInfo(entry.getValue()), entry.getKey(), + isUserCommentOwner(entry.getValue(), user), entry.getKey().getIsSpoiler(), + isBlocked(user, entry.getValue()), entry.getKey().getIsHidden())).toList(); + + return CommentsGetResponse.of(responses); + } + + private Boolean isUserCommentOwner(User createdUser, User user) { + return createdUser.equals(user); + } + + private Boolean isBlocked(User user, User createdFeedUser) { + return blockRepository.existsByBlockingIdAndBlockedId(user.getUserId(), createdFeedUser.getUserId()); + } + + 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()); + } + + @Transactional + public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { + Feed feed = getFeedOrException(feedId); + Comment comment = commentRepository.findById(commentId).orElseThrow( + () -> new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); + comment.validateFeedAssociation(feed); + comment.validateUserAuthorization(user.getUserId(), UPDATE); + comment.updateContent(request.commentContent()); + } + + @Transactional + public void deleteComment(User user, Long feedId, Long commentId) { + Feed feed = getFeedOrException(feedId); + Comment comment = commentRepository.findById(commentId).orElseThrow( + () -> new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); + comment.validateFeedAssociation(feed); + comment.validateUserAuthorization(user.getUserId(), DELETE); + commentRepository.delete(comment); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/CommentServiceImpl.java b/src/main/java/org/websoso/WSSServer/feed/service/CommentServiceImpl.java new file mode 100644 index 000000000..2a5934d41 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/CommentServiceImpl.java @@ -0,0 +1,42 @@ +package org.websoso.WSSServer.feed.service; + +import static org.websoso.WSSServer.exception.error.CustomCommentError.COMMENT_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.dto.comment.CommentCreateRequest; +import org.websoso.WSSServer.dto.comment.CommentUpdateRequest; +import org.websoso.WSSServer.exception.exception.CustomCommentException; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.repository.CommentRepository; + +@Service +@RequiredArgsConstructor +public class CommentServiceImpl { + + private final CommentRepository commentRepository; + + @Transactional + public void createComment(User user, Feed feed, CommentCreateRequest request) { + commentRepository.save(Comment.create(user.getUserId(), feed, request.commentContent())); + } + + @Transactional(readOnly = true) + public Comment findComment(Long commentId) { + return commentRepository.findById(commentId).orElseThrow( + () -> new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); + } + + @Transactional + public void updateComment(Comment comment, CommentUpdateRequest request) { + comment.updateContent(request.commentContent()); + } + + @Transactional + public void deleteComment(Comment comment) { + commentRepository.delete(comment); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/FeedService.java b/src/main/java/org/websoso/WSSServer/feed/service/FeedService.java new file mode 100644 index 000000000..9909db010 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/FeedService.java @@ -0,0 +1,576 @@ +package org.websoso.WSSServer.feed.service; + +import static java.lang.Boolean.TRUE; +import static org.websoso.WSSServer.exception.error.CustomAvatarError.AVATAR_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomCategoryError.CATEGORY_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomCategoryError.INVALID_CATEGORY_FORMAT; +import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_LIKED; +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomFeedError.NOT_LIKED; +import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomUserError.PRIVATE_PROFILE_STATUS; +import static org.websoso.WSSServer.exception.error.CustomUserError.USER_NOT_FOUND; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +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.springframework.web.multipart.MultipartFile; +import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.domain.Genre; +import org.websoso.WSSServer.domain.GenrePreference; +import org.websoso.WSSServer.domain.Notification; +import org.websoso.WSSServer.domain.NotificationType; +import org.websoso.WSSServer.repository.AvatarProfileRepository; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; +import org.websoso.WSSServer.domain.common.CategoryName; +import org.websoso.WSSServer.domain.common.FeedGetOption; +import org.websoso.WSSServer.domain.common.SortCriteria; +import org.websoso.WSSServer.dto.feed.FeedCreateRequest; +import org.websoso.WSSServer.dto.feed.FeedCreateResponse; +import org.websoso.WSSServer.dto.feed.FeedGetResponse; +import org.websoso.WSSServer.dto.feed.FeedImageCreateRequest; +import org.websoso.WSSServer.dto.feed.FeedImageDeleteEvent; +import org.websoso.WSSServer.dto.feed.FeedImageUpdateRequest; +import org.websoso.WSSServer.dto.feed.FeedInfo; +import org.websoso.WSSServer.dto.feed.FeedUpdateRequest; +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.feed.UserFeedGetResponse; +import org.websoso.WSSServer.dto.feed.UserFeedsGetResponse; +import org.websoso.WSSServer.dto.novel.NovelGetResponseFeedTab; +import org.websoso.WSSServer.dto.user.UserBasicInfo; +import org.websoso.WSSServer.exception.exception.CustomAvatarException; +import org.websoso.WSSServer.exception.exception.CustomCategoryException; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.exception.exception.CustomGenreException; +import org.websoso.WSSServer.exception.exception.CustomNovelException; +import org.websoso.WSSServer.exception.exception.CustomUserException; +import org.websoso.WSSServer.feed.domain.Category; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.FeedCategory; +import org.websoso.WSSServer.feed.domain.FeedImage; +import org.websoso.WSSServer.feed.domain.Like; +import org.websoso.WSSServer.feed.domain.PopularFeed; +import org.websoso.WSSServer.feed.repository.CategoryRepository; +import org.websoso.WSSServer.feed.repository.CommentRepository; +import org.websoso.WSSServer.feed.repository.FeedCategoryRepository; +import org.websoso.WSSServer.feed.repository.FeedImageCustomRepository; +import org.websoso.WSSServer.feed.repository.FeedImageRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.LikeRepository; +import org.websoso.WSSServer.feed.repository.PopularFeedRepository; +import org.websoso.WSSServer.feed.repository.ReportedCommentRepository; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.repository.UserNovelRepository; +import org.websoso.WSSServer.notification.FCMClient; +import org.websoso.WSSServer.notification.dto.FCMMessageRequest; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.repository.NovelRepository; +import org.websoso.WSSServer.repository.AvatarRepository; +import org.websoso.WSSServer.repository.BlockRepository; +import org.websoso.WSSServer.repository.GenrePreferenceRepository; +import org.websoso.WSSServer.repository.GenreRepository; +import org.websoso.WSSServer.repository.NotificationRepository; +import org.websoso.WSSServer.repository.NotificationTypeRepository; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.service.ImageClient; + +@Service +@RequiredArgsConstructor +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 static final int NOTIFICATION_TITLE_MAX_LENGTH = 12; + private static final int NOTIFICATION_TITLE_MIN_LENGTH = 0; + + private final NovelRepository novelRepository; + private final FeedRepository feedRepository; + private final CategoryRepository categoryRepository; + private final FeedCategoryRepository feedCategoryRepository; + private final CommentRepository commentRepository; + private final ReportedCommentRepository reportedCommentRepository; + private final ApplicationEventPublisher eventPublisher; + private final LikeRepository likeRepository; + private final PopularFeedRepository popularFeedRepository; + private final NotificationRepository notificationRepository; + private final NotificationTypeRepository notificationTypeRepository; + private final BlockRepository blockRepository; + private final GenrePreferenceRepository genrePreferenceRepository; + private final UserRepository userRepository; + private final AvatarProfileRepository avatarProfileRepository; + private final FeedImageRepository feedImageRepository; + private final FeedImageCustomRepository feedImageCustomRepository; + private final UserNovelRepository userNovelRepository; + private final GenreRepository genreRepository; + private final ImageClient imageClient; + private final FCMClient fcmClient; + + @Transactional + public FeedCreateResponse createFeed(User user, FeedCreateRequest request, FeedImageCreateRequest imagesRequest) { + List feedImages = processFeedImages(imagesRequest.images()); + + Optional.ofNullable(request.novelId()).ifPresent(novelId -> novelRepository.findById(novelId) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found"))); + Feed feed = Feed.create(request.feedContent(), request.novelId(), request.isSpoiler(), request.isPublic(), user, + feedImages); + + feedRepository.save(feed); + for (String relevantCategory : request.relevantCategories()) { + Category category = findCategoryByName(relevantCategory); + feedCategoryRepository.save(FeedCategory.create(feed, category)); + } + + return FeedCreateResponse.of(feedImages); + } + + @Transactional + public FeedCreateResponse updateFeed(Long feedId, FeedUpdateRequest request, FeedImageUpdateRequest imagesRequest) { + Feed feed = getFeedOrException(feedId); + + List oldImages = new ArrayList<>(feed.getImages()); + + if (request.novelId() != null && feed.isNovelChanged(request.novelId())) { + novelRepository.findById(request.novelId()) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, + "novel with the given id is not found")); + } + + List feedImages = processFeedImages(imagesRequest.images()); + + feed.updateFeed(request.feedContent(), request.isSpoiler(), request.isPublic(), request.novelId(), feedImages); + List feedCategories = feedCategoryRepository.findByFeed(feed); + + if (feedCategories.isEmpty()) { + throw new CustomCategoryException(CATEGORY_NOT_FOUND, "Category for the given feed was not found"); + } + + Set categories = feedCategories.stream().map(FeedCategory::getCategory).collect(Collectors.toSet()); + + Set newCategories = request.relevantCategories().stream().map(this::findCategoryByName) + .collect(Collectors.toSet()); + + for (Category newCategory : newCategories) { + if (categories.contains(newCategory)) { + categories.remove(newCategory); + } else { + feedCategoryRepository.save(FeedCategory.create(feed, newCategory)); + } + } + + for (Category category : categories) { + feedCategoryRepository.deleteByCategoryAndFeed(category, feed); + } + + List oldImageUrls = oldImages.stream().map(FeedImage::getUrl).toList(); + eventPublisher.publishEvent(new FeedImageDeleteEvent(oldImageUrls)); + + return FeedCreateResponse.of(feedImages); + } + + private List processFeedImages(List images) { + List uploadedImageUrls = new ArrayList<>(); + + if (images != null && !images.isEmpty()) { + try { + for (MultipartFile image : images) { + String imageUrl = imageClient.uploadFeedImage(image); + uploadedImageUrls.add(imageUrl); + } + } catch (Exception e) { + if (!uploadedImageUrls.isEmpty()) { + imageClient.deleteImages(uploadedImageUrls); + } + + throw e; + } + } + + List feedImages = new ArrayList<>(); + if (!uploadedImageUrls.isEmpty()) { + feedImages.add(FeedImage.createThumbnail(uploadedImageUrls.get(0))); + for (int i = 1; i < uploadedImageUrls.size(); i++) { + feedImages.add(FeedImage.createCommon(uploadedImageUrls.get(i), i)); + } + } + + return feedImages; + } + + @Transactional + public void deleteFeed(Long feedId) { + List commentIds = commentRepository.findAllByFeedId(feedId).stream().map(Comment::getCommentId).toList(); + reportedCommentRepository.deleteByCommentIdsIn(commentIds); + feedRepository.deleteById(feedId); + } + + @Transactional + public void likeFeed(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + + if (likeRepository.existsByUserIdAndFeed(user.getUserId(), feed)) { + throw new CustomFeedException(ALREADY_LIKED, "user already liked that feed"); + } + likeRepository.save(Like.create(user.getUserId(), feed)); + + if (feed.getLikes().size() == POPULAR_FEED_LIKE_THRESHOLD) { + if (!popularFeedRepository.existsByFeed(feed)) { + popularFeedRepository.save(PopularFeed.create(feed)); + + sendPopularFeedPushMessage(feed); + } + } + + sendLikePushMessage(user, feed); + } + + private void sendLikePushMessage(User liker, Feed feed) { + User feedOwner = feed.getUser(); + if (liker.equals(feedOwner) || blockRepository.existsByBlockingIdAndBlockedId(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(); + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + } + + private String createNotificationTitle(Feed feed) { + if (feed.getNovelId() == null) { + String feedContent = feed.getFeedContent(); + feedContent = feedContent.length() <= NOTIFICATION_TITLE_MAX_LENGTH ? feedContent + : feedContent.substring(NOTIFICATION_TITLE_MIN_LENGTH, NOTIFICATION_TITLE_MAX_LENGTH); + return "'" + feedContent + "...'"; + } + Novel novel = novelRepository.findById(feed.getNovelId()) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); + return novel.getTitle(); + } + + @Transactional + public void unLikeFeed(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + Like like = likeRepository.findByUserIdAndFeed(user.getUserId(), feed) + .orElseThrow(() -> new CustomFeedException(NOT_LIKED, + "User did not like this feed or like already deleted")); + likeRepository.delete(like); + } + + @Transactional(readOnly = true) + public FeedGetResponse getFeedById(User user, Long feedId) { + Feed feed = getFeedOrException(feedId); + UserBasicInfo feedUserBasicInfo = getUserBasicInfo(feed.getUser()); + Novel novel = getLinkedNovelOrNull(feed.getNovelId()); + Boolean isLiked = isUserLikedFeed(user, feed); + List 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); + } + + @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 genres = getPreferenceGenres(user); + + Slice feeds = findFeedsByCategoryLabel(getChosenCategoryOrDefault(category), lastFeedId, userIdOrNull, + PageRequest.of(DEFAULT_PAGE_NUMBER, size), feedGetOption, genres); + + List 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 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 Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId) + .orElseThrow(() -> new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } + + private UserBasicInfo getUserBasicInfo(User user) { + return user.getUserBasicInfo( + avatarProfileRepository.findById(user.getAvatarProfileId()).orElseThrow(() -> + new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")) + .getAvatarProfileImage()); + } + + private Novel getLinkedNovelOrNull(Long linkedNovelId) { + if (linkedNovelId == null) { + return null; + } + return novelRepository.findById(linkedNovelId) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, + "novel with the given id is not found")); + } + + private Boolean isUserLikedFeed(User user, Feed feed) { + return likeRepository.existsByUserIdAndFeed(user.getUserId(), feed); + } + + private Boolean isUserFeedOwner(User createdUser, User user) { + return createdUser.equals(user); + } + + 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 relevantCategories = feed.getFeedCategories().stream() + .map(feedCategory -> feedCategory.getCategory().getCategoryName().getLabel()) + .collect(Collectors.toList()); + Boolean isMyFeed = user != null && isUserFeedOwner(feed.getUser(), user); + Integer imageCount = feedImageRepository.countByFeedId(feed.getFeedId()); + Optional thumbnailImage = feedImageCustomRepository.findThumbnailFeedImageByFeedId(feed.getFeedId()); + String thumbnailUrl = thumbnailImage.map(FeedImage::getUrl).orElse(null); + + return FeedInfo.of(feed, userBasicInfo, novel, isLiked, relevantCategories, isMyFeed, thumbnailUrl, imageCount, + user); + } + + private Slice findFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, PageRequest pageRequest, + FeedGetOption feedGetOption, List genres) { + if (DEFAULT_CATEGORY.equals(category)) { + if (FeedGetOption.isAll(feedGetOption)) { + return feedRepository.findFeeds(lastFeedId, userId, pageRequest); + } else { + return feedRepository.findRecommendedFeeds(lastFeedId, userId, pageRequest, genres); + } + } else { + if (FeedGetOption.isAll(feedGetOption)) { + return feedCategoryRepository.findFeedsByCategory(findCategoryByName(category), lastFeedId, userId, + pageRequest); + } else { + return feedCategoryRepository.findRecommendedFeedsByCategoryLabel(findCategoryByName(category), + lastFeedId, + userId, pageRequest, genres); + } + } + } + + @Transactional(readOnly = true) + public InterestFeedsGetResponse getInterestFeeds(User user) { + List interestNovels = userNovelRepository.findByUserAndIsInterestTrue(user).stream() + .map(UserNovel::getNovel).toList(); + + if (interestNovels.isEmpty()) { + return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_INTEREST_NOVELS"); + } + + Map novelMap = interestNovels.stream() + .collect(Collectors.toMap(Novel::getNovelId, novel -> novel)); + List interestNovelIds = new ArrayList<>(novelMap.keySet()); + + List interestFeeds = feedRepository.findTop10ByNovelIdInOrderByFeedIdDesc(interestNovelIds); + + if (interestFeeds.isEmpty()) { + return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_ASSOCIATED_FEEDS"); + } + + Set avatarIds = interestFeeds.stream().map(feed -> feed.getUser().getAvatarProfileId()) + .collect(Collectors.toSet()); + Map avatarMap = avatarProfileRepository.findAllById(avatarIds).stream() + .collect(Collectors.toMap(AvatarProfile::getAvatarProfileId, avatar -> avatar)); + + List interestFeedGetResponses = interestFeeds.stream() + .filter(feed -> feed.isVisibleTo(user.getUserId())).map(feed -> { + Novel novel = novelMap.get(feed.getNovelId()); + AvatarProfile avatar = avatarMap.get(feed.getUser().getAvatarProfileId()); + return InterestFeedGetResponse.of(novel, feed.getUser(), feed, avatar); + }).toList(); + return InterestFeedsGetResponse.of(interestFeedGetResponses, ""); + } + + @Transactional(readOnly = true) + public NovelGetResponseFeedTab getFeedsByNovel(User user, Long novelId, Long lastFeedId, int size) { + Long userIdOrNull = Optional.ofNullable(user).map(User::getUserId).orElse(null); + Slice feeds = feedRepository.findFeedsByNovelId(novelId, lastFeedId, userIdOrNull, + PageRequest.of(DEFAULT_PAGE_NUMBER, size)); + + List feedGetResponses = feeds.getContent().stream().filter(feed -> feed.isVisibleTo(userIdOrNull)) + .map(feed -> createFeedInfo(feed, user)).toList(); + + return NovelGetResponseFeedTab.of(feeds.hasNext(), feedGetResponses); + } + + @Transactional(readOnly = true) + public UserFeedsGetResponse getUserFeeds(User visitor, Long ownerId, Long lastFeedId, int size, Boolean isVisible, + Boolean isUnVisible, List genreNames, SortCriteria sortCriteria) { + User owner = userRepository.findById(ownerId) + .orElseThrow(() -> new CustomUserException(USER_NOT_FOUND, "user with the given id was not found")); + Long visitorId = Optional.ofNullable(visitor).map(User::getUserId).orElse(null); + + if (owner.getIsProfilePublic() || isOwner(visitor, ownerId)) { + List genres = getGenres(genreNames); + + List visibleFeeds = feedRepository.findFeedsByNoOffsetPagination(owner, lastFeedId, size, isVisible, + isUnVisible, sortCriteria, genres, visitorId); + + List novelIds = visibleFeeds.stream().map(Feed::getNovelId).filter(Objects::nonNull) + .collect(Collectors.toList()); + Map novelMap = novelRepository.findAllById(novelIds).stream() + .collect(Collectors.toMap(Novel::getNovelId, novel -> novel)); + + List userFeedGetResponseList = visibleFeeds.stream() + .map(feed -> UserFeedGetResponse.of(feed, novelMap.get(feed.getNovelId()), visitorId, + getThumbnailUrl(feed), getImageCount(feed))).toList(); + + // TODO Slice의 hasNext()로 판단하도록 수정 + Boolean isLoadable = visibleFeeds.size() == size; + long feedsCount = feedRepository.countVisibleFeeds(owner, lastFeedId, isVisible, isUnVisible, genres, + visitorId); + + return UserFeedsGetResponse.of(isLoadable, feedsCount, userFeedGetResponseList); + } + + throw new CustomUserException(PRIVATE_PROFILE_STATUS, "the profile status of the user is set to private"); + } + + private static boolean isOwner(User visitor, Long ownerId) { + //TODO 현재는 비로그인 회원인 경우 + return visitor != null && visitor.getUserId().equals(ownerId); + } + + private List getGenres(List genreNames) { + if (genreNames != null && !genreNames.isEmpty()) { + return genreNames.stream() + .map(genreName -> genreRepository.findByGenreName(genreName) + .orElseThrow(() -> new CustomGenreException(GENRE_NOT_FOUND, + "genre with the given name is not found")) + ).toList(); + } + return null; + } + + private String getThumbnailUrl(Feed feed) { + Optional thumbnailImage = feedImageCustomRepository.findThumbnailFeedImageByFeedId(feed.getFeedId()); + return thumbnailImage.map(FeedImage::getUrl).orElse(null); + } + + private Integer getImageCount(Feed feed) { + return feedImageRepository.countByFeedId(feed.getFeedId()); + } + + private Category findCategoryByName(String categoryName) { + return categoryRepository.findByCategoryName(CategoryName.valueOf(categoryName)).orElseThrow( + () -> new CustomCategoryException(INVALID_CATEGORY_FORMAT, + "Category for the given feed was not found")); + } + + 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(); + fcmClient.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 = novelRepository.findById(feed.getNovelId()) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, + "novel with the given id is not found")); + return String.format("<%s>", novel.getTitle()); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/FeedServiceImpl.java b/src/main/java/org/websoso/WSSServer/feed/service/FeedServiceImpl.java new file mode 100644 index 000000000..2f0c6ca9e --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/FeedServiceImpl.java @@ -0,0 +1,99 @@ +package org.websoso.WSSServer.feed.service; + +import static org.websoso.WSSServer.exception.error.CustomCategoryError.INVALID_CATEGORY_FORMAT; +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; + +import java.util.List; +import java.util.Optional; +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.Genre; +import org.websoso.WSSServer.domain.common.CategoryName; +import org.websoso.WSSServer.domain.common.FeedGetOption; +import org.websoso.WSSServer.exception.exception.CustomCategoryException; +import org.websoso.WSSServer.exception.exception.CustomFeedException; +import org.websoso.WSSServer.feed.domain.Category; +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.CategoryRepository; +import org.websoso.WSSServer.feed.repository.FeedCategoryRepository; +import org.websoso.WSSServer.feed.repository.FeedImageCustomRepository; +import org.websoso.WSSServer.feed.repository.FeedImageRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.PopularFeedRepository; + +@Service +@RequiredArgsConstructor +public class FeedServiceImpl { + + private final FeedRepository feedRepository; + private final FeedCategoryRepository feedCategoryRepository; + private final CategoryRepository categoryRepository; + private final FeedImageRepository feedImageRepository; + private final FeedImageCustomRepository feedImageCustomRepository; + private final PopularFeedRepository popularFeedRepository; + + private static final String DEFAULT_CATEGORY = "all"; + + @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")); + } + + @Transactional(readOnly = true) + public Slice findFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, PageRequest pageRequest, + FeedGetOption feedGetOption, List genres) { + if (DEFAULT_CATEGORY.equals(category)) { + if (FeedGetOption.isAll(feedGetOption)) { + return feedRepository.findFeeds(lastFeedId, userId, pageRequest); + } else { + return feedRepository.findRecommendedFeeds(lastFeedId, userId, pageRequest, genres); + } + } else { + if (FeedGetOption.isAll(feedGetOption)) { + return feedCategoryRepository.findFeedsByCategory(findCategoryByName(category), lastFeedId, userId, + pageRequest); + } else { + return feedCategoryRepository.findRecommendedFeedsByCategoryLabel(findCategoryByName(category), + lastFeedId, userId, pageRequest, genres); + } + } + } + + // 이 부분 private으로 두는 게 맞을지 아니면 public으로 두는 게 나을지 고민 + private Category findCategoryByName(String categoryName) { + return categoryRepository.findByCategoryName(CategoryName.valueOf(categoryName)).orElseThrow( + () -> new CustomCategoryException(INVALID_CATEGORY_FORMAT, + "Category for the given feed was not found")); + } + + @Transactional(readOnly = true) + public Integer countByFeedId(Long feedId) { + return feedImageRepository.countByFeedId(feedId); + } + + @Transactional(readOnly = true) + public Optional findThumbnailFeedImageByFeedId(Long feedId) { + return feedImageCustomRepository.findThumbnailFeedImageByFeedId(feedId); + } + + @Transactional(readOnly = true) + public List findPopularFeedsWithUser(Long userId) { + return popularFeedRepository.findTodayPopularFeeds(userId); + } + + @Transactional(readOnly = true) + public List findPopularFeedsWithoutUser() { + return popularFeedRepository.findTop9ByOrderByPopularFeedIdDesc(); + } + + @Transactional(readOnly = true) + public List findInterestFeeds(List interestNovelIds) { + return feedRepository.findTop10ByNovelIdInOrderByFeedIdDesc(interestNovelIds); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/PopularFeedService.java b/src/main/java/org/websoso/WSSServer/feed/service/PopularFeedService.java new file mode 100644 index 000000000..abf3a54ea --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/PopularFeedService.java @@ -0,0 +1,47 @@ +package org.websoso.WSSServer.feed.service; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.dto.popularFeed.PopularFeedGetResponse; +import org.websoso.WSSServer.dto.popularFeed.PopularFeedsGetResponse; +import org.websoso.WSSServer.feed.domain.PopularFeed; +import org.websoso.WSSServer.feed.repository.PopularFeedRepository; + +@Service +@RequiredArgsConstructor +@Transactional +public class PopularFeedService { + + private final PopularFeedRepository popularFeedRepository; + + @Transactional(readOnly = true) + public PopularFeedsGetResponse getPopularFeeds(User user) { + Long currentUserId = Optional.ofNullable(user).map(User::getUserId).orElse(null); + + List popularFeeds = Optional.ofNullable(user).map(u -> findPopularFeedsWithUser(u.getUserId())) + .orElseGet(this::findPopularFeedsWithoutUser); + + List popularFeedGetResponses = mapToPopularFeedGetResponseList(popularFeeds, + currentUserId); + + return new PopularFeedsGetResponse(popularFeedGetResponses); + } + + private List findPopularFeedsWithUser(Long userId) { + return popularFeedRepository.findTodayPopularFeeds(userId); + } + + private List findPopularFeedsWithoutUser() { + return popularFeedRepository.findTop9ByOrderByPopularFeedIdDesc(); + } + + private static List mapToPopularFeedGetResponseList(List popularFeeds, + Long currentUserId) { + return popularFeeds.stream().filter(pf -> pf.getFeed().isVisibleTo(currentUserId)) + .map(PopularFeedGetResponse::of).toList(); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/ReportService.java b/src/main/java/org/websoso/WSSServer/feed/service/ReportService.java new file mode 100644 index 000000000..0d1701c1e --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/ReportService.java @@ -0,0 +1,115 @@ +package org.websoso.WSSServer.feed.service; + +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.CustomCommentError.COMMENT_NOT_FOUND; +import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_REPORTED_FEED; +import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; +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.user.domain.User; +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.domain.ReportedComment; +import org.websoso.WSSServer.feed.domain.ReportedFeed; +import org.websoso.WSSServer.feed.repository.CommentRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.ReportedCommentRepository; +import org.websoso.WSSServer.feed.repository.ReportedFeedRepository; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; +import org.websoso.WSSServer.service.MessageFormatter; + +@Service +@RequiredArgsConstructor +public class ReportService { + + private final ReportedCommentRepository reportedCommentRepository; + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final FeedRepository feedRepository; + private final ReportedFeedRepository reportedFeedRepository; + private final DiscordMessageClient discordMessageClient; + + @Transactional + public void reportComment(User user, Long feedId, Long commentId, ReportedType reportedType) { + Feed feed = getFeedOrException(feedId); + Comment comment = commentRepository.findById(commentId).orElseThrow( + () -> new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); + 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 (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)); + + int reportedCount = reportedCommentRepository.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)); + } + + private Feed getFeedOrException(Long feedId) { + return feedRepository.findById(feedId) + .orElseThrow(() -> new CustomFeedException(FEED_NOT_FOUND, "feed with the given id was not found")); + } + + @Transactional + public void reportFeed(User user, Long feedId, ReportedType reportedType) { + Feed feed = getFeedOrException(feedId); + + if (isUserFeedOwner(feed.getUser(), user)) { + throw new CustomFeedException(SELF_REPORT_NOT_ALLOWED, "cannot report own feed"); + } + + 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)); + + int reportedCount = reportedFeedRepository.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); + } +} diff --git a/src/main/java/org/websoso/WSSServer/feed/service/ReportServiceImpl.java b/src/main/java/org/websoso/WSSServer/feed/service/ReportServiceImpl.java new file mode 100644 index 000000000..0b30326f2 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/feed/service/ReportServiceImpl.java @@ -0,0 +1,51 @@ +package org.websoso.WSSServer.feed.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.common.ReportedType; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.feed.domain.ReportedComment; +import org.websoso.WSSServer.feed.domain.ReportedFeed; +import org.websoso.WSSServer.feed.repository.ReportedCommentRepository; +import org.websoso.WSSServer.feed.repository.ReportedFeedRepository; +import org.websoso.WSSServer.user.domain.User; + +@Service +@RequiredArgsConstructor +public class ReportServiceImpl { + + private final ReportedCommentRepository reportedCommentRepository; + private final ReportedFeedRepository reportedFeedRepository; + + @Transactional(readOnly = true) + public Boolean isExistsByCommentAndUserAndReportedType(Comment comment, User user, ReportedType reportedType) { + return reportedCommentRepository.existsByCommentAndUserAndReportedType(comment, user, reportedType); + } + + @Transactional + public void saveReportedComment(Comment comment, User user, ReportedType reportedType) { + reportedCommentRepository.save(ReportedComment.create(comment, user, reportedType)); + } + + @Transactional(readOnly = true) + public int countByCommentAndReportedType(Comment comment, ReportedType reportedType) { + return reportedCommentRepository.countByCommentAndReportedType(comment, reportedType); + } + + @Transactional(readOnly = true) + public Boolean isExistsByFeedAndUserAndReportedType(Feed feed, User user, ReportedType reportedType) { + return reportedFeedRepository.existsByFeedAndUserAndReportedType(feed, user, reportedType); + } + + @Transactional + public void saveReportedFeed(Feed feed, User user, ReportedType reportedType) { + reportedFeedRepository.save(ReportedFeed.create(feed, user, reportedType)); + } + + @Transactional(readOnly = true) + public int countByFeedAndReportedType(Feed feed, ReportedType reportedType) { + return reportedFeedRepository.countByFeedAndReportedType(feed, reportedType); + } +} diff --git a/src/main/java/org/websoso/WSSServer/domain/AttractivePoint.java b/src/main/java/org/websoso/WSSServer/library/domain/AttractivePoint.java similarity index 92% rename from src/main/java/org/websoso/WSSServer/domain/AttractivePoint.java rename to src/main/java/org/websoso/WSSServer/library/domain/AttractivePoint.java index 4a9f3f3f1..e8955ee4f 100644 --- a/src/main/java/org/websoso/WSSServer/domain/AttractivePoint.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/AttractivePoint.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/Keyword.java b/src/main/java/org/websoso/WSSServer/library/domain/Keyword.java similarity index 80% rename from src/main/java/org/websoso/WSSServer/domain/Keyword.java rename to src/main/java/org/websoso/WSSServer/library/domain/Keyword.java index 56ade2bd9..655a8d0d5 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Keyword.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/Keyword.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -10,6 +10,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import java.io.Serializable; +import java.util.Arrays; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -31,4 +32,8 @@ public class Keyword implements Serializable { @JoinColumn(name = "keyword_category_id", nullable = false) private KeywordCategory keywordCategory; + public boolean containsAllWords(String[] words) { + return Arrays.stream(words) + .allMatch(keywordName::contains); + } } diff --git a/src/main/java/org/websoso/WSSServer/domain/KeywordCategory.java b/src/main/java/org/websoso/WSSServer/library/domain/KeywordCategory.java similarity index 93% rename from src/main/java/org/websoso/WSSServer/domain/KeywordCategory.java rename to src/main/java/org/websoso/WSSServer/library/domain/KeywordCategory.java index 5f959e467..7646727a2 100644 --- a/src/main/java/org/websoso/WSSServer/domain/KeywordCategory.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/KeywordCategory.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/common/ParentTouchListener.java b/src/main/java/org/websoso/WSSServer/library/domain/ParentTouchListener.java similarity index 74% rename from src/main/java/org/websoso/WSSServer/domain/common/ParentTouchListener.java rename to src/main/java/org/websoso/WSSServer/library/domain/ParentTouchListener.java index 2b61568b8..fc21fec97 100644 --- a/src/main/java/org/websoso/WSSServer/domain/common/ParentTouchListener.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/ParentTouchListener.java @@ -1,10 +1,8 @@ -package org.websoso.WSSServer.domain.common; +package org.websoso.WSSServer.library.domain; import jakarta.persistence.PostPersist; import jakarta.persistence.PostRemove; import jakarta.persistence.PostUpdate; -import org.websoso.WSSServer.domain.UserNovelAttractivePoint; -import org.websoso.WSSServer.domain.UserNovelKeyword; public class ParentTouchListener { @PostPersist diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java b/src/main/java/org/websoso/WSSServer/library/domain/UserNovel.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/UserNovel.java rename to src/main/java/org/websoso/WSSServer/library/domain/UserNovel.java index c4dd6bad6..fca72eb23 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovel.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/UserNovel.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.GenerationType.IDENTITY; @@ -22,6 +22,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.novel.domain.Novel; import org.websoso.common.entity.BaseEntity; import org.websoso.WSSServer.domain.common.ReadStatus; diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovelAttractivePoint.java b/src/main/java/org/websoso/WSSServer/library/domain/UserNovelAttractivePoint.java similarity index 93% rename from src/main/java/org/websoso/WSSServer/domain/UserNovelAttractivePoint.java rename to src/main/java/org/websoso/WSSServer/library/domain/UserNovelAttractivePoint.java index a4dad2dd6..957109d33 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovelAttractivePoint.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/UserNovelAttractivePoint.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -13,7 +13,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.websoso.WSSServer.domain.common.ParentTouchListener; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/domain/UserNovelKeyword.java b/src/main/java/org/websoso/WSSServer/library/domain/UserNovelKeyword.java similarity index 92% rename from src/main/java/org/websoso/WSSServer/domain/UserNovelKeyword.java rename to src/main/java/org/websoso/WSSServer/library/domain/UserNovelKeyword.java index 0d47158e5..11d5687a0 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserNovelKeyword.java +++ b/src/main/java/org/websoso/WSSServer/library/domain/UserNovelKeyword.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.library.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -13,7 +13,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.websoso.WSSServer.domain.common.ParentTouchListener; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/repository/AttractivePointRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/AttractivePointRepository.java similarity index 74% rename from src/main/java/org/websoso/WSSServer/repository/AttractivePointRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/AttractivePointRepository.java index 43f9e8d1d..e7a7c1685 100644 --- a/src/main/java/org/websoso/WSSServer/repository/AttractivePointRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/AttractivePointRepository.java @@ -1,9 +1,9 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.AttractivePoint; +import org.websoso.WSSServer.library.domain.AttractivePoint; @Repository public interface AttractivePointRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/KeywordCategoryRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/KeywordCategoryRepository.java similarity index 74% rename from src/main/java/org/websoso/WSSServer/repository/KeywordCategoryRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/KeywordCategoryRepository.java index d26592ba6..859dd588c 100644 --- a/src/main/java/org/websoso/WSSServer/repository/KeywordCategoryRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/KeywordCategoryRepository.java @@ -1,9 +1,9 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.KeywordCategory; +import org.websoso.WSSServer.library.domain.KeywordCategory; @Repository public interface KeywordCategoryRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/KeywordRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/KeywordRepository.java similarity index 66% rename from src/main/java/org/websoso/WSSServer/repository/KeywordRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/KeywordRepository.java index a2217eae2..a1d8890ff 100644 --- a/src/main/java/org/websoso/WSSServer/repository/KeywordRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/KeywordRepository.java @@ -1,8 +1,8 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Keyword; +import org.websoso.WSSServer.library.domain.Keyword; @Repository public interface KeywordRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelAttractivePointRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelAttractivePointRepository.java similarity index 75% rename from src/main/java/org/websoso/WSSServer/repository/UserNovelAttractivePointRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/UserNovelAttractivePointRepository.java index d82824d0f..b480241c6 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelAttractivePointRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelAttractivePointRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; @@ -6,10 +6,10 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.AttractivePoint; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.UserNovelAttractivePoint; +import org.websoso.WSSServer.library.domain.AttractivePoint; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelAttractivePoint; @Repository public interface UserNovelAttractivePointRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepository.java similarity index 87% rename from src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepository.java index 2ab61d178..c1dc8b36d 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepository.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.time.LocalDateTime; import java.util.List; import org.springframework.data.domain.Pageable; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse; public interface UserNovelCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepositoryImpl.java similarity index 95% rename from src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepositoryImpl.java rename to src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepositoryImpl.java index 524c3ee0a..0347e2bab 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelCustomRepositoryImpl.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; -import static org.websoso.WSSServer.domain.QNovel.novel; -import static org.websoso.WSSServer.domain.QNovelGenre.novelGenre; -import static org.websoso.WSSServer.domain.QUserNovel.userNovel; import static org.websoso.WSSServer.domain.common.ReadStatus.QUIT; import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHED; import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHING; +import static org.websoso.WSSServer.library.domain.QUserNovel.userNovel; +import static org.websoso.WSSServer.novel.domain.QNovel.novel; +import static org.websoso.WSSServer.novel.domain.QNovelGenre.novelGenre; import com.querydsl.core.types.Order; import com.querydsl.core.types.OrderSpecifier; @@ -22,8 +22,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.domain.common.ReadStatus; import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse; diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelKeywordRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelKeywordRepository.java similarity index 74% rename from src/main/java/org/websoso/WSSServer/repository/UserNovelKeywordRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/UserNovelKeywordRepository.java index 6b6ec13f7..56bda1aad 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelKeywordRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelKeywordRepository.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.util.List; import java.util.Set; @@ -7,10 +7,10 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.UserNovelKeyword; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelKeyword; @Repository public interface UserNovelKeywordRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelRepository.java similarity index 83% rename from src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java rename to src/main/java/org/websoso/WSSServer/library/repository/UserNovelRepository.java index e0fdda963..1e367ec22 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/library/repository/UserNovelRepository.java @@ -1,13 +1,13 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.library.repository; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.WSSServer.domain.common.ReadStatus; @Repository diff --git a/src/main/java/org/websoso/WSSServer/library/service/AttractivePointService.java b/src/main/java/org/websoso/WSSServer/library/service/AttractivePointService.java new file mode 100644 index 000000000..0ea2a9a78 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/library/service/AttractivePointService.java @@ -0,0 +1,125 @@ +package org.websoso.WSSServer.library.service; + +import static org.websoso.WSSServer.exception.error.CustomAttractivePointError.INVALID_ATTRACTIVE_POINT; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.common.AttractivePointName; +import org.websoso.WSSServer.library.domain.AttractivePoint; +import org.websoso.WSSServer.exception.exception.CustomAttractivePointException; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelAttractivePoint; +import org.websoso.WSSServer.library.repository.AttractivePointRepository; +import org.websoso.WSSServer.library.repository.UserNovelAttractivePointRepository; +import org.websoso.WSSServer.novel.domain.Novel; + +@Service +@RequiredArgsConstructor +@Transactional +public class AttractivePointService { + + private static final int ATTRACTIVE_POINT_SIZE = 3; + + private final AttractivePointRepository attractivePointRepository; + private final UserNovelAttractivePointRepository userNovelAttractivePointRepository; + + @Transactional(readOnly = true) + public AttractivePoint getAttractivePointOrException(String attractivePoint) { + return attractivePointRepository.findByAttractivePointName(attractivePoint) + .orElseThrow(() -> new CustomAttractivePointException(INVALID_ATTRACTIVE_POINT, + "invalid attractive point provided in the request")); + } + + public AttractivePoint getAttractivePointByString(String request) { + String lowerCaseRequest = request.toLowerCase(); + return getAttractivePointOrException(lowerCaseRequest); + } + + public void createUserNovelAttractivePoint(UserNovel userNovel, AttractivePoint attractivePoint) { + userNovelAttractivePointRepository.save(UserNovelAttractivePoint.create(userNovel, attractivePoint)); + } + + public void createUserNovelAttractivePoints(UserNovel userNovel, List request) { + List attractivePoints = request.stream() + .map(this::getAttractivePointByString) + .map(attractivePoint -> UserNovelAttractivePoint.create(userNovel, attractivePoint)) + .toList(); + + userNovelAttractivePointRepository.saveAll(attractivePoints); + } + + public void deleteUserNovelAttractivePoints(List userNovelAttractivePoints) { + userNovelAttractivePointRepository.deleteAll(userNovelAttractivePoints); + } + + public List getAttractivePoints(Novel novel) { + Map attractivePointMap = makeAttractivePointMapExcludingZero(novel); + + if (attractivePointMap.isEmpty()) { + return Collections.emptyList(); + } + + return getTOP3AttractivePoints(attractivePointMap); + } + + private Map makeAttractivePointMapExcludingZero(Novel novel) { + Map attractivePointMap = new HashMap<>(); + + for (AttractivePointName point : AttractivePointName.values()) { + attractivePointMap.put(point.getLabel(), getAttractivePointCount(novel, point)); + } + + attractivePointMap.entrySet().removeIf(entry -> entry.getValue() == 0); + + return attractivePointMap; + } + + private List getTOP3AttractivePoints(Map attractivePointMap) { + Map> groupedByValue = groupAttractivePointByValue(attractivePointMap); + + List result = new ArrayList<>(); + List sortedKeys = new ArrayList<>(groupedByValue.keySet()); + sortedKeys.sort(Collections.reverseOrder()); + + Random random = new Random(); + + for (Integer key : sortedKeys) { + List items = groupedByValue.get(key); + if (result.size() + items.size() > ATTRACTIVE_POINT_SIZE) { + Collections.shuffle(items, random); + items = items.subList(0, ATTRACTIVE_POINT_SIZE - result.size()); + } + result.addAll(items); + if (result.size() >= ATTRACTIVE_POINT_SIZE) { + break; + } + } + + return result; + } + + private Map> groupAttractivePointByValue(Map attractivePointMap) { + Map> groupedByValue = new HashMap<>(); + + for (Map.Entry entry : attractivePointMap.entrySet()) { + groupedByValue + .computeIfAbsent(entry.getValue(), k -> new ArrayList<>()) + .add(entry.getKey()); + } + + return groupedByValue; + } + + public int getAttractivePointCount(Novel novel, AttractivePointName point) { + return userNovelAttractivePointRepository.countByUserNovel_NovelAndAttractivePoint_AttractivePointName( + novel, point.getLabel()); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/service/KeywordService.java b/src/main/java/org/websoso/WSSServer/library/service/KeywordService.java similarity index 54% rename from src/main/java/org/websoso/WSSServer/service/KeywordService.java rename to src/main/java/org/websoso/WSSServer/library/service/KeywordService.java index cade3f3d0..3c69c8534 100644 --- a/src/main/java/org/websoso/WSSServer/service/KeywordService.java +++ b/src/main/java/org/websoso/WSSServer/library/service/KeywordService.java @@ -1,32 +1,42 @@ -package org.websoso.WSSServer.service; +package org.websoso.WSSServer.library.service; import static org.websoso.WSSServer.exception.error.CustomKeywordCategoryError.KEYWORD_CATEGORY_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomKeywordError.KEYWORD_NOT_FOUND; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.KeywordCategory; +import org.websoso.WSSServer.dto.keyword.KeywordCountGetResponse; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.library.domain.KeywordCategory; import org.websoso.WSSServer.domain.common.KeywordCategoryName; import org.websoso.WSSServer.dto.keyword.CategoryGetResponse; import org.websoso.WSSServer.dto.keyword.KeywordByCategoryGetResponse; import org.websoso.WSSServer.dto.keyword.KeywordGetResponse; import org.websoso.WSSServer.exception.exception.CustomKeywordCategoryException; import org.websoso.WSSServer.exception.exception.CustomKeywordException; -import org.websoso.WSSServer.repository.KeywordCategoryRepository; -import org.websoso.WSSServer.repository.KeywordRepository; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelKeyword; +import org.websoso.WSSServer.library.repository.KeywordCategoryRepository; +import org.websoso.WSSServer.library.repository.KeywordRepository; +import org.websoso.WSSServer.library.repository.UserNovelKeywordRepository; +import org.websoso.WSSServer.novel.domain.Novel; @Service @RequiredArgsConstructor @Transactional public class KeywordService { + private static final int KEYWORD_SIZE = 5; + private final KeywordRepository keywordRepository; private final KeywordCategoryRepository keywordCategoryRepository; + private final UserNovelKeywordRepository userNovelKeywordRepository; @Transactional(readOnly = true) public Keyword getKeywordOrException(Integer keywordId) { @@ -41,10 +51,25 @@ public KeywordByCategoryGetResponse searchKeywordByCategory(String query) { List categories = Arrays.stream(KeywordCategoryName.values()) .map(category -> CategoryGetResponse.of(getKeywordCategory(category.getLabel()), sortByCategory(category, searchedKeywords))) - .collect(Collectors.toList()); + .toList(); return KeywordByCategoryGetResponse.of(categories); } + public void createNovelKeyword(UserNovel userNovel, Keyword keyword) { + userNovelKeywordRepository.save(UserNovelKeyword.create(userNovel, keyword)); + } + + public void createNovelKeywords(UserNovel userNovel, List request) { + for (Integer keywordId : request) { + Keyword keyword = getKeywordOrException(keywordId); + userNovelKeywordRepository.save(UserNovelKeyword.create(userNovel, keyword)); + } + } + + public void deleteUserNovelKeywords(List userNovelKeywords) { + userNovelKeywordRepository.deleteAll(userNovelKeywords); + } + private KeywordCategory getKeywordCategory(String keywordCategoryName) { return keywordCategoryRepository.findByKeywordCategoryName(keywordCategoryName).orElseThrow( () -> new CustomKeywordCategoryException(KEYWORD_CATEGORY_NOT_FOUND, @@ -56,7 +81,7 @@ private List sortByCategory(KeywordCategoryName keywordCateg return searchedKeyword.stream() .filter(keyword -> keyword.getKeywordCategory().getKeywordCategoryName() .equals(keywordCategoryName.getLabel())) - .map(KeywordGetResponse::of).collect(Collectors.toList()); + .map(KeywordGetResponse::of).toList(); } private List searchKeyword(String query) { @@ -65,16 +90,29 @@ private List searchKeyword(String query) { } String[] words = query.split(" "); return keywordRepository.findAll().stream() - .filter(keyword -> containsAllWords(keyword.getKeywordName(), words)).toList(); + .filter(keyword -> keyword.containsAllWords(words)).toList(); } - private boolean containsAllWords(String keywordName, String[] words) { - for (String word : words) { - if (!keywordName.contains(word)) { - return false; - } + public List getKeywordNameAndCount(Novel novel) { + List userNovelKeywords = getKeywords(novel); + + if (userNovelKeywords.isEmpty()) { + return Collections.emptyList(); } - return true; + + Map keywordFrequencyMap = userNovelKeywords.stream() + .collect(Collectors.groupingBy(UserNovelKeyword::getKeyword, Collectors.counting())); + + return keywordFrequencyMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(KEYWORD_SIZE) + .map(entry -> KeywordCountGetResponse.of(entry.getKey(), entry.getValue().intValue())) + .toList(); } + public List getKeywords(Novel novel) { + return userNovelKeywordRepository.findAllByUserNovel_Novel(novel); + } + + } diff --git a/src/main/java/org/websoso/WSSServer/library/service/LibraryService.java b/src/main/java/org/websoso/WSSServer/library/service/LibraryService.java new file mode 100644 index 000000000..aebeb9f08 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/library/service/LibraryService.java @@ -0,0 +1,96 @@ +package org.websoso.WSSServer.library.service; + +import static org.websoso.WSSServer.domain.common.ReadStatus.QUIT; +import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHED; +import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHING; +import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_NOT_FOUND; + +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.Genre; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.domain.common.ReadStatus; +import org.websoso.WSSServer.exception.exception.CustomUserNovelException; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.repository.UserNovelRepository; +import org.websoso.WSSServer.novel.domain.Novel; + +@Service +@RequiredArgsConstructor +public class LibraryService { + + private final UserNovelRepository userNovelRepository; + + // TODO: novelId로 불러옴 + @Transactional(readOnly = true) + public UserNovel getLibraryOrException(User user, Long novelId) { + return userNovelRepository.findByNovel_NovelIdAndUser(novelId, user) + .orElseThrow(() -> new CustomUserNovelException(USER_NOVEL_NOT_FOUND, + "user novel with the given user and novel is not found")); + } + + // TODO: Novel 객체로 불러옴 + // TODO: 사용자 객체가 Null이면 Null로 반환함 헷갈리수도? + @Transactional(readOnly = true) + public UserNovel getLibraryOrNull(User user, Novel novel) { + if (user == null) { + return null; + } + + return userNovelRepository.findByNovel_NovelIdAndUser(novel.getNovelId(), user).orElse(null); + } + + @Transactional + public UserNovel createLibrary(ReadStatus status, Float userNovelRating, LocalDate startDate, LocalDate endDate, + User user, Novel novel) { + return userNovelRepository.save(UserNovel.create( + status, + userNovelRating, + startDate, + endDate, + user, + novel)); + } + + public void delete(UserNovel library) { + userNovelRepository.delete(library); + } + + public int getRatingCount(Novel novel) { + return userNovelRepository.countByNovelAndUserNovelRatingNot(novel, 0.0f); + } + + public float getRatingSum(Novel novel) { + return userNovelRepository.sumUserNovelRatingByNovel(novel); + } + + public int getInterestCount(Novel novel) { + return userNovelRepository.countByNovelAndIsInterestTrue(novel); + } + + + public int getWatchingCount(Novel novel) { + return userNovelRepository.countByNovelAndStatus(novel, WATCHING); + } + + public int getWatchedCount(Novel novel) { + return userNovelRepository.countByNovelAndStatus(novel, WATCHED); + } + + public int getQuitCount(Novel novel) { + return userNovelRepository.countByNovelAndStatus(novel, QUIT); + } + + public List getTasteNovels(List preferGenres) { + return userNovelRepository.findTasteNovels(preferGenres); + } + + + public List getTodayPopularNovelIds(PageRequest pageRequest) { + return userNovelRepository.findTodayPopularNovelsId(pageRequest); + } +} diff --git a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java b/src/main/java/org/websoso/WSSServer/library/service/UserNovelService.java similarity index 54% rename from src/main/java/org/websoso/WSSServer/service/UserNovelService.java rename to src/main/java/org/websoso/WSSServer/library/service/UserNovelService.java index 804c2df53..e61216cc5 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserNovelService.java +++ b/src/main/java/org/websoso/WSSServer/library/service/UserNovelService.java @@ -1,39 +1,30 @@ -package org.websoso.WSSServer.service; +package org.websoso.WSSServer.library.service; import static org.websoso.WSSServer.domain.common.Gender.M; import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; -import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomUserError.PRIVATE_PROFILE_STATUS; -import static org.websoso.WSSServer.exception.error.CustomUserNovelError.NOT_EVALUATED; -import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_ALREADY_EXISTS; -import static org.websoso.WSSServer.exception.error.CustomUserNovelError.USER_NOVEL_NOT_FOUND; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.AttractivePoint; -import org.websoso.WSSServer.domain.Feed; +import org.websoso.WSSServer.library.domain.AttractivePoint; +import org.websoso.WSSServer.feed.domain.Feed; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.NovelGenre; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.UserNovelAttractivePoint; -import org.websoso.WSSServer.domain.UserNovelKeyword; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.NovelGenre; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.library.domain.UserNovel; +import org.websoso.WSSServer.library.domain.UserNovelAttractivePoint; +import org.websoso.WSSServer.library.domain.UserNovelKeyword; import org.websoso.WSSServer.domain.common.Gender; import org.websoso.WSSServer.domain.common.SortCriteria; -import org.websoso.WSSServer.dto.keyword.KeywordGetResponse; import org.websoso.WSSServer.dto.user.UserNovelCountGetResponse; import org.websoso.WSSServer.dto.userNovel.TasteKeywordGetResponse; import org.websoso.WSSServer.dto.userNovel.UserGenrePreferenceGetResponse; @@ -42,38 +33,25 @@ import org.websoso.WSSServer.dto.userNovel.UserNovelAndNovelGetResponseLegacy; import org.websoso.WSSServer.dto.userNovel.UserNovelAndNovelsGetResponse; import org.websoso.WSSServer.dto.userNovel.UserNovelAndNovelsGetResponseLegacy; -import org.websoso.WSSServer.dto.userNovel.UserNovelCreateRequest; -import org.websoso.WSSServer.dto.userNovel.UserNovelGetResponse; -import org.websoso.WSSServer.dto.userNovel.UserNovelUpdateRequest; import org.websoso.WSSServer.dto.userNovel.UserTasteAttractivePointPreferencesAndKeywordsGetResponse; import org.websoso.WSSServer.exception.exception.CustomGenreException; -import org.websoso.WSSServer.exception.exception.CustomNovelException; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.exception.exception.CustomUserNovelException; -import org.websoso.WSSServer.repository.FeedRepository; +import org.websoso.WSSServer.feed.repository.FeedRepository; import org.websoso.WSSServer.repository.GenreRepository; -import org.websoso.WSSServer.repository.NovelRepository; -import org.websoso.WSSServer.repository.UserNovelAttractivePointRepository; -import org.websoso.WSSServer.repository.UserNovelKeywordRepository; -import org.websoso.WSSServer.repository.UserNovelRepository; +import org.websoso.WSSServer.library.repository.UserNovelRepository; +import org.websoso.WSSServer.user.service.UserService; +// TODO: 남아있는 코드는 User Controller에서 사용중이라 일단 유지 @Service @RequiredArgsConstructor @Transactional public class UserNovelService { - private final NovelRepository novelRepository; private final UserNovelRepository userNovelRepository; - private final KeywordService keywordService; - private final UserNovelAttractivePointRepository userNovelAttractivePointRepository; - private final UserNovelKeywordRepository userNovelKeywordRepository; - private final AttractivePointService attractivePointService; private final UserService userService; private final GenreRepository genreRepository; private final FeedRepository feedRepository; - public static final String SORT_TYPE_OLDEST = "OLDEST"; - private static final List priorityGenreNamesOfMale = List.of( "fantasy", "modernFantasy", "wuxia", "drama", "mystery", "lightNovel", "romance", "romanceFantasy", "BL" ); @@ -81,195 +59,6 @@ public class UserNovelService { "romance", "romanceFantasy", "fantasy", "modernFantasy", "wuxia", "drama", "mystery", "lightNovel", "BL" ); - @Transactional(readOnly = true) - public UserNovel getUserNovelOrException(User user, Long novelId) { - return userNovelRepository.findByNovel_NovelIdAndUser(novelId, user) - .orElseThrow(() -> new CustomUserNovelException(USER_NOVEL_NOT_FOUND, - "user novel with the given user and novel is not found")); - } - - @Transactional(readOnly = true) - public UserNovel getUserNovelOrNull(User user, Novel novel) { - if (user == null) { - return null; - } - return userNovelRepository.findByNovelAndUser(novel, user).orElse(null); - } - - public void createEvaluation(User user, UserNovelCreateRequest request) { - Novel novel = novelRepository.findById(request.novelId()) - .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, "novel with the given id is not found")); - - try { - UserNovel userNovel = userNovelRepository.save(UserNovel.create( - request.status(), - request.userNovelRating(), - request.startDate(), - request.endDate(), - user, - novel)); - - createUserNovelAttractivePoints(userNovel, request.attractivePoints()); - createNovelKeywords(userNovel, request.keywordIds()); - } catch (DataIntegrityViolationException e) { - throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); - } - } - - public void updateEvaluation(User user, Long novelId, UserNovelUpdateRequest request) { - UserNovel userNovel = getUserNovelOrException(user, novelId); - updateUserNovel(userNovel, request); - updateAssociations(userNovel, request); - } - - private void updateUserNovel(UserNovel userNovel, UserNovelUpdateRequest request) { - userNovel.updateUserNovel(request.userNovelRating(), request.status(), request.startDate(), request.endDate()); - } - - private void updateAssociations(UserNovel userNovel, UserNovelUpdateRequest request) { - updateAttractivePoints(userNovel, request.attractivePoints()); - updateKeywords(userNovel, request.keywordIds()); - } - - private void updateAttractivePoints(UserNovel userNovel, List attractivePoints) { - Map currentPointMap = userNovel.getUserNovelAttractivePoints() - .stream() - .collect(Collectors.toMap(UserNovelAttractivePoint::getAttractivePoint, it -> it)); - - Set requestedPoints = attractivePoints.stream() - .map(attractivePointService::getAttractivePointByString) - .collect(Collectors.toSet()); - - addUserNovelAttractivePoints(userNovel, currentPointMap, requestedPoints); - deleteUserNovelAttractivePoints(userNovel, currentPointMap, requestedPoints); - } - - private void addUserNovelAttractivePoints(UserNovel userNovel, - Map currentPointMap, - Set requestedPoints) { - for (AttractivePoint requested : requestedPoints) { - if (!currentPointMap.containsKey(requested)) { - userNovelAttractivePointRepository.save(UserNovelAttractivePoint.create(userNovel, requested)); - } - } - } - - private void deleteUserNovelAttractivePoints(UserNovel userNovel, - Map currentPointMap, - Set requestedPoints) { - List toDelete = new ArrayList<>(); - for (Map.Entry entry : currentPointMap.entrySet()) { - if (!requestedPoints.contains(entry.getKey())) { - toDelete.add(entry.getValue()); - } - } - if (!toDelete.isEmpty()) { - userNovel.getUserNovelAttractivePoints().removeAll(toDelete); - userNovel.touch(); - } - } - - private void updateKeywords(UserNovel userNovel, List keywordIds) { - Map currentKeywordMap = userNovel.getUserNovelKeywords() - .stream() - .collect(Collectors.toMap(UserNovelKeyword::getKeyword, it -> it)); - - Set requestedKeywords = keywordIds.stream() - .map(keywordService::getKeywordOrException) - .collect(Collectors.toSet()); - - addUserNovelKeywords(userNovel, currentKeywordMap, requestedKeywords); - deleteUserNovelKeywords(userNovel, currentKeywordMap, requestedKeywords); - } - - private void addUserNovelKeywords(UserNovel userNovel, - Map currentKeywordMap, - Set requestedKeywords) { - for (Keyword requested : requestedKeywords) { - if (!currentKeywordMap.containsKey(requested)) { - userNovelKeywordRepository.save(UserNovelKeyword.create(userNovel, requested)); - } - } - } - - private void deleteUserNovelKeywords(UserNovel userNovel, - Map currentKeywordMap, - Set requestedKeywords) { - List toDelete = new ArrayList<>(); - for (Map.Entry entry : currentKeywordMap.entrySet()) { - if (!requestedKeywords.contains(entry.getKey())) { - toDelete.add(entry.getValue()); - } - } - if (!toDelete.isEmpty()) { - userNovel.getUserNovelKeywords().removeAll(toDelete); - userNovel.touch(); - } - } - - private void createUserNovelAttractivePoints(UserNovel userNovel, List request) { - for (String stringAttractivePoint : request) { - AttractivePoint attractivePoint = attractivePointService.getAttractivePointByString(stringAttractivePoint); - userNovelAttractivePointRepository.save(UserNovelAttractivePoint.create(userNovel, attractivePoint)); - } - } - - private void createNovelKeywords(UserNovel userNovel, List request) { - for (Integer keywordId : request) { - Keyword keyword = keywordService.getKeywordOrException(keywordId); - userNovelKeywordRepository.save(UserNovelKeyword.create(userNovel, keyword)); - } - } - - public void deleteEvaluation(User user, Long novelId) { - UserNovel userNovel = getUserNovelOrException(user, novelId); - - if (userNovel.getStatus() == null) { - throw new CustomUserNovelException(NOT_EVALUATED, "this novel has not been evaluated by the user"); - } - - if (userNovel.getIsInterest()) { - userNovel.deleteEvaluation(); - userNovelAttractivePointRepository.deleteAll(userNovel.getUserNovelAttractivePoints()); - userNovelKeywordRepository.deleteAll(userNovel.getUserNovelKeywords()); - } else { - userNovelRepository.delete(userNovel); - } - } - - public UserNovel createUserNovelByInterest(User user, Novel novel) { - if (getUserNovelOrNull(user, novel) != null) { - throw new CustomUserNovelException(USER_NOVEL_ALREADY_EXISTS, "this novel is already registered"); - } - return userNovelRepository.save(UserNovel.create(null, 0.0f, null, null, user, novel)); - } - - @Transactional(readOnly = true) - public UserNovelGetResponse getEvaluation(User user, Novel novel) { - UserNovel userNovel = getUserNovelOrNull(user, novel); - - if (userNovel == null) { - return UserNovelGetResponse.of(novel, null, Collections.emptyList(), Collections.emptyList()); - } - - List attractivePoints = getStringAttractivePoints(userNovel); - List keywords = getKeywordGetResponses(userNovel); - - return UserNovelGetResponse.of(novel, userNovel, attractivePoints, keywords); - } - - private List getStringAttractivePoints(UserNovel userNovel) { - return userNovel.getUserNovelAttractivePoints().stream() - .map(attractivePoint -> attractivePoint.getAttractivePoint().getAttractivePointName()) - .collect(Collectors.toList()); - } - - private List getKeywordGetResponses(UserNovel userNovel) { - return userNovel.getUserNovelKeywords().stream() - .map(userNovelKeyword -> KeywordGetResponse.of(userNovelKeyword.getKeyword())) - .collect(Collectors.toList()); - } - @Transactional(readOnly = true) public UserNovelCountGetResponse getUserNovelStatistics(Long ownerId) { return userNovelRepository.findUserNovelStatistics(ownerId); diff --git a/src/main/java/org/websoso/WSSServer/notification/FCMService.java b/src/main/java/org/websoso/WSSServer/notification/FCMClient.java similarity index 98% rename from src/main/java/org/websoso/WSSServer/notification/FCMService.java rename to src/main/java/org/websoso/WSSServer/notification/FCMClient.java index 2e2c5b2b6..1a8238d8b 100644 --- a/src/main/java/org/websoso/WSSServer/notification/FCMService.java +++ b/src/main/java/org/websoso/WSSServer/notification/FCMClient.java @@ -12,13 +12,13 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.websoso.WSSServer.notification.dto.FCMMessageRequest; @Slf4j -@Service +@Component @RequiredArgsConstructor -public class FCMService { +public class FCMClient { private final FirebaseMessaging firebaseMessaging; diff --git a/src/main/java/org/websoso/WSSServer/domain/Novel.java b/src/main/java/org/websoso/WSSServer/novel/domain/Novel.java similarity index 92% rename from src/main/java/org/websoso/WSSServer/domain/Novel.java rename to src/main/java/org/websoso/WSSServer/novel/domain/Novel.java index 8f6450cfd..d7a9ac7bf 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Novel.java +++ b/src/main/java/org/websoso/WSSServer/novel/domain/Novel.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.novel.domain; import static jakarta.persistence.GenerationType.IDENTITY; @@ -13,6 +13,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.library.domain.UserNovel; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/domain/NovelGenre.java b/src/main/java/org/websoso/WSSServer/novel/domain/NovelGenre.java similarity index 89% rename from src/main/java/org/websoso/WSSServer/domain/NovelGenre.java rename to src/main/java/org/websoso/WSSServer/novel/domain/NovelGenre.java index b0727f48a..b47de3b4a 100644 --- a/src/main/java/org/websoso/WSSServer/domain/NovelGenre.java +++ b/src/main/java/org/websoso/WSSServer/novel/domain/NovelGenre.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.novel.domain; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -10,6 +10,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.websoso.WSSServer.domain.Genre; @Entity @Getter diff --git a/src/main/java/org/websoso/WSSServer/domain/NovelPlatform.java b/src/main/java/org/websoso/WSSServer/novel/domain/NovelPlatform.java similarity index 95% rename from src/main/java/org/websoso/WSSServer/domain/NovelPlatform.java rename to src/main/java/org/websoso/WSSServer/novel/domain/NovelPlatform.java index 0234297b3..5ab3617ce 100644 --- a/src/main/java/org/websoso/WSSServer/domain/NovelPlatform.java +++ b/src/main/java/org/websoso/WSSServer/novel/domain/NovelPlatform.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.novel.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/Platform.java b/src/main/java/org/websoso/WSSServer/novel/domain/Platform.java similarity index 93% rename from src/main/java/org/websoso/WSSServer/domain/Platform.java rename to src/main/java/org/websoso/WSSServer/novel/domain/Platform.java index a04182973..f397a0eef 100644 --- a/src/main/java/org/websoso/WSSServer/domain/Platform.java +++ b/src/main/java/org/websoso/WSSServer/novel/domain/Platform.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.novel.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/PopularNovel.java b/src/main/java/org/websoso/WSSServer/novel/domain/PopularNovel.java similarity index 94% rename from src/main/java/org/websoso/WSSServer/domain/PopularNovel.java rename to src/main/java/org/websoso/WSSServer/novel/domain/PopularNovel.java index a15996a02..bbebea4e7 100644 --- a/src/main/java/org/websoso/WSSServer/domain/PopularNovel.java +++ b/src/main/java/org/websoso/WSSServer/novel/domain/PopularNovel.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.novel.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepository.java b/src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepository.java similarity index 75% rename from src/main/java/org/websoso/WSSServer/repository/NovelCustomRepository.java rename to src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepository.java index 962881dab..27f160016 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepository.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepository.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; public interface NovelCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java b/src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepositoryImpl.java similarity index 92% rename from src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java rename to src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepositoryImpl.java index 3ca263567..718465ac8 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelCustomRepositoryImpl.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/NovelCustomRepositoryImpl.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; -import static org.websoso.WSSServer.domain.QNovel.novel; -import static org.websoso.WSSServer.domain.QNovelGenre.novelGenre; -import static org.websoso.WSSServer.domain.QUserNovel.userNovel; import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHED; import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHING; +import static org.websoso.WSSServer.library.domain.QUserNovel.userNovel; +import static org.websoso.WSSServer.novel.domain.QNovel.novel; +import static org.websoso.WSSServer.novel.domain.QNovelGenre.novelGenre; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; @@ -23,9 +23,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.QNovel; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.QNovel; @Repository @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelGenreRepository.java b/src/main/java/org/websoso/WSSServer/novel/repository/NovelGenreRepository.java similarity index 65% rename from src/main/java/org/websoso/WSSServer/repository/NovelGenreRepository.java rename to src/main/java/org/websoso/WSSServer/novel/repository/NovelGenreRepository.java index e061b425f..cb02d40ce 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelGenreRepository.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/NovelGenreRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.NovelGenre; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.NovelGenre; @Repository public interface NovelGenreRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelPlatformRepository.java b/src/main/java/org/websoso/WSSServer/novel/repository/NovelPlatformRepository.java similarity index 65% rename from src/main/java/org/websoso/WSSServer/repository/NovelPlatformRepository.java rename to src/main/java/org/websoso/WSSServer/novel/repository/NovelPlatformRepository.java index facc2c5bc..234f60443 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelPlatformRepository.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/NovelPlatformRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.NovelPlatform; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.NovelPlatform; @Repository public interface NovelPlatformRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java b/src/main/java/org/websoso/WSSServer/novel/repository/NovelRepository.java similarity index 85% rename from src/main/java/org/websoso/WSSServer/repository/NovelRepository.java rename to src/main/java/org/websoso/WSSServer/novel/repository/NovelRepository.java index 02d235a19..a59c6c4e4 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/NovelRepository.java @@ -1,11 +1,11 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.Novel; +import org.websoso.WSSServer.novel.domain.Novel; @Repository public interface NovelRepository extends JpaRepository, NovelCustomRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/PopularNovelRepository.java b/src/main/java/org/websoso/WSSServer/novel/repository/PopularNovelRepository.java similarity index 67% rename from src/main/java/org/websoso/WSSServer/repository/PopularNovelRepository.java rename to src/main/java/org/websoso/WSSServer/novel/repository/PopularNovelRepository.java index 71549a37f..cc2c534f7 100644 --- a/src/main/java/org/websoso/WSSServer/repository/PopularNovelRepository.java +++ b/src/main/java/org/websoso/WSSServer/novel/repository/PopularNovelRepository.java @@ -1,8 +1,8 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.novel.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.PopularNovel; +import org.websoso.WSSServer.novel.domain.PopularNovel; @Repository public interface PopularNovelRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/service/GenreService.java b/src/main/java/org/websoso/WSSServer/novel/service/GenreServiceImpl.java similarity index 90% rename from src/main/java/org/websoso/WSSServer/service/GenreService.java rename to src/main/java/org/websoso/WSSServer/novel/service/GenreServiceImpl.java index eff1fa805..2d6fe2ddc 100644 --- a/src/main/java/org/websoso/WSSServer/service/GenreService.java +++ b/src/main/java/org/websoso/WSSServer/novel/service/GenreServiceImpl.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.service; +package org.websoso.WSSServer.novel.service; import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; @@ -11,8 +11,7 @@ @Service @RequiredArgsConstructor -@Transactional -public class GenreService { +public class GenreServiceImpl { private final GenreRepository genreRepository; @@ -22,5 +21,4 @@ public Genre getGenreOrException(String genreName) { .orElseThrow(() -> new CustomGenreException(GENRE_NOT_FOUND, "genre with the given name is not found")); } - } diff --git a/src/main/java/org/websoso/WSSServer/novel/service/KeywordServiceImpl.java b/src/main/java/org/websoso/WSSServer/novel/service/KeywordServiceImpl.java new file mode 100644 index 000000000..ba0af34ec --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/novel/service/KeywordServiceImpl.java @@ -0,0 +1,23 @@ +package org.websoso.WSSServer.novel.service; + +import static org.websoso.WSSServer.exception.error.CustomKeywordError.KEYWORD_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.exception.exception.CustomKeywordException; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.library.repository.KeywordRepository; + +@Service +@RequiredArgsConstructor +public class KeywordServiceImpl { + private KeywordRepository keywordRepository; + + @Transactional(readOnly = true) + public Keyword getKeywordOrException(Integer keywordId) { + return keywordRepository.findById(keywordId) + .orElseThrow(() -> new CustomKeywordException(KEYWORD_NOT_FOUND, + "keyword with the given id is not found")); + } +} diff --git a/src/main/java/org/websoso/WSSServer/novel/service/NovelServiceImpl.java b/src/main/java/org/websoso/WSSServer/novel/service/NovelServiceImpl.java new file mode 100644 index 000000000..2d57fbc57 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/novel/service/NovelServiceImpl.java @@ -0,0 +1,64 @@ +package org.websoso.WSSServer.novel.service; + +import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.websoso.WSSServer.domain.Genre; +import org.websoso.WSSServer.novel.domain.PopularNovel; +import org.websoso.WSSServer.dto.platform.PlatformGetResponse; +import org.websoso.WSSServer.exception.exception.CustomNovelException; +import org.websoso.WSSServer.library.domain.Keyword; +import org.websoso.WSSServer.novel.domain.Novel; +import org.websoso.WSSServer.novel.domain.NovelGenre; +import org.websoso.WSSServer.novel.repository.NovelGenreRepository; +import org.websoso.WSSServer.novel.repository.NovelPlatformRepository; +import org.websoso.WSSServer.novel.repository.NovelRepository; +import org.websoso.WSSServer.novel.repository.PopularNovelRepository; + +@Service +@RequiredArgsConstructor +public class NovelServiceImpl { + + private final NovelRepository novelRepository; + private final NovelGenreRepository novelGenreRepository; + private final NovelPlatformRepository novelPlatformRepository; + + @Transactional(readOnly = true) + public Novel getNovelOrException(Long novelId) { + return novelRepository.findById(novelId) + .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, + "novel with the given id is not found")); + } + + public Page searchNovels(PageRequest pageRequest, String searchQuery) { + return novelRepository.findSearchedNovels(pageRequest, searchQuery); + } + + public Page findFilteredNovels(PageRequest pageRequest, List genres, List keywords, + Boolean isCompleted, Float novelRating) { + return novelRepository.findFilteredNovels(pageRequest, genres, isCompleted, novelRating, + keywords); + } + + public List getGenresByNovel(Novel novel) { + return novelGenreRepository.findAllByNovel(novel); + } + + public List getPlatforms(Novel novel) { + return novelPlatformRepository.findAllByNovel(novel).stream() + .map(PlatformGetResponse::of) + .toList(); + } + + public List getSelectedPopularNovels(List selectedPopularNovelIds) { + return novelRepository.findAllById(selectedPopularNovelIds); + } + +} diff --git a/src/main/java/org/websoso/WSSServer/novel/service/PopularNovelService.java b/src/main/java/org/websoso/WSSServer/novel/service/PopularNovelService.java new file mode 100644 index 000000000..a39bfe6d1 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/novel/service/PopularNovelService.java @@ -0,0 +1,36 @@ +package org.websoso.WSSServer.novel.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.websoso.WSSServer.novel.domain.PopularNovel; +import org.websoso.WSSServer.novel.repository.PopularNovelRepository; + +@Service +@RequiredArgsConstructor +public class PopularNovelService { + + private final PopularNovelRepository popularNovelRepository; + + public void saveAll(List popularNovels) { + popularNovelRepository.saveAll(popularNovels); + } + + public List getPopularNovels() { + return popularNovelRepository.findAll(); + } + + public List getNovelIdsFromPopularNovel() { + return new ArrayList<>(popularNovelRepository.findAll() + .stream() + .map(PopularNovel::getNovelId) + .toList()); + } + + public void deleteAll(List popularNovels) { + popularNovelRepository.deleteAll(popularNovels); + } + + +} diff --git a/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java b/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java index 955fe8000..b55083735 100644 --- a/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java +++ b/src/main/java/org/websoso/WSSServer/oauth2/service/AppleService.java @@ -50,9 +50,9 @@ import org.springframework.web.client.RestClient; import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.domain.RefreshToken; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserAppleToken; +import org.websoso.WSSServer.user.domain.RefreshToken; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserAppleToken; import org.websoso.WSSServer.dto.auth.AppleLoginRequest; import org.websoso.WSSServer.dto.auth.ApplePublicKey; import org.websoso.WSSServer.dto.auth.ApplePublicKeys; @@ -60,9 +60,9 @@ import org.websoso.WSSServer.dto.auth.AuthResponse; import org.websoso.WSSServer.exception.exception.CustomAppleLoginException; import org.websoso.WSSServer.repository.RefreshTokenRepository; -import org.websoso.WSSServer.repository.UserAppleTokenRepository; -import org.websoso.WSSServer.repository.UserRepository; -import org.websoso.WSSServer.service.MessageService; +import org.websoso.WSSServer.user.repository.UserAppleTokenRepository; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; @Transactional @Service @@ -82,7 +82,7 @@ public class AppleService { private final UserRepository userRepository; private final UserAppleTokenRepository userAppleTokenRepository; private final JwtProvider jwtProvider; - private final MessageService messageService; + private final DiscordMessageClient discordMessageClient; @Value("${apple.public-keys-url}") private String applePublicKeysUrl; @@ -285,7 +285,8 @@ private AuthResponse authenticate(String socialId, String email, String nickname userAppleTokenRepository.save(UserAppleToken.create(user, appleRefreshToken)); } - CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, + null); String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); diff --git a/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java b/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java index eb46e9c5f..514d9574c 100644 --- a/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java +++ b/src/main/java/org/websoso/WSSServer/oauth2/service/KakaoService.java @@ -14,14 +14,14 @@ import org.springframework.web.client.RestClient; import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.domain.RefreshToken; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.RefreshToken; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.auth.AuthResponse; import org.websoso.WSSServer.exception.exception.CustomKakaoException; import org.websoso.WSSServer.oauth2.dto.KakaoUserInfo; import org.websoso.WSSServer.repository.RefreshTokenRepository; -import org.websoso.WSSServer.repository.UserRepository; -import org.websoso.WSSServer.service.MessageService; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; @Service @Transactional @@ -31,7 +31,7 @@ public class KakaoService { private final UserRepository userRepository; private final RefreshTokenRepository refreshTokenRepository; private final JwtProvider jwtProvider; - private final MessageService messageService; + private final DiscordMessageClient discordMessageClient; @Value("${kakao.user-info-url}") private String kakaoUserInfoUrl; @@ -70,7 +70,8 @@ public AuthResponse getUserInfoFromKakao(String kakaoAccessToken) { user = userRepository.save(User.createBySocial(socialId, defaultNickname, kakaoUserInfo.email())); } - CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); + CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, + null); String accessToken = jwtProvider.generateAccessToken(customAuthenticationToken); String refreshToken = jwtProvider.generateRefreshToken(customAuthenticationToken); diff --git a/src/main/java/org/websoso/WSSServer/repository/AvatarProfileRepository.java b/src/main/java/org/websoso/WSSServer/repository/AvatarProfileRepository.java new file mode 100644 index 000000000..9492fc5c8 --- /dev/null +++ b/src/main/java/org/websoso/WSSServer/repository/AvatarProfileRepository.java @@ -0,0 +1,12 @@ +package org.websoso.WSSServer.repository; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.websoso.WSSServer.domain.AvatarProfile; + +@Repository +public interface AvatarProfileRepository extends JpaRepository { + List findAllByAvatarProfileIdNot(Long avatarProfileId); + +} diff --git a/src/main/java/org/websoso/WSSServer/repository/GenrePreferenceRepository.java b/src/main/java/org/websoso/WSSServer/repository/GenrePreferenceRepository.java index c7b5fa0de..22cfd5ef7 100644 --- a/src/main/java/org/websoso/WSSServer/repository/GenrePreferenceRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/GenrePreferenceRepository.java @@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.GenrePreference; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; @Repository public interface GenrePreferenceRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/NotificationRepository.java b/src/main/java/org/websoso/WSSServer/repository/NotificationRepository.java index 9f6824788..5329b82ed 100644 --- a/src/main/java/org/websoso/WSSServer/repository/NotificationRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/NotificationRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.Notification; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; @Repository public interface NotificationRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/ReadNotificationRepository.java b/src/main/java/org/websoso/WSSServer/repository/ReadNotificationRepository.java index 6468120c2..bc2ce378c 100644 --- a/src/main/java/org/websoso/WSSServer/repository/ReadNotificationRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/ReadNotificationRepository.java @@ -5,7 +5,7 @@ import org.springframework.stereotype.Repository; import org.websoso.WSSServer.domain.Notification; import org.websoso.WSSServer.domain.ReadNotification; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; @Repository public interface ReadNotificationRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/RefreshTokenRepository.java b/src/main/java/org/websoso/WSSServer/repository/RefreshTokenRepository.java index 01b884d33..af41ef77c 100644 --- a/src/main/java/org/websoso/WSSServer/repository/RefreshTokenRepository.java +++ b/src/main/java/org/websoso/WSSServer/repository/RefreshTokenRepository.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.repository.CrudRepository; -import org.websoso.WSSServer.domain.RefreshToken; +import org.websoso.WSSServer.user.domain.RefreshToken; public interface RefreshTokenRepository extends CrudRepository { diff --git a/src/main/java/org/websoso/WSSServer/service/AttractivePointService.java b/src/main/java/org/websoso/WSSServer/service/AttractivePointService.java deleted file mode 100644 index 502e9d61c..000000000 --- a/src/main/java/org/websoso/WSSServer/service/AttractivePointService.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.exception.error.CustomAttractivePointError.INVALID_ATTRACTIVE_POINT; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.AttractivePoint; -import org.websoso.WSSServer.exception.exception.CustomAttractivePointException; -import org.websoso.WSSServer.repository.AttractivePointRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class AttractivePointService { - - private final AttractivePointRepository attractivePointRepository; - - @Transactional(readOnly = true) - public AttractivePoint getAttractivePointOrException(String attractivePoint) { - return attractivePointRepository.findByAttractivePointName(attractivePoint) - .orElseThrow(() -> new CustomAttractivePointException(INVALID_ATTRACTIVE_POINT, - "invalid attractive point provided in the request")); - } - - public AttractivePoint getAttractivePointByString(String request) { - String lowerCaseRequest = request.toLowerCase(); - return getAttractivePointOrException(lowerCaseRequest); - } -} diff --git a/src/main/java/org/websoso/WSSServer/service/AvatarService.java b/src/main/java/org/websoso/WSSServer/service/AvatarService.java index f7b4d2d25..e0b80d648 100644 --- a/src/main/java/org/websoso/WSSServer/service/AvatarService.java +++ b/src/main/java/org/websoso/WSSServer/service/AvatarService.java @@ -9,7 +9,12 @@ import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Avatar; import org.websoso.WSSServer.domain.AvatarLine; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.domain.AvatarProfile; +import org.websoso.WSSServer.domain.AvatarProfileLine; +import org.websoso.WSSServer.dto.avatar.AvatarProfileGetResponse; +import org.websoso.WSSServer.dto.avatar.AvatarProfilesGetResponse; +import org.websoso.WSSServer.repository.AvatarProfileRepository; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.avatar.AvatarGetResponse; import org.websoso.WSSServer.dto.avatar.AvatarsGetResponse; import org.websoso.WSSServer.exception.exception.CustomAvatarException; @@ -21,6 +26,10 @@ public class AvatarService { private final AvatarRepository avatarRepository; + private final AvatarProfileRepository avatarProfileRepository; + + private static final long ADMIN_AVATAR_PROFILE_ID = -1L; + private static final Random random = new Random(); //TODO thread-safe하지 않아서 multi-thread 환경에서는 사용X @Transactional(readOnly = true) @@ -29,6 +38,13 @@ public Avatar getAvatarOrException(Byte avatarId) { new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")); } + @Transactional(readOnly = true) + public AvatarProfile getAvatarProfileOrException(Long avatarProfileId) { + return avatarProfileRepository.findById(avatarProfileId).orElseThrow(() -> + new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")); + } + + @Transactional(readOnly = true) public AvatarsGetResponse getAvatarList(User user) { Byte representativeAvatarId = user.getAvatarId(); @@ -42,9 +58,31 @@ public AvatarsGetResponse getAvatarList(User user) { return new AvatarsGetResponse(avatarGetResponses); } + public AvatarProfilesGetResponse getAvatarProfileList(User user) { + long representativeAvatarProfileId = user.getAvatarProfileId(); + + List avatarProfiles = avatarProfileRepository.findAllByAvatarProfileIdNot( + ADMIN_AVATAR_PROFILE_ID); + + List avatarProfileGetResponses = avatarProfiles.stream() + .map(avatarProfile -> { + List avatarProfileLines = avatarProfile.getAvatarLines(); + return AvatarProfileGetResponse.of(avatarProfile, getRandomAvatarProfileLine(avatarProfileLines), + representativeAvatarProfileId); + }).toList(); + + return new AvatarProfilesGetResponse(avatarProfileGetResponses); + + } + private static AvatarLine getRandomAvatarLine(List avatarLines) { final int avatarLineSize = avatarLines.size(); return avatarLines.get(random.nextInt(avatarLineSize)); } + private static AvatarProfileLine getRandomAvatarProfileLine(List avatarLines) { + final int avatarLineSize = avatarLines.size(); + return avatarLines.get(random.nextInt(avatarLineSize)); + } + } diff --git a/src/main/java/org/websoso/WSSServer/service/BlockService.java b/src/main/java/org/websoso/WSSServer/service/BlockService.java index 9413ab05d..b44892012 100644 --- a/src/main/java/org/websoso/WSSServer/service/BlockService.java +++ b/src/main/java/org/websoso/WSSServer/service/BlockService.java @@ -10,12 +10,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; import org.websoso.WSSServer.domain.Block; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.dto.block.BlockGetResponse; import org.websoso.WSSServer.dto.block.BlocksGetResponse; import org.websoso.WSSServer.exception.exception.CustomBlockException; import org.websoso.WSSServer.repository.BlockRepository; +import org.websoso.WSSServer.user.service.UserService; @Service @RequiredArgsConstructor @@ -50,7 +52,7 @@ public BlocksGetResponse getBlockList(User user) { List blockGetResponses = blocks.stream() .map(block -> { User blockedUser = userService.getUserOrException(block.getBlockedId()); - Avatar avatarOfBlockedUser = avatarService.getAvatarOrException(blockedUser.getAvatarId()); + AvatarProfile avatarOfBlockedUser = avatarService.getAvatarProfileOrException(blockedUser.getAvatarProfileId()); return BlockGetResponse.of(block, blockedUser, avatarOfBlockedUser); }).toList(); return new BlocksGetResponse(blockGetResponses); diff --git a/src/main/java/org/websoso/WSSServer/service/CategoryService.java b/src/main/java/org/websoso/WSSServer/service/CategoryService.java deleted file mode 100644 index 21eeb12e8..000000000 --- a/src/main/java/org/websoso/WSSServer/service/CategoryService.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.exception.error.CustomCategoryError.INVALID_CATEGORY_FORMAT; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Category; -import org.websoso.WSSServer.domain.common.CategoryName; -import org.websoso.WSSServer.exception.exception.CustomCategoryException; -import org.websoso.WSSServer.repository.CategoryRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class CategoryService { - - private final CategoryRepository categoryRepository; - - @Transactional(readOnly = true) - public Category getCategory(String categoryName) { - return categoryRepository.findByCategoryName(CategoryName.valueOf(categoryName)) - .orElseThrow(() -> new CustomCategoryException(INVALID_CATEGORY_FORMAT, - "Category for the given feed was not found")); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/service/CommentService.java b/src/main/java/org/websoso/WSSServer/service/CommentService.java deleted file mode 100644 index 25a7df116..000000000 --- a/src/main/java/org/websoso/WSSServer/service/CommentService.java +++ /dev/null @@ -1,265 +0,0 @@ -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; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -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; - - 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) { - Comment comment = getCommentOrException(commentId); - comment.validateFeedAssociation(feed); - comment.validateUserAuthorization(userId, UPDATE); - comment.updateContent(commentContent); - } - - public void deleteComment(Long userId, Feed feed, Long commentId) { - Comment comment = getCommentOrException(commentId); - comment.validateFeedAssociation(feed); - comment.validateUserAuthorization(userId, DELETE); - commentRepository.delete(comment); - } - - @Transactional(readOnly = true) - public CommentsGetResponse getComments(User user, Feed feed) { - List responses = feed.getComments() - .stream() - .map(comment -> new AbstractMap.SimpleEntry<>( - comment, userService.getUserOrException(comment.getUserId()))) - .map(entry -> CommentGetResponse.of( - getUserBasicInfo(entry.getValue()), - entry.getKey(), - isUserCommentOwner(entry.getValue(), user), - entry.getKey().getIsSpoiler(), - isBlocked(user, entry.getValue()), - entry.getKey().getIsHidden())) - .toList(); - - 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) { - return commentRepository.findById(commentId).orElseThrow(() -> - new CustomCommentException(COMMENT_NOT_FOUND, "comment with the given id was not found")); - } - - private UserBasicInfo getUserBasicInfo(User user) { - return user.getUserBasicInfo( - avatarService.getAvatarOrException(user.getAvatarId()).getAvatarImage() - ); - } - - private Boolean isUserCommentOwner(User createdUser, User user) { - return createdUser.equals(user); - } - - private Boolean isBlocked(User user, User createdFeedUser) { - return blockService.isBlocked(user.getUserId(), createdFeedUser.getUserId()); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/service/MessageService.java b/src/main/java/org/websoso/WSSServer/service/DiscordMessageClient.java similarity index 98% rename from src/main/java/org/websoso/WSSServer/service/MessageService.java rename to src/main/java/org/websoso/WSSServer/service/DiscordMessageClient.java index dc5f035cd..05c4ef3a1 100644 --- a/src/main/java/org/websoso/WSSServer/service/MessageService.java +++ b/src/main/java/org/websoso/WSSServer/service/DiscordMessageClient.java @@ -17,7 +17,7 @@ @Service @Slf4j -public class MessageService { +public class DiscordMessageClient { @Value("${logging.discord.report-webhook-url}") private String discordReportWebhookUrl; diff --git a/src/main/java/org/websoso/WSSServer/service/FeedCategoryService.java b/src/main/java/org/websoso/WSSServer/service/FeedCategoryService.java deleted file mode 100644 index dddc25b54..000000000 --- a/src/main/java/org/websoso/WSSServer/service/FeedCategoryService.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.exception.error.CustomCategoryError.CATEGORY_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.Category; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedCategory; -import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.exception.exception.CustomCategoryException; -import org.websoso.WSSServer.repository.FeedCategoryRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class FeedCategoryService { - - private final FeedCategoryRepository feedcategoryRepository; - private final CategoryService categoryservice; - - public void createFeedCategory(Feed feed, List relevantCategories) { - for (String relevantCategory : relevantCategories) { - feedcategoryRepository.save(FeedCategory.create(feed, categoryservice.getCategory(relevantCategory))); - } - } - - public void updateFeedCategory(Feed feed, List relevantCategories) { - List feedCategories = feedcategoryRepository.findByFeed(feed); - - if (feedCategories.isEmpty()) { - throw new CustomCategoryException(CATEGORY_NOT_FOUND, "Category for the given feed was not found"); - } - - Set categories = feedCategories - .stream() - .map(FeedCategory::getCategory).collect(Collectors.toSet()); - - Set newCategories = relevantCategories.stream().map(categoryservice::getCategory) - .collect(Collectors.toSet()); - - for (Category newCategory : newCategories) { - if (categories.contains(newCategory)) { - categories.remove(newCategory); - } else { - feedcategoryRepository.save(FeedCategory.create(feed, newCategory)); - } - } - - for (Category category : categories) { - feedcategoryRepository.deleteByCategoryAndFeed(category, feed); - } - } - - @Transactional(readOnly = true) - public List getRelevantCategoryNames(List feedCategories) { - return feedCategories.stream() - .map(feedCategory -> feedCategory.getCategory().getCategoryName().getLabel()) - .collect(Collectors.toList()); - } - - @Transactional(readOnly = true) - public Slice getFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, - PageRequest pageRequest) { - return feedcategoryRepository.findFeedsByCategory(categoryservice.getCategory(category), lastFeedId, - userId, pageRequest); - } - - @Transactional(readOnly = true) - public Slice getRecommendedFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, - PageRequest pageRequest, List genres) { - return feedcategoryRepository.findRecommendedFeedsByCategoryLabel(categoryservice.getCategory(category), - lastFeedId, userId, pageRequest, genres); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/service/FeedService.java b/src/main/java/org/websoso/WSSServer/service/FeedService.java deleted file mode 100644 index 1df5c6817..000000000 --- a/src/main/java/org/websoso/WSSServer/service/FeedService.java +++ /dev/null @@ -1,524 +0,0 @@ -package org.websoso.WSSServer.service; - -import static java.lang.Boolean.TRUE; -import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.REPORT; -import static org.websoso.WSSServer.exception.error.CustomFeedError.FEED_NOT_FOUND; -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; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -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.springframework.web.multipart.MultipartFile; -import org.websoso.WSSServer.domain.Avatar; -import org.websoso.WSSServer.domain.Comment; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.FeedImage; -import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.GenrePreference; -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.FeedGetOption; -import org.websoso.WSSServer.domain.common.ReportedType; -import org.websoso.WSSServer.domain.common.SortCriteria; -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.*; -import org.websoso.WSSServer.dto.novel.NovelGetResponseFeedTab; -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.CommentRepository; -import org.websoso.WSSServer.repository.FeedImageCustomRepository; -import org.websoso.WSSServer.repository.FeedImageRepository; -import org.websoso.WSSServer.repository.FeedRepository; -import org.websoso.WSSServer.repository.GenrePreferenceRepository; -import org.websoso.WSSServer.repository.NotificationRepository; -import org.websoso.WSSServer.repository.NotificationTypeRepository; -import org.websoso.WSSServer.repository.NovelRepository; -import org.websoso.WSSServer.repository.ReportedCommentRepository; -import org.websoso.WSSServer.repository.UserNovelRepository; - -@Service -@RequiredArgsConstructor -@Transactional -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 ImageService imageService; - private final FeedImageCustomRepository feedImageCustomRepository; - 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; - private final ApplicationEventPublisher eventPublisher; - private final FeedImageRepository feedImageRepository; - private final GenreService genreService; - private final GenrePreferenceRepository genrePreferenceRepository; - private final CommentRepository commentRepository; - private final ReportedCommentRepository reportedCommentRepository; - - public FeedCreateResponse createFeed(User user, FeedCreateRequest request, FeedImageCreateRequest imagesRequest) { - List feedImages = processFeedImages(imagesRequest.images()); - - Optional.ofNullable(request.novelId()) - .ifPresent(novelService::getNovelOrException); - Feed feed = Feed.create( - request.feedContent(), - request.novelId(), - request.isSpoiler(), - request.isPublic(), - user, - feedImages); - feedRepository.save(feed); - feedCategoryService.createFeedCategory(feed, request.relevantCategories()); - - return FeedCreateResponse.of(feedImages); - } - - public FeedCreateResponse updateFeed(Long feedId, FeedUpdateRequest request, FeedImageUpdateRequest imagesRequest) { - Feed feed = getFeedOrException(feedId); - - List oldImages = new ArrayList<>(feed.getImages()); - - if (request.novelId() != null && feed.isNovelChanged(request.novelId())) { - novelService.getNovelOrException(request.novelId()); - } - - List feedImages = processFeedImages(imagesRequest.images()); - - feed.updateFeed( - request.feedContent(), - request.isSpoiler(), - request.isPublic(), - request.novelId(), - feedImages); - feedCategoryService.updateFeedCategory(feed, request.relevantCategories()); - - List oldImageUrls = oldImages.stream() - .map(FeedImage::getUrl) - .toList(); - eventPublisher.publishEvent(new FeedImageDeleteEvent(oldImageUrls)); - - return FeedCreateResponse.of(feedImages); - } - - private List processFeedImages(List images) { - List uploadedImageUrls = new ArrayList<>(); - - if (images != null && !images.isEmpty()) { - try { - for (MultipartFile image : images) { - String imageUrl = imageService.uploadFeedImage(image); - uploadedImageUrls.add(imageUrl); - } - } catch (Exception e) { - if (!uploadedImageUrls.isEmpty()) { - imageService.deleteImages(uploadedImageUrls); - } - - throw e; - } - } - - List feedImages = new ArrayList<>(); - if (!uploadedImageUrls.isEmpty()) { - feedImages.add(FeedImage.createThumbnail(uploadedImageUrls.get(0))); - for (int i = 1; i < uploadedImageUrls.size(); i++) { - feedImages.add(FeedImage.createCommon(uploadedImageUrls.get(i), i)); - } - } - - return feedImages; - } - - public void deleteFeed(Long feedId) { - List commentIds = commentRepository.findAllByFeedId(feedId).stream() - .map(Comment::getCommentId).toList(); - reportedCommentRepository.deleteByCommentIdsIn(commentIds); - feedRepository.deleteById(feedId); - } - - public void likeFeed(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - - 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); - likeService.deleteLike(user, feed); - } - - @Transactional(readOnly = true) - public FeedGetResponse getFeedById(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - UserBasicInfo feedUserBasicInfo = getUserBasicInfo(feed.getUser()); - Novel novel = getLinkedNovelOrNull(feed.getNovelId()); - Boolean isLiked = isUserLikedFeed(user, feed); - List relevantCategories = feedCategoryService.getRelevantCategoryNames(feed.getFeedCategories()); - Boolean isMyFeed = isUserFeedOwner(feed.getUser(), user); - - return FeedGetResponse.of(feed, feedUserBasicInfo, novel, isLiked, relevantCategories, isMyFeed); - } - - @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 genres = getPreferenceGenres(user); - - Slice feeds = findFeedsByCategoryLabel(getChosenCategoryOrDefault(category), - lastFeedId, userIdOrNull, PageRequest.of(DEFAULT_PAGE_NUMBER, size), feedGetOption, genres); - - List 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 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); - } - - public void createComment(User user, Long feedId, CommentCreateRequest request) { - Feed feed = getFeedOrException(feedId); - commentService.createComment(user, feed, request.commentContent()); - } - - public void updateComment(User user, Long feedId, Long commentId, CommentUpdateRequest request) { - Feed feed = getFeedOrException(feedId); - commentService.updateComment(user.getUserId(), feed, commentId, request.commentContent()); - } - - public void deleteComment(User user, Long feedId, Long commentId) { - Feed feed = getFeedOrException(feedId); - commentService.deleteComment(user.getUserId(), feed, commentId); - } - - @Transactional(readOnly = true) - public CommentsGetResponse getComments(User user, Long feedId) { - Feed feed = getFeedOrException(feedId); - return commentService.getComments(user, feed); - } - - public void reportFeed(User user, Long feedId, ReportedType reportedType) { - Feed feed = getFeedOrException(feedId); - - 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(); - } - - messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( - MessageFormatter.formatFeedReportMessage(user, feed, reportedType, reportedCount, shouldHide), REPORT)); - } - - public void reportComment(User user, Long feedId, Long commentId, ReportedType reportedType) { - Feed feed = getFeedOrException(feedId); - 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")); - } - - private UserBasicInfo getUserBasicInfo(User user) { - return user.getUserBasicInfo( - avatarService.getAvatarOrException(user.getAvatarId()).getAvatarImage() - ); - } - - private Novel getLinkedNovelOrNull(Long linkedNovelId) { - if (linkedNovelId == null) { - return null; - } - return novelService.getNovelOrException(linkedNovelId); - } - - private Boolean isUserLikedFeed(User user, Feed feed) { - return likeService.isUserLikedFeed(user, feed); - } - - private Boolean isUserFeedOwner(User createdUser, User user) { - return createdUser.equals(user); - } - - 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 relevantCategories = feedCategoryService.getRelevantCategoryNames(feed.getFeedCategories()); - Boolean isMyFeed = user != null && isUserFeedOwner(feed.getUser(), user); - Integer imageCount = feedImageRepository.countByFeedId(feed.getFeedId()); - Optional thumbnailImage = feedImageCustomRepository.findThumbnailFeedImageByFeedId(feed.getFeedId()); - String thumbnailUrl = thumbnailImage.map(FeedImage::getUrl).orElse(null); - - return FeedInfo.of(feed, userBasicInfo, novel, isLiked, relevantCategories, isMyFeed, thumbnailUrl, imageCount, - user); - } - - private Slice findFeedsByCategoryLabel(String category, Long lastFeedId, Long userId, - PageRequest pageRequest, FeedGetOption feedGetOption, - List genres) { - if (DEFAULT_CATEGORY.equals(category)) { - if (FeedGetOption.isAll(feedGetOption)) { - return feedRepository.findFeeds(lastFeedId, userId, pageRequest); - } else { - return feedRepository.findRecommendedFeeds(lastFeedId, userId, pageRequest, genres); - } - } else { - if (FeedGetOption.isAll(feedGetOption)) { - return feedCategoryService.getFeedsByCategoryLabel(category, lastFeedId, userId, pageRequest); - } else { - return feedCategoryService.getRecommendedFeedsByCategoryLabel(category, lastFeedId, userId, pageRequest, - genres); - } - } - } - - public InterestFeedsGetResponse getInterestFeeds(User user) { - List interestNovels = userNovelRepository.findByUserAndIsInterestTrue(user) - .stream() - .map(UserNovel::getNovel) - .toList(); - - if (interestNovels.isEmpty()) { - return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_INTEREST_NOVELS"); - } - - Map novelMap = interestNovels - .stream() - .collect(Collectors.toMap(Novel::getNovelId, novel -> novel)); - List interestNovelIds = new ArrayList<>(novelMap.keySet()); - - List interestFeeds = feedRepository.findTop10ByNovelIdInOrderByFeedIdDesc(interestNovelIds); - - if (interestFeeds.isEmpty()) { - return InterestFeedsGetResponse.of(Collections.emptyList(), "NO_ASSOCIATED_FEEDS"); - } - - Set avatarIds = interestFeeds.stream() - .map(feed -> feed.getUser().getAvatarId()) - .collect(Collectors.toSet()); - Map avatarMap = avatarRepository.findAllById(avatarIds) - .stream() - .collect(Collectors.toMap(Avatar::getAvatarId, avatar -> avatar)); - - List 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, ""); - } - - public NovelGetResponseFeedTab getFeedsByNovel(User user, Long novelId, Long lastFeedId, int size) { - Long userIdOrNull = Optional.ofNullable(user) - .map(User::getUserId) - .orElse(null); - Slice feeds = feedRepository.findFeedsByNovelId(novelId, lastFeedId, userIdOrNull, - PageRequest.of(DEFAULT_PAGE_NUMBER, size)); - - List feedGetResponses = feeds.getContent() - .stream() - .filter(feed -> feed.isVisibleTo(userIdOrNull)) - .map(feed -> createFeedInfo(feed, user)) - .toList(); - - return NovelGetResponseFeedTab.of(feeds.hasNext(), feedGetResponses); - } - - @Transactional(readOnly = true) - public UserFeedsGetResponse getUserFeeds(User visitor, Long ownerId, Long lastFeedId, int size, Boolean isVisible, - Boolean isUnVisible, List genreNames, - SortCriteria sortCriteria) { - User owner = userService.getUserOrException(ownerId); - Long visitorId = Optional.ofNullable(visitor) - .map(User::getUserId) - .orElse(null); - - if (owner.getIsProfilePublic() || isOwner(visitor, ownerId)) { - List genres = getGenres(genreNames); - - List visibleFeeds = feedRepository.findFeedsByNoOffsetPagination(owner, lastFeedId, size, isVisible, - isUnVisible, sortCriteria, genres, visitorId); - - List novelIds = visibleFeeds.stream() - .map(Feed::getNovelId) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - Map novelMap = novelRepository.findAllById(novelIds) - .stream() - .collect(Collectors.toMap(Novel::getNovelId, novel -> novel)); - - List userFeedGetResponseList = visibleFeeds.stream() - .map(feed -> UserFeedGetResponse.of(feed, novelMap.get(feed.getNovelId()), visitorId, - getThumbnailUrl(feed), - getImageCount(feed))) - .toList(); - - // TODO Slice의 hasNext()로 판단하도록 수정 - Boolean isLoadable = visibleFeeds.size() == size; - long feedsCount = feedRepository.countVisibleFeeds(owner, lastFeedId, isVisible, - isUnVisible, genres, visitorId); - - return UserFeedsGetResponse.of(isLoadable, feedsCount, userFeedGetResponseList); - } - - throw new CustomUserException(PRIVATE_PROFILE_STATUS, "the profile status of the user is set to private"); - } - - private static boolean isOwner(User visitor, Long ownerId) { - //TODO 현재는 비로그인 회원인 경우 - return visitor != null && visitor.getUserId().equals(ownerId); - } - - private List getGenres(List genreNames) { - if (genreNames != null && !genreNames.isEmpty()) { - return genreNames.stream() - .map(genreService::getGenreOrException) - .toList(); - } - return null; - } - - private String getThumbnailUrl(Feed feed) { - Optional thumbnailImage = feedImageCustomRepository.findThumbnailFeedImageByFeedId( - feed.getFeedId()); - return thumbnailImage.map(FeedImage::getUrl).orElse(null); - } - - private Integer getImageCount(Feed feed) { - return feedImageRepository.countByFeedId(feed.getFeedId()); - } -} diff --git a/src/main/java/org/websoso/WSSServer/service/ImageService.java b/src/main/java/org/websoso/WSSServer/service/ImageClient.java similarity index 99% rename from src/main/java/org/websoso/WSSServer/service/ImageService.java rename to src/main/java/org/websoso/WSSServer/service/ImageClient.java index 06c3eb229..0104246a4 100644 --- a/src/main/java/org/websoso/WSSServer/service/ImageService.java +++ b/src/main/java/org/websoso/WSSServer/service/ImageClient.java @@ -5,7 +5,11 @@ import static org.websoso.WSSServer.exception.error.CustomImageError.INVALID_IMAGE_FILE_NAME; import static org.websoso.WSSServer.exception.error.CustomImageError.UPLOAD_FAIL_FILE; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -17,15 +21,10 @@ import org.websoso.s3.core.S3FileService; import org.websoso.s3.modle.S3UploadResult; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.UUID; - @Slf4j @Service @RequiredArgsConstructor -public class ImageService { +public class ImageClient { private static final String FEED_UPLOAD_DIRECTORY = "feed/"; private static final String SOURCE_DEFAULT = "DEFAULT"; diff --git a/src/main/java/org/websoso/WSSServer/service/LikeService.java b/src/main/java/org/websoso/WSSServer/service/LikeService.java deleted file mode 100644 index a981c5a8e..000000000 --- a/src/main/java/org/websoso/WSSServer/service/LikeService.java +++ /dev/null @@ -1,45 +0,0 @@ -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; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Like; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.exception.exception.CustomFeedException; -import org.websoso.WSSServer.repository.LikeRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class LikeService { - - private final LikeRepository likeRepository; - - public void createLike(User user, Feed feed) { - if (isUserLikedFeed(user, feed)) { - throw new CustomFeedException(ALREADY_LIKED, "user already liked that feed"); - } - likeRepository.save(Like.create(user.getUserId(), feed)); - } - - public void deleteLike(User user, Feed feed) { - Like like = getLikeOrException(user, feed); - likeRepository.delete(like); - } - - private Like getLikeOrException(User user, Feed feed) { - return likeRepository.findByUserIdAndFeed(user.getUserId(), feed) - .orElseThrow(() -> new CustomFeedException(NOT_LIKED, - "User did not like this feed or like already deleted")); - } - - @Transactional(readOnly = true) - public boolean isUserLikedFeed(User user, Feed feed) { - return likeRepository.existsByUserIdAndFeed(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 0fc4541bf..1856836e4 100644 --- a/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java +++ b/src/main/java/org/websoso/WSSServer/service/MessageFormatter.java @@ -7,9 +7,9 @@ import static org.websoso.WSSServer.domain.common.ReportedType.IMPERTINENCE; import static org.websoso.WSSServer.domain.common.ReportedType.SPOILER; -import org.websoso.WSSServer.domain.Comment; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.feed.domain.Comment; +import org.websoso.WSSServer.feed.domain.Feed; +import org.websoso.WSSServer.user.domain.User; import org.websoso.WSSServer.domain.common.DiscordMessageTemplate; import org.websoso.WSSServer.domain.common.ReportedType; import org.websoso.WSSServer.domain.common.SocialLoginType; diff --git a/src/main/java/org/websoso/WSSServer/service/NotificationService.java b/src/main/java/org/websoso/WSSServer/service/NotificationService.java index d51867538..cb12a605a 100644 --- a/src/main/java/org/websoso/WSSServer/service/NotificationService.java +++ b/src/main/java/org/websoso/WSSServer/service/NotificationService.java @@ -19,8 +19,8 @@ import org.websoso.WSSServer.domain.Notification; import org.websoso.WSSServer.domain.NotificationType; import org.websoso.WSSServer.domain.ReadNotification; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; import org.websoso.WSSServer.domain.common.NotificationTypeGroup; import org.websoso.WSSServer.dto.notification.NotificationCreateRequest; import org.websoso.WSSServer.dto.notification.NotificationGetResponse; @@ -29,12 +29,13 @@ 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; +import org.websoso.WSSServer.notification.FCMClient; import org.websoso.WSSServer.notification.dto.FCMMessageRequest; import org.websoso.WSSServer.repository.NotificationRepository; import org.websoso.WSSServer.repository.NotificationTypeRepository; import org.websoso.WSSServer.repository.ReadNotificationRepository; -import org.websoso.WSSServer.repository.UserRepository; +import org.websoso.WSSServer.user.repository.UserRepository; +import org.websoso.WSSServer.user.service.UserService; @Service @RequiredArgsConstructor @@ -46,7 +47,7 @@ public class NotificationService { private final ReadNotificationRepository readNotificationRepository; private final NotificationTypeRepository notificationTypeRepository; private final UserRepository userRepository; - private final FCMService fcmService; + private final FCMClient fcmClient; private final UserService userService; @Transactional(readOnly = true) @@ -153,7 +154,7 @@ private void sendNoticePushMessage(Long userId, Notification notification) { List targetFCMTokens = getTargetFCMTokens(userId); - fcmService.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); + fcmClient.sendMulticastPushMessage(targetFCMTokens, fcmMessageRequest); } private List getTargetFCMTokens(Long userId) { diff --git a/src/main/java/org/websoso/WSSServer/service/NovelService.java b/src/main/java/org/websoso/WSSServer/service/NovelService.java deleted file mode 100644 index 804facb7a..000000000 --- a/src/main/java/org/websoso/WSSServer/service/NovelService.java +++ /dev/null @@ -1,440 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.domain.common.ReadStatus.QUIT; -import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHED; -import static org.websoso.WSSServer.domain.common.ReadStatus.WATCHING; -import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; -import static org.websoso.WSSServer.exception.error.CustomNovelError.NOVEL_NOT_FOUND; -import static org.websoso.WSSServer.exception.error.CustomUserNovelError.ALREADY_INTERESTED; -import static org.websoso.WSSServer.exception.error.CustomUserNovelError.NOT_INTERESTED; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Avatar; -import org.websoso.WSSServer.domain.Feed; -import org.websoso.WSSServer.domain.Genre; -import org.websoso.WSSServer.domain.GenrePreference; -import org.websoso.WSSServer.domain.Keyword; -import org.websoso.WSSServer.domain.Novel; -import org.websoso.WSSServer.domain.NovelGenre; -import org.websoso.WSSServer.domain.PopularNovel; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserNovel; -import org.websoso.WSSServer.domain.UserNovelKeyword; -import org.websoso.WSSServer.domain.common.AttractivePointName; -import org.websoso.WSSServer.domain.common.GenreName; -import org.websoso.WSSServer.dto.keyword.KeywordCountGetResponse; -import org.websoso.WSSServer.dto.novel.FilteredNovelsGetResponse; -import org.websoso.WSSServer.dto.novel.NovelGetResponseBasic; -import org.websoso.WSSServer.dto.novel.NovelGetResponseInfoTab; -import org.websoso.WSSServer.dto.novel.NovelGetResponsePreview; -import org.websoso.WSSServer.dto.novel.SearchedNovelsGetResponse; -import org.websoso.WSSServer.dto.platform.PlatformGetResponse; -import org.websoso.WSSServer.dto.popularNovel.PopularNovelGetResponse; -import org.websoso.WSSServer.dto.popularNovel.PopularNovelsGetResponse; -import org.websoso.WSSServer.dto.userNovel.TasteNovelGetResponse; -import org.websoso.WSSServer.dto.userNovel.TasteNovelsGetResponse; -import org.websoso.WSSServer.exception.exception.CustomGenreException; -import org.websoso.WSSServer.exception.exception.CustomNovelException; -import org.websoso.WSSServer.exception.exception.CustomUserNovelException; -import org.websoso.WSSServer.repository.AvatarRepository; -import org.websoso.WSSServer.repository.FeedRepository; -import org.websoso.WSSServer.repository.GenrePreferenceRepository; -import org.websoso.WSSServer.repository.NovelGenreRepository; -import org.websoso.WSSServer.repository.NovelPlatformRepository; -import org.websoso.WSSServer.repository.NovelRepository; -import org.websoso.WSSServer.repository.PopularNovelRepository; -import org.websoso.WSSServer.repository.UserNovelAttractivePointRepository; -import org.websoso.WSSServer.repository.UserNovelKeywordRepository; -import org.websoso.WSSServer.repository.UserNovelRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class NovelService { - - private static final int ATTRACTIVE_POINT_SIZE = 3; - private static final int KEYWORD_SIZE = 5; - - private final NovelRepository novelRepository; - private final UserNovelService userNovelService; - private final UserNovelRepository userNovelRepository; - private final NovelPlatformRepository novelPlatformRepository; - private final UserNovelAttractivePointRepository userNovelAttractivePointRepository; - private final FeedRepository feedRepository; - private final NovelGenreRepository novelGenreRepository; - private final UserNovelKeywordRepository userNovelKeywordRepository; - private final KeywordService keywordService; - private final GenreService genreService; - private final PopularNovelRepository popularNovelRepository; - private final AvatarRepository avatarRepository; - private final GenrePreferenceRepository genrePreferenceRepository; - - @Transactional(readOnly = true) - public Novel getNovelOrException(Long novelId) { - return novelRepository.findById(novelId) - .orElseThrow(() -> new CustomNovelException(NOVEL_NOT_FOUND, - "novel with the given id is not found")); - } - - @Transactional(readOnly = true) - public NovelGetResponseBasic getNovelInfoBasic(User user, Long novelId) { - Novel novel = getNovelOrException(novelId); - List novelGenres = novelGenreRepository.findAllByNovel(novel); - Integer novelRatingCount = userNovelRepository.countByNovelAndUserNovelRatingNot(novel, 0.0f); - Float novelRating = novelRatingCount == 0 ? 0.0f - : Math.round(userNovelRepository.sumUserNovelRatingByNovel(novel) / novelRatingCount * 10.0f) / 10.0f; - return NovelGetResponseBasic.of( - novel, - userNovelService.getUserNovelOrNull(user, novel), - getNovelGenreNames(novelGenres), - getRandomNovelGenreImage(novelGenres), - userNovelRepository.countByNovelAndIsInterestTrue(novel), - novelRating, - novelRatingCount, - feedRepository.countByNovelId(novelId) - ); - } - - private String getNovelGenreNames(List novelGenres) { - return novelGenres.stream() - .map(novelGenre -> getKoreanGenreName(novelGenre.getGenre().getGenreName())) - .collect(Collectors.joining("/")); - } - - private String getKoreanGenreName(String englishGenreName) { - return Arrays.stream(GenreName.values()) - .filter(genreName -> genreName.getLabel().equalsIgnoreCase(englishGenreName)) - .findFirst() - .map(GenreName::getKoreanLabel) - .orElseThrow( - () -> new CustomGenreException(GENRE_NOT_FOUND, "Genre with the given genreName is not found")); - } - - private String getRandomNovelGenreImage(List novelGenres) { - Random random = new Random(); - return novelGenres.get(random.nextInt(novelGenres.size())).getGenre().getGenreImage(); - } - - public void registerAsInterest(User user, Long novelId) { - Novel novel = getNovelOrException(novelId); - UserNovel userNovel = userNovelService.getUserNovelOrNull(user, novel); - - if (userNovel != null && userNovel.getIsInterest()) { - throw new CustomUserNovelException(ALREADY_INTERESTED, "already registered as interested"); - } - - if (userNovel == null) { - try { - userNovel = userNovelService.createUserNovelByInterest(user, novel); - } catch (DataIntegrityViolationException e) { - userNovel = userNovelService.getUserNovelOrException(user, novelId); - } - } - - userNovel.setIsInterest(true); - } - - public void unregisterAsInterest(User user, Long novelId) { - UserNovel userNovel = userNovelService.getUserNovelOrException(user, novelId); - - if (!userNovel.getIsInterest()) { - throw new CustomUserNovelException(NOT_INTERESTED, "not registered as interest"); - } - - userNovel.setIsInterest(false); - - if (isUserNovelOnlyByInterest(userNovel)) { - userNovelRepository.delete(userNovel); - } - - } - - private Boolean isUserNovelOnlyByInterest(UserNovel userNovel) { - return userNovel.getStatus() == null; - } - - @Transactional(readOnly = true) - public NovelGetResponseInfoTab getNovelInfoInfoTab(Long novelId) { - Novel novel = getNovelOrException(novelId); - return NovelGetResponseInfoTab.of( - novel, - getPlatforms(novel), - getAttractivePoints(novel), - getKeywords(novel), - userNovelRepository.countByNovelAndStatus(novel, WATCHING), - userNovelRepository.countByNovelAndStatus(novel, WATCHED), - userNovelRepository.countByNovelAndStatus(novel, QUIT) - ); - } - - private List getPlatforms(Novel novel) { - return novelPlatformRepository.findAllByNovel(novel).stream() - .map(PlatformGetResponse::of) - .collect(Collectors.toList()); - } - - private List getAttractivePoints(Novel novel) { - Map attractivePointMap = makeAttractivePointMapExcludingZero(novel); - - if (attractivePointMap.isEmpty()) { - return Collections.emptyList(); - } - - return getTOP3AttractivePoints(attractivePointMap); - } - - private Map makeAttractivePointMapExcludingZero(Novel novel) { - Map attractivePointMap = new HashMap<>(); - - for (AttractivePointName point : AttractivePointName.values()) { - attractivePointMap.put(point.getLabel(), - userNovelAttractivePointRepository.countByUserNovel_NovelAndAttractivePoint_AttractivePointName( - novel, point.getLabel())); - } - - attractivePointMap.entrySet().removeIf(entry -> entry.getValue() == 0); - - return attractivePointMap; - } - - private List getTOP3AttractivePoints(Map attractivePointMap) { - Map> groupedByValue = groupAttractivePointByValue(attractivePointMap); - - List result = new ArrayList<>(); - List sortedKeys = new ArrayList<>(groupedByValue.keySet()); - Collections.sort(sortedKeys, Collections.reverseOrder()); - - Random random = new Random(); - - for (Integer key : sortedKeys) { - List items = groupedByValue.get(key); - if (result.size() + items.size() > ATTRACTIVE_POINT_SIZE) { - Collections.shuffle(items, random); - items = items.subList(0, ATTRACTIVE_POINT_SIZE - result.size()); - } - result.addAll(items); - if (result.size() >= ATTRACTIVE_POINT_SIZE) { - break; - } - } - - return result; - } - - private Map> groupAttractivePointByValue(Map attractivePointMap) { - Map> groupedByValue = new HashMap<>(); - - for (Map.Entry entry : attractivePointMap.entrySet()) { - groupedByValue - .computeIfAbsent(entry.getValue(), k -> new ArrayList<>()) - .add(entry.getKey()); - } - - return groupedByValue; - } - - private List getKeywords(Novel novel) { - List userNovelKeywords = userNovelKeywordRepository.findAllByUserNovel_Novel(novel); - - if (userNovelKeywords.isEmpty()) { - return Collections.emptyList(); - } - - Map keywordFrequencyMap = userNovelKeywords.stream() - .collect(Collectors.groupingBy(UserNovelKeyword::getKeyword, Collectors.counting())); - - return keywordFrequencyMap.entrySet().stream() - .sorted(Map.Entry.comparingByValue().reversed()) - .limit(KEYWORD_SIZE) - .map(entry -> KeywordCountGetResponse.of(entry.getKey(), entry.getValue().intValue())) - .collect(Collectors.toList()); - } - - @Transactional(readOnly = true) - public FilteredNovelsGetResponse getFilteredNovels(List genreNames, Boolean isCompleted, Float novelRating, - List keywordIds, int page, int size) { - PageRequest pageRequest = PageRequest.of(page, size); - List genres = getGenres(genreNames); - List keywords = getKeywords(keywordIds); - - Page novels = novelRepository.findFilteredNovels(pageRequest, genres, isCompleted, novelRating, - keywords); - - List novelGetResponsePreviews = novels.stream() - .map(this::convertToDTO) - .collect(Collectors.toList()); - - return FilteredNovelsGetResponse.of(novels.getTotalElements(), novels.hasNext(), novelGetResponsePreviews); - } - - @Transactional(readOnly = true) - public SearchedNovelsGetResponse searchNovels(String query, int page, int size) { - PageRequest pageRequest = PageRequest.of(page, size); - String searchQuery = query.replaceAll("\\s+", "").replaceAll("[^a-zA-Z0-9가-힣]", ""); - - if (searchQuery.isBlank()) { - return SearchedNovelsGetResponse.of(0L, false, Collections.emptyList()); - } - - Page novels = novelRepository.findSearchedNovels(pageRequest, searchQuery); - - List novelGetResponsePreviews = novels.stream() - .map(this::convertToDTO) - .collect(Collectors.toList()); - - return SearchedNovelsGetResponse.of(novels.getTotalElements(), novels.hasNext(), novelGetResponsePreviews); - } - - private List getGenres(List genreNames) { - genreNames = genreNames == null - ? Collections.emptyList() - : genreNames; - - List genres = new ArrayList<>(); - if (!genreNames.isEmpty()) { - for (String genreName : genreNames) { - genres.add(genreService.getGenreOrException(genreName)); - } - } - - return genres; - } - - private List getKeywords(List keywordIds) { - keywordIds = keywordIds == null - ? Collections.emptyList() - : keywordIds; - - List keywords = new ArrayList<>(); - if (!keywordIds.isEmpty()) { - for (Integer keywordId : keywordIds) { - keywords.add(keywordService.getKeywordOrException(keywordId)); - } - } - - return keywords; - } - - private NovelGetResponsePreview convertToDTO(Novel novel) { - List userNovels = novel.getUserNovels(); - - long interestCount = userNovels.stream() - .filter(UserNovel::getIsInterest) - .count(); - long novelRatingCount = userNovels.stream() - .filter(un -> un.getUserNovelRating() != 0.0f) - .count(); - double novelRatingSum = userNovels.stream() - .filter(un -> un.getUserNovelRating() != 0.0f) - .mapToDouble(UserNovel::getUserNovelRating) - .sum(); - - Float novelRatingAverage = novelRatingCount == 0 - ? 0.0f - : Math.round((float) (novelRatingSum / novelRatingCount) * 10.0f) / 10.0f; - - return NovelGetResponsePreview.of( - novel, - interestCount, - novelRatingAverage, - novelRatingCount - ); - } - - @Transactional(readOnly = true) - public PopularNovelsGetResponse getTodayPopularNovels() { - List novelIdsFromPopularNovel = getNovelIdsFromPopularNovel(); - List selectedNovelIdsFromPopularNovel = getSelectedNovelIdsFromPopularNovel(novelIdsFromPopularNovel); - List popularNovels = getSelectedPopularNovels(selectedNovelIdsFromPopularNovel); - List popularFeedsFromPopularNovels = getPopularFeedsFromPopularNovels(selectedNovelIdsFromPopularNovel); - - Map feedMap = createFeedMap(popularFeedsFromPopularNovels); - Map avatarMap = createAvatarMap(feedMap); - - return createPopularNovelsGetResponse(popularNovels, feedMap, avatarMap); - } - - private List getNovelIdsFromPopularNovel() { - return new ArrayList<>(popularNovelRepository.findAll() - .stream() - .map(PopularNovel::getNovelId) - .toList()); - } - - private static List getSelectedNovelIdsFromPopularNovel(List popularNovelIds) { - Collections.shuffle(popularNovelIds); - return popularNovelIds.size() > 10 - ? popularNovelIds.subList(0, 10) - : popularNovelIds; - } - - private List getSelectedPopularNovels(List selectedPopularNovelIds) { - return novelRepository.findAllById(selectedPopularNovelIds); - } - - private List getPopularFeedsFromPopularNovels(List selectedPopularNovelIds) { - return feedRepository.findPopularFeedsByNovelIds(selectedPopularNovelIds); - } - - private static Map createFeedMap(List popularFeedsFromPopularNovels) { - return popularFeedsFromPopularNovels.stream() - .collect(Collectors.toMap(Feed::getNovelId, feed -> feed)); - } - - private Map createAvatarMap(Map feedMap) { - Set avatarIds = feedMap.values() - .stream() - .map(feed -> feed.getUser().getAvatarId()) - .collect(Collectors.toSet()); - - List avatars = avatarRepository.findAllById(avatarIds); - return avatars.stream() - .collect(Collectors.toMap(Avatar::getAvatarId, avatar -> avatar)); - } - - private static PopularNovelsGetResponse createPopularNovelsGetResponse(List popularNovels, - Map feedMap, - Map avatarMap) { - List popularNovelResponses = popularNovels.stream() - .map(novel -> { - Feed feed = feedMap.get(novel.getNovelId()); - if (feed == null) { - return PopularNovelGetResponse.of(novel, null, null); - } - Avatar avatar = avatarMap.get(feed.getUser().getAvatarId()); - return PopularNovelGetResponse.of(novel, avatar, feed); - }) - .toList(); - return new PopularNovelsGetResponse(popularNovelResponses); - } - - @Transactional(readOnly = true) - public TasteNovelsGetResponse getTasteNovels(User user) { - List preferGenres = genrePreferenceRepository.findByUser(user) - .stream() - .map(GenrePreference::getGenre) - .toList(); - - List tasteNovels = userNovelRepository.findTasteNovels(preferGenres); - - List tasteNovelGetResponses = tasteNovels.stream() - .map(TasteNovelGetResponse::of) - .toList(); - - return TasteNovelsGetResponse.of(tasteNovelGetResponses); - } -} diff --git a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java b/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java deleted file mode 100644 index d2890983d..000000000 --- a/src/main/java/org/websoso/WSSServer/service/PopularFeedService.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.websoso.WSSServer.service; - -import static java.lang.Boolean.TRUE; - -import java.util.List; -import java.util.Optional; -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 -@RequiredArgsConstructor -@Transactional -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) - public PopularFeedsGetResponse getPopularFeeds(User user) { - Long currentUserId = Optional.ofNullable(user) - .map(User::getUserId) - .orElse(null); - - List popularFeeds = Optional.ofNullable(user) - .map(u -> findPopularFeedsWithUser(u.getUserId())) - .orElseGet(this::findPopularFeedsWithoutUser); - - List popularFeedGetResponses = - mapToPopularFeedGetResponseList(popularFeeds, currentUserId); - - return new PopularFeedsGetResponse(popularFeedGetResponses); - } - - private List findPopularFeedsWithUser(Long userId) { - return popularFeedRepository.findTodayPopularFeeds(userId); - } - - private List findPopularFeedsWithoutUser() { - return popularFeedRepository.findTop9ByOrderByPopularFeedIdDesc(); - } - - private static List mapToPopularFeedGetResponseList(List popularFeeds, - Long currentUserId) { - return popularFeeds.stream() - .filter(pf -> pf.getFeed().isVisibleTo(currentUserId)) - .map(PopularFeedGetResponse::of) - .toList(); - } -} diff --git a/src/main/java/org/websoso/WSSServer/service/ReportedCommentService.java b/src/main/java/org/websoso/WSSServer/service/ReportedCommentService.java deleted file mode 100644 index cd751707f..000000000 --- a/src/main/java/org/websoso/WSSServer/service/ReportedCommentService.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.exception.error.CustomCommentError.ALREADY_REPORTED_COMMENT; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.domain.Comment; -import org.websoso.WSSServer.domain.ReportedComment; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.common.ReportedType; -import org.websoso.WSSServer.exception.exception.CustomCommentException; -import org.websoso.WSSServer.repository.ReportedCommentRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class ReportedCommentService { - - private final ReportedCommentRepository reportedCommentRepository; - - public 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)); - } - - public int getReportedCountByReportedType(Comment comment, ReportedType reportedType) { - return reportedCommentRepository.countByCommentAndReportedType(comment, reportedType); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/service/ReportedFeedService.java b/src/main/java/org/websoso/WSSServer/service/ReportedFeedService.java deleted file mode 100644 index db60db6dc..000000000 --- a/src/main/java/org/websoso/WSSServer/service/ReportedFeedService.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.websoso.WSSServer.service; - -import static org.websoso.WSSServer.exception.error.CustomFeedError.ALREADY_REPORTED_FEED; - -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.ReportedFeed; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.common.ReportedType; -import org.websoso.WSSServer.exception.exception.CustomFeedException; -import org.websoso.WSSServer.repository.ReportedFeedRepository; - -@Service -@RequiredArgsConstructor -@Transactional -public class ReportedFeedService { - - private final ReportedFeedRepository reportedFeedRepository; - - public 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)); - } - - public int getReportedCountByReportedType(Feed feed, ReportedType reportedType) { - return reportedFeedRepository.countByFeedAndReportedType(feed, reportedType); - } - -} diff --git a/src/main/java/org/websoso/WSSServer/service/SosoPickService.java b/src/main/java/org/websoso/WSSServer/service/SosoPickService.java index cf4e8af73..1345eba83 100644 --- a/src/main/java/org/websoso/WSSServer/service/SosoPickService.java +++ b/src/main/java/org/websoso/WSSServer/service/SosoPickService.java @@ -9,7 +9,7 @@ import org.springframework.transaction.annotation.Transactional; import org.websoso.WSSServer.dto.sosoPick.SosoPickGetResponse; import org.websoso.WSSServer.dto.sosoPick.SosoPickNovelGetResponse; -import org.websoso.WSSServer.repository.NovelRepository; +import org.websoso.WSSServer.novel.repository.NovelRepository; @Service @RequiredArgsConstructor diff --git a/src/main/java/org/websoso/WSSServer/domain/RefreshToken.java b/src/main/java/org/websoso/WSSServer/user/domain/RefreshToken.java similarity index 90% rename from src/main/java/org/websoso/WSSServer/domain/RefreshToken.java rename to src/main/java/org/websoso/WSSServer/user/domain/RefreshToken.java index 3ac89006f..82f6d1db2 100644 --- a/src/main/java/org/websoso/WSSServer/domain/RefreshToken.java +++ b/src/main/java/org/websoso/WSSServer/user/domain/RefreshToken.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.user.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/org/websoso/WSSServer/domain/User.java b/src/main/java/org/websoso/WSSServer/user/domain/User.java similarity index 84% rename from src/main/java/org/websoso/WSSServer/domain/User.java rename to src/main/java/org/websoso/WSSServer/user/domain/User.java index 6dcd4509d..2a5ac0fe3 100644 --- a/src/main/java/org/websoso/WSSServer/domain/User.java +++ b/src/main/java/org/websoso/WSSServer/user/domain/User.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.user.domain; import static jakarta.persistence.CascadeType.ALL; import static org.websoso.WSSServer.domain.common.Gender.M; @@ -21,6 +21,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; +import org.websoso.WSSServer.domain.GenrePreference; +import org.websoso.WSSServer.domain.ReadNotification; +import org.websoso.WSSServer.feed.domain.ReportedComment; +import org.websoso.WSSServer.feed.domain.ReportedFeed; +import org.websoso.WSSServer.library.domain.UserNovel; import org.websoso.common.entity.BaseEntity; import org.websoso.WSSServer.domain.common.Gender; import org.websoso.WSSServer.domain.common.Role; @@ -71,6 +76,10 @@ public class User extends BaseEntity { @Column(columnDefinition = "tinyint default 1", nullable = false) private Byte avatarId; + // TODO: 우선 연관 관계를 직접 맺지 않게 수정 + @Column(nullable = false) + private Long avatarProfileId; + @Column(columnDefinition = "Boolean default true", nullable = false) private Boolean isProfilePublic; @@ -115,7 +124,7 @@ public void updateProfileStatus(Boolean profileStatus) { public void updateUserProfile(UpdateMyProfileRequest updateMyProfileRequest) { if (updateMyProfileRequest.avatarId() != null) { - this.avatarId = updateMyProfileRequest.avatarId(); + this.avatarProfileId = updateMyProfileRequest.avatarId(); } if (updateMyProfileRequest.nickname() != null) { this.nickname = updateMyProfileRequest.nickname(); @@ -135,11 +144,15 @@ public UserBasicInfo getUserBasicInfo(String avatarImage) { return UserBasicInfo.of(this.getUserId(), this.getNickname(), avatarImage); } + // TODO: 유저 객체 생성시, 기본 값이 바로 박혀있으면 확장성에서 불리하다고 생각 (기중) + // DEFAULT 값이 하드코딩되어 있음 -> private static final로 분리하는게 좋지 않을까 생각 + // 컬럼 매핑에 DEFAULT가 선언되어 있어서 중복이라고 생각할 수 있지만, 2차 적으로 어플리케이션에도 있는게 컨텍스트 파악하는데 유리하다고 생각 private User(String socialId, String nickname, String email) { this.intro = "안녕하세요"; this.gender = M; this.birth = Year.now(); this.avatarId = 1; + this.avatarProfileId = 1L; this.isProfilePublic = true; this.isPushEnabled = true; this.serviceAgreed = false; @@ -151,6 +164,7 @@ private User(String socialId, String nickname, String email) { this.email = email; } + // TODO: 소셜 로그인 가입 처럼 이러한 메서드에서 기본 값 설정하는게 더 유리할 것으로 판단 (기중) public static User createBySocial(String socialId, String nickname, String email) { return new User(socialId, nickname, email); } diff --git a/src/main/java/org/websoso/WSSServer/domain/UserAppleToken.java b/src/main/java/org/websoso/WSSServer/user/domain/UserAppleToken.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/UserAppleToken.java rename to src/main/java/org/websoso/WSSServer/user/domain/UserAppleToken.java index 2f30cdf65..d905e23db 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserAppleToken.java +++ b/src/main/java/org/websoso/WSSServer/user/domain/UserAppleToken.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.user.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/domain/UserDevice.java b/src/main/java/org/websoso/WSSServer/user/domain/UserDevice.java similarity index 96% rename from src/main/java/org/websoso/WSSServer/domain/UserDevice.java rename to src/main/java/org/websoso/WSSServer/user/domain/UserDevice.java index 61de56ad5..e417df8ab 100644 --- a/src/main/java/org/websoso/WSSServer/domain/UserDevice.java +++ b/src/main/java/org/websoso/WSSServer/user/domain/UserDevice.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.user.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/org/websoso/WSSServer/domain/WithdrawalReason.java b/src/main/java/org/websoso/WSSServer/user/domain/WithdrawalReason.java similarity index 95% rename from src/main/java/org/websoso/WSSServer/domain/WithdrawalReason.java rename to src/main/java/org/websoso/WSSServer/user/domain/WithdrawalReason.java index 8b3c2d704..223ded172 100644 --- a/src/main/java/org/websoso/WSSServer/domain/WithdrawalReason.java +++ b/src/main/java/org/websoso/WSSServer/user/domain/WithdrawalReason.java @@ -1,4 +1,4 @@ -package org.websoso.WSSServer.domain; +package org.websoso.WSSServer.user.domain; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/src/main/java/org/websoso/WSSServer/repository/UserAppleTokenRepository.java b/src/main/java/org/websoso/WSSServer/user/repository/UserAppleTokenRepository.java similarity index 66% rename from src/main/java/org/websoso/WSSServer/repository/UserAppleTokenRepository.java rename to src/main/java/org/websoso/WSSServer/user/repository/UserAppleTokenRepository.java index e15741359..7631cf94a 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserAppleTokenRepository.java +++ b/src/main/java/org/websoso/WSSServer/user/repository/UserAppleTokenRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.user.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserAppleToken; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserAppleToken; @Repository public interface UserAppleTokenRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserDeviceRepository.java b/src/main/java/org/websoso/WSSServer/user/repository/UserDeviceRepository.java similarity index 73% rename from src/main/java/org/websoso/WSSServer/repository/UserDeviceRepository.java rename to src/main/java/org/websoso/WSSServer/user/repository/UserDeviceRepository.java index 357758cea..81aedef37 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserDeviceRepository.java +++ b/src/main/java/org/websoso/WSSServer/user/repository/UserDeviceRepository.java @@ -1,10 +1,10 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.user.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; @Repository public interface UserDeviceRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/UserRepository.java b/src/main/java/org/websoso/WSSServer/user/repository/UserRepository.java similarity index 79% rename from src/main/java/org/websoso/WSSServer/repository/UserRepository.java rename to src/main/java/org/websoso/WSSServer/user/repository/UserRepository.java index 99db45972..7f86407cf 100644 --- a/src/main/java/org/websoso/WSSServer/repository/UserRepository.java +++ b/src/main/java/org/websoso/WSSServer/user/repository/UserRepository.java @@ -1,9 +1,9 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.user.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.User; +import org.websoso.WSSServer.user.domain.User; @Repository public interface UserRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/repository/WithdrawalReasonRepository.java b/src/main/java/org/websoso/WSSServer/user/repository/WithdrawalReasonRepository.java similarity index 67% rename from src/main/java/org/websoso/WSSServer/repository/WithdrawalReasonRepository.java rename to src/main/java/org/websoso/WSSServer/user/repository/WithdrawalReasonRepository.java index 8f32b71a7..e94928644 100644 --- a/src/main/java/org/websoso/WSSServer/repository/WithdrawalReasonRepository.java +++ b/src/main/java/org/websoso/WSSServer/user/repository/WithdrawalReasonRepository.java @@ -1,8 +1,8 @@ -package org.websoso.WSSServer.repository; +package org.websoso.WSSServer.user.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.websoso.WSSServer.domain.WithdrawalReason; +import org.websoso.WSSServer.user.domain.WithdrawalReason; @Repository public interface WithdrawalReasonRepository extends JpaRepository { diff --git a/src/main/java/org/websoso/WSSServer/service/UserService.java b/src/main/java/org/websoso/WSSServer/user/service/UserService.java similarity index 70% rename from src/main/java/org/websoso/WSSServer/service/UserService.java rename to src/main/java/org/websoso/WSSServer/user/service/UserService.java index 23da7345d..3debea369 100644 --- a/src/main/java/org/websoso/WSSServer/service/UserService.java +++ b/src/main/java/org/websoso/WSSServer/user/service/UserService.java @@ -1,8 +1,7 @@ -package org.websoso.WSSServer.service; +package org.websoso.WSSServer.user.service; import static java.lang.Boolean.FALSE; import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.JOIN; -import static org.websoso.WSSServer.domain.common.DiscordWebhookMessageType.WITHDRAW; import static org.websoso.WSSServer.exception.error.CustomAvatarError.AVATAR_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomGenreError.GENRE_NOT_FOUND; import static org.websoso.WSSServer.exception.error.CustomUserError.ALREADY_SET_AVATAR; @@ -18,22 +17,20 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.websoso.WSSServer.config.jwt.CustomAuthenticationToken; -import org.websoso.WSSServer.config.jwt.JwtProvider; -import org.websoso.WSSServer.domain.Avatar; +import org.websoso.WSSServer.domain.AvatarProfile; import org.websoso.WSSServer.domain.Genre; import org.websoso.WSSServer.domain.GenrePreference; -import org.websoso.WSSServer.domain.User; -import org.websoso.WSSServer.domain.UserDevice; -import org.websoso.WSSServer.domain.WithdrawalReason; +import org.websoso.WSSServer.repository.AvatarProfileRepository; +import org.websoso.WSSServer.service.DiscordMessageClient; +import org.websoso.WSSServer.service.MessageFormatter; +import org.websoso.WSSServer.user.domain.User; +import org.websoso.WSSServer.user.domain.UserDevice; import org.websoso.WSSServer.domain.common.DiscordWebhookMessage; import org.websoso.WSSServer.domain.common.SocialLoginType; -import org.websoso.WSSServer.dto.auth.LogoutRequest; import org.websoso.WSSServer.dto.notification.PushSettingGetResponse; import org.websoso.WSSServer.dto.user.EditMyInfoRequest; import org.websoso.WSSServer.dto.user.EditProfileStatusRequest; import org.websoso.WSSServer.dto.user.FCMTokenRequest; -import org.websoso.WSSServer.dto.user.LoginResponse; import org.websoso.WSSServer.dto.user.MyProfileResponse; import org.websoso.WSSServer.dto.user.NicknameValidation; import org.websoso.WSSServer.dto.user.ProfileGetResponse; @@ -43,22 +40,14 @@ import org.websoso.WSSServer.dto.user.UpdateMyProfileRequest; import org.websoso.WSSServer.dto.user.UserIdAndNicknameResponse; import org.websoso.WSSServer.dto.user.UserInfoGetResponse; -import org.websoso.WSSServer.dto.user.WithdrawalRequest; import org.websoso.WSSServer.exception.error.CustomUserError; import org.websoso.WSSServer.exception.exception.CustomAvatarException; import org.websoso.WSSServer.exception.exception.CustomGenreException; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.oauth2.service.AppleService; -import org.websoso.WSSServer.oauth2.service.KakaoService; -import org.websoso.WSSServer.repository.AvatarRepository; -import org.websoso.WSSServer.repository.CommentRepository; -import org.websoso.WSSServer.repository.FeedRepository; import org.websoso.WSSServer.repository.GenrePreferenceRepository; import org.websoso.WSSServer.repository.GenreRepository; -import org.websoso.WSSServer.repository.RefreshTokenRepository; -import org.websoso.WSSServer.repository.UserDeviceRepository; -import org.websoso.WSSServer.repository.UserRepository; -import org.websoso.WSSServer.repository.WithdrawalReasonRepository; +import org.websoso.WSSServer.user.repository.UserDeviceRepository; +import org.websoso.WSSServer.user.repository.UserRepository; @Service @RequiredArgsConstructor @@ -66,20 +55,13 @@ public class UserService { private final UserRepository userRepository; - private final JwtProvider jwtProvider; - private final AvatarRepository avatarRepository; + private final AvatarProfileRepository avatarProfileRepository; private final GenrePreferenceRepository genrePreferenceRepository; private final GenreRepository genreRepository; - private final RefreshTokenRepository refreshTokenRepository; - private final KakaoService kakaoService; - private final AppleService appleService; - private final FeedRepository feedRepository; - private final CommentRepository commentRepository; - private final MessageService messageService; - private final WithdrawalReasonRepository withdrawalReasonRepository; private final UserDeviceRepository userDeviceRepository; - private static final String KAKAO_PREFIX = "kakao"; - private static final String APPLE_PREFIX = "apple"; + + // TODO: 상위 레이어에서 분리 예정 + private final DiscordMessageClient discordMessageClient; @Transactional(readOnly = true) public NicknameValidation isNicknameAvailable(User user, String nickname) { @@ -92,16 +74,6 @@ public NicknameValidation isNicknameAvailable(User user, String nickname) { return NicknameValidation.of(true); } - @Transactional(readOnly = true) - public LoginResponse login(Long userId) { - User user = getUserOrException(userId); - - CustomAuthenticationToken customAuthenticationToken = new CustomAuthenticationToken(user.getUserId(), null, null); - String token = jwtProvider.generateAccessToken(customAuthenticationToken); - - return LoginResponse.of(token); - } - @Transactional(readOnly = true) public UserInfoGetResponse getUserInfo(User user) { return UserInfoGetResponse.of(user); @@ -127,14 +99,18 @@ public User getUserOrException(Long userId) { @Transactional(readOnly = true) public MyProfileResponse getMyProfileInfo(User user) { - Byte avatarId = user.getAvatarId(); - Avatar avatar = findAvatarByIdOrThrow(avatarId); + Long avatarProfileId = user.getAvatarProfileId(); + + AvatarProfile avatarProfile = findAvatarProfileByIdOrThrow(avatarProfileId); + List genrePreferences = genrePreferenceRepository.findByUser(user); - return MyProfileResponse.of(user, avatar, genrePreferences); + + return MyProfileResponse.of(user, avatarProfile, genrePreferences); } + // TODO: 멱등성을 보장하는데, Exception이 발생하는게 맞나? (기중) public void updateMyProfileInfo(User user, UpdateMyProfileRequest updateMyProfileRequest) { - checkIfAlreadySetOrThrow(user.getAvatarId(), updateMyProfileRequest.avatarId(), + checkIfAlreadySetOrThrow(user.getAvatarProfileId(), updateMyProfileRequest.avatarId(), ALREADY_SET_AVATAR, "avatarId with given is already set"); checkIfAlreadySetOrThrow(user.getNickname(), updateMyProfileRequest.nickname(), @@ -144,6 +120,7 @@ public void updateMyProfileInfo(User user, UpdateMyProfileRequest updateMyProfil checkIfAlreadySetOrThrow(user.getIntro(), updateMyProfileRequest.intro(), ALREADY_SET_INTRO, "intro with given is already set"); + // TODO: 선호 장르만 기존 값을 받고 있음 (기중) genrePreferenceRepository.deleteAllByUser(user); List newPreferGenres = createGenrePreferences(user, updateMyProfileRequest.genrePreferences()); @@ -152,6 +129,16 @@ public void updateMyProfileInfo(User user, UpdateMyProfileRequest updateMyProfil user.updateUserProfile(updateMyProfileRequest); } + public void registerUserInfo(User user, RegisterUserInfoRequest registerUserInfoRequest) { + checkNicknameIfAlreadyExist(registerUserInfoRequest.nickname()); + user.updateUserInfo(registerUserInfoRequest); + List preferGenres = createGenrePreferences(user, registerUserInfoRequest.genrePreferences()); + genrePreferenceRepository.saveAll(preferGenres); + + discordMessageClient.sendDiscordWebhookMessage(DiscordWebhookMessage.of( + MessageFormatter.formatUserJoinMessage(user, SocialLoginType.fromSocialId(user.getSocialId())), JOIN)); + } + @Transactional(readOnly = true) public ProfileGetResponse getProfileInfo(User visitor, Long ownerId) { if (ownerId == -1L) { @@ -159,55 +146,20 @@ public ProfileGetResponse getProfileInfo(User visitor, Long ownerId) { "The profile for this user is inaccessible: unknown"); } User owner = getUserOrException(ownerId); - Byte avatarId = owner.getAvatarId(); - Avatar avatar = findAvatarByIdOrThrow(avatarId); + Long avatarId = owner.getAvatarProfileId(); + AvatarProfile avatar = findAvatarProfileByIdOrThrow(avatarId); List genrePreferences = genrePreferenceRepository.findByUser(owner); boolean isOwner = visitor != null && visitor.getUserId().equals(ownerId); return ProfileGetResponse.of(isOwner, owner, avatar, genrePreferences); } - private Avatar findAvatarByIdOrThrow(Byte avatarId) { - return avatarRepository.findById(avatarId) + private AvatarProfile findAvatarProfileByIdOrThrow(Long avatarId) { + return avatarProfileRepository.findById(avatarId) .orElseThrow( () -> new CustomAvatarException(AVATAR_NOT_FOUND, "avatar with the given id was not found")); } - public void registerUserInfo(User user, RegisterUserInfoRequest registerUserInfoRequest) { - checkNicknameIfAlreadyExist(registerUserInfoRequest.nickname()); - user.updateUserInfo(registerUserInfoRequest); - List preferGenres = createGenrePreferences(user, registerUserInfoRequest.genrePreferences()); - genrePreferenceRepository.saveAll(preferGenres); - - messageService.sendDiscordWebhookMessage(DiscordWebhookMessage.of( - MessageFormatter.formatUserJoinMessage(user, SocialLoginType.fromSocialId(user.getSocialId())), JOIN)); - } - - public void logout(User user, LogoutRequest request) { - refreshTokenRepository.findByRefreshToken(request.refreshToken()) - .ifPresent(refreshTokenRepository::delete); - - userDeviceRepository.deleteByUserAndDeviceIdentifier(user, request.deviceIdentifier()); - - if (user.getSocialId().startsWith(KAKAO_PREFIX)) { - kakaoService.kakaoLogout(user); - } - } - - public void withdrawUser(User user, WithdrawalRequest withdrawalRequest) { - unlinkSocialAccount(user); - - String messageContent = MessageFormatter.formatUserWithdrawMessage(user.getUserId(), user.getNickname(), - withdrawalRequest.reason()); - - cleanupUserData(user.getUserId()); - - messageService.sendDiscordWebhookMessage( - DiscordWebhookMessage.of(messageContent, WITHDRAW)); - - withdrawalReasonRepository.save(WithdrawalReason.create(withdrawalRequest.reason())); - } - private void checkNicknameIfAlreadyExist(String nickname) { if (userRepository.existsByNickname(nickname)) { throw new CustomUserException(DUPLICATED_NICKNAME, "nickname is duplicated."); @@ -235,21 +187,6 @@ private Genre findByGenreNameOrThrow(String genreName) { new CustomGenreException(GENRE_NOT_FOUND, "genre with the given genreName is not found")); } - private void unlinkSocialAccount(User user) { - if (user.getSocialId().startsWith(KAKAO_PREFIX)) { - kakaoService.unlinkFromKakao(user); - } else if (user.getSocialId().startsWith(APPLE_PREFIX)) { - appleService.unlinkFromApple(user); - } - } - - private void cleanupUserData(Long userId) { - refreshTokenRepository.deleteAll(refreshTokenRepository.findAllByUserId(userId)); - feedRepository.updateUserToUnknown(userId); - commentRepository.updateUserToUnknown(userId); - userRepository.deleteById(userId); - } - public void editMyInfo(User user, EditMyInfoRequest editMyInfoRequest) { user.editMyInfo(editMyInfoRequest); } diff --git a/src/main/java/org/websoso/WSSServer/validation/UserIdValidator.java b/src/main/java/org/websoso/WSSServer/validation/UserIdValidator.java index 612691e1b..ece33c63d 100644 --- a/src/main/java/org/websoso/WSSServer/validation/UserIdValidator.java +++ b/src/main/java/org/websoso/WSSServer/validation/UserIdValidator.java @@ -8,7 +8,7 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; import org.websoso.WSSServer.exception.exception.CustomUserException; -import org.websoso.WSSServer.repository.UserRepository; +import org.websoso.WSSServer.user.repository.UserRepository; @Component @AllArgsConstructor