From d474d8d2fae85226c188b20f5fdf148d78ad18fa Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Thu, 18 Sep 2025 20:30:03 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat=20:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeNotificationListener.java | 36 ++++++ .../NotificationCommandService.java | 20 ++++ .../NotificationContentAssembler.java | 51 +++++++++ .../application/NotificationQueryService.java | 24 ++++ .../application/dto/CommentLikedContent.java | 20 ++++ .../application/dto/NotificationContent.java | 13 +++ .../application/port/PostReadPort.java | 8 ++ .../application/port/UserReadPort.java | 9 ++ .../application/port/view/PostView.java | 6 + .../application/port/view/UserView.java | 7 ++ .../notification/domain/Notification.java | 103 ++++++++++++++++++ .../domain/NotificationQueryRepository.java | 10 ++ .../domain/NotificationRepository.java | 7 ++ .../notification/domain/NotificationType.java | 7 ++ .../com/chooz/notification/domain/Target.java | 23 ++++ .../chooz/notification/domain/TargetType.java | 7 ++ .../domain/event/CommentLikedEvent.java | 11 ++ .../adapter/PostReadAdapter.java | 19 ++++ .../adapter/PostViewRepository.java | 18 +++ .../adapter/UserReadAdapter.java | 27 +++++ .../adapter/UserViewRepository.java | 25 +++++ .../NotificationJpaRepository.java | 27 +++++ .../NotificationQueryRepositoryImpl.java | 20 ++++ .../NotificationRepositoryImpl.java | 20 ++++ .../presentation/NotificationController.java | 30 +++++ .../dto/NotificationResponse.java | 43 ++++++++ 26 files changed, 591 insertions(+) create mode 100644 src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java create mode 100644 src/main/java/com/chooz/notification/application/NotificationCommandService.java create mode 100644 src/main/java/com/chooz/notification/application/NotificationContentAssembler.java create mode 100644 src/main/java/com/chooz/notification/application/NotificationQueryService.java create mode 100644 src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java create mode 100644 src/main/java/com/chooz/notification/application/dto/NotificationContent.java create mode 100644 src/main/java/com/chooz/notification/application/port/PostReadPort.java create mode 100644 src/main/java/com/chooz/notification/application/port/UserReadPort.java create mode 100644 src/main/java/com/chooz/notification/application/port/view/PostView.java create mode 100644 src/main/java/com/chooz/notification/application/port/view/UserView.java create mode 100644 src/main/java/com/chooz/notification/domain/Notification.java create mode 100644 src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java create mode 100644 src/main/java/com/chooz/notification/domain/NotificationRepository.java create mode 100644 src/main/java/com/chooz/notification/domain/NotificationType.java create mode 100644 src/main/java/com/chooz/notification/domain/Target.java create mode 100644 src/main/java/com/chooz/notification/domain/TargetType.java create mode 100644 src/main/java/com/chooz/notification/domain/event/CommentLikedEvent.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java create mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java create mode 100644 src/main/java/com/chooz/notification/presentation/NotificationController.java create mode 100644 src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java diff --git a/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java b/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java new file mode 100644 index 0000000..6d176f8 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java @@ -0,0 +1,36 @@ +package com.chooz.notification.application; + +import com.chooz.notification.application.dto.CommentLikedContent; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.TargetType; +import com.chooz.notification.domain.event.CommentLikedEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class CommentLikeNotificationListener { + + private final NotificationCommandService notificationCommandService; + private final NotificationContentAssembler notificationContentAssembler; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onCommentLiked(CommentLikedEvent e) { + CommentLikedContent commentLikedContent = notificationContentAssembler.forCommentLiked(e.commentId(), e.likerId()); + Notification.create( + commentLikedContent.getCommentAuthorId(), + e.likerId(), + NotificationType.COMMENT_LIKED, + TargetType.COMMENT, + e.commentId(), + commentLikedContent.getTitle(), + commentLikedContent.getBody(), + commentLikedContent.getThumbnailUrl(), + commentLikedContent.getProfileImageUrl(), + e.eventAt() + ).ifPresent(notificationCommandService::create); + } +} diff --git a/src/main/java/com/chooz/notification/application/NotificationCommandService.java b/src/main/java/com/chooz/notification/application/NotificationCommandService.java new file mode 100644 index 0000000..6818ade --- /dev/null +++ b/src/main/java/com/chooz/notification/application/NotificationCommandService.java @@ -0,0 +1,20 @@ +package com.chooz.notification.application; + +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class NotificationCommandService { + + private final NotificationRepository notificationRepository; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Notification create(Notification notification) { + return notificationRepository.save(notification); + } +} diff --git a/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java b/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java new file mode 100644 index 0000000..c16b382 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java @@ -0,0 +1,51 @@ +package com.chooz.notification.application; + +import com.chooz.common.exception.BadRequestException; +import com.chooz.common.exception.ErrorCode; +import com.chooz.notification.application.dto.CommentLikedContent; +import com.chooz.notification.application.port.PostReadPort; +import com.chooz.notification.application.port.UserReadPort; +import com.chooz.notification.application.port.view.PostView; +import com.chooz.notification.application.port.view.UserView; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NotificationContentAssembler { + + private final UserReadPort userReadPort; + private final PostReadPort postReadPort; + + public CommentLikedContent forCommentLiked(Long commentId, Long likerId) { + UserView likerUserView = userReadPort.getUser(likerId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + UserView commentAuthorView = userReadPort.getUserByCommentId(commentId) + .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); + PostView postView = postReadPort.getPost(commentId) + .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); + + return new CommentLikedContent( + likerUserView.nickname() + " 님이 당신의 댓글에 좋아요를 눌렀어요!", + "지금 확인해보세요.", + postView.imageUrl(), + likerUserView.profileUrl(), + commentAuthorView.id() + ); + } + +// public NotificationContent forVoteClosed(Long postId) { +// String title = postPort.getPostTitle(postId).orElse("투표 마감"); +// String body = "참여한 투표가 마감되었어요."; +// String thumbnail = postPort.getPostThumbnailUrl(postId).orElse(null); +// return new NotificationContent(title, body, thumbnail); +// } +// +// public NotificationContent forPostParticipated(Long postId, Long voterId) { +// String title = postPort.getPostTitle(postId).orElse("새로운 참여"); +// String voter = userPort.getDisplayName(voterId).orElse("누군가"); +// String body = voter + "님이 내 투표에 참여했어요."; +// String thumbnail = userPort.getAvatarUrl(voterId).orElse(null); +// return new NotificationContent(title, body, thumbnail); +// } +} diff --git a/src/main/java/com/chooz/notification/application/NotificationQueryService.java b/src/main/java/com/chooz/notification/application/NotificationQueryService.java new file mode 100644 index 0000000..094161a --- /dev/null +++ b/src/main/java/com/chooz/notification/application/NotificationQueryService.java @@ -0,0 +1,24 @@ +package com.chooz.notification.application; + +import com.chooz.common.dto.CursorBasePaginatedResponse; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationQueryRepository; +import com.chooz.notification.presentation.dto.NotificationResponse; +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; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class NotificationQueryService { + + private final NotificationQueryRepository notificationQueryRepository; + + public CursorBasePaginatedResponse findNotifications(Long userId, Long cursor, int size) { + Slice notificationSlice = notificationQueryRepository.findNotifications(userId, cursor, PageRequest.ofSize(size)); + return CursorBasePaginatedResponse.of(notificationSlice.map(NotificationResponse::of)); + } +} diff --git a/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java b/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java new file mode 100644 index 0000000..ed64266 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java @@ -0,0 +1,20 @@ +package com.chooz.notification.application.dto; + +import lombok.Getter; + +@Getter +public class CommentLikedContent extends NotificationContent { + + private final Long commentAuthorId; + + public CommentLikedContent( + String title, + String body, + String thumbnailUrl, + String profileImageUrl, + Long commentAuthorId + ) { + super(title, body, thumbnailUrl, profileImageUrl); + this.commentAuthorId = commentAuthorId; + } +} diff --git a/src/main/java/com/chooz/notification/application/dto/NotificationContent.java b/src/main/java/com/chooz/notification/application/dto/NotificationContent.java new file mode 100644 index 0000000..406e2d8 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/dto/NotificationContent.java @@ -0,0 +1,13 @@ +package com.chooz.notification.application.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public abstract class NotificationContent { + private final String title; + private final String body; + private final String thumbnailUrl; + private final String profileImageUrl; +} diff --git a/src/main/java/com/chooz/notification/application/port/PostReadPort.java b/src/main/java/com/chooz/notification/application/port/PostReadPort.java new file mode 100644 index 0000000..cbec683 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/port/PostReadPort.java @@ -0,0 +1,8 @@ +package com.chooz.notification.application.port; + +import com.chooz.notification.application.port.view.PostView; +import java.util.Optional; + +public interface PostReadPort { + Optional getPost(Long commentId); +} diff --git a/src/main/java/com/chooz/notification/application/port/UserReadPort.java b/src/main/java/com/chooz/notification/application/port/UserReadPort.java new file mode 100644 index 0000000..eefae76 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/port/UserReadPort.java @@ -0,0 +1,9 @@ +package com.chooz.notification.application.port; + +import com.chooz.notification.application.port.view.UserView; +import java.util.Optional; + +public interface UserReadPort { + Optional getUserByCommentId(Long commentId); + Optional getUser(Long userId); +} diff --git a/src/main/java/com/chooz/notification/application/port/view/PostView.java b/src/main/java/com/chooz/notification/application/port/view/PostView.java new file mode 100644 index 0000000..395c98d --- /dev/null +++ b/src/main/java/com/chooz/notification/application/port/view/PostView.java @@ -0,0 +1,6 @@ +package com.chooz.notification.application.port.view; + +public record PostView( + Long id, + String imageUrl +) {} diff --git a/src/main/java/com/chooz/notification/application/port/view/UserView.java b/src/main/java/com/chooz/notification/application/port/view/UserView.java new file mode 100644 index 0000000..ca5d8b5 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/port/view/UserView.java @@ -0,0 +1,7 @@ +package com.chooz.notification.application.port.view; + +public record UserView( + Long id, + String nickname, + String profileUrl +) {} diff --git a/src/main/java/com/chooz/notification/domain/Notification.java b/src/main/java/com/chooz/notification/domain/Notification.java new file mode 100644 index 0000000..3dba137 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/Notification.java @@ -0,0 +1,103 @@ +package com.chooz.notification.domain; + +import com.chooz.common.domain.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Getter +@Entity +@Table(name = "notifications") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +public class Notification extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "actor_id") + private Long actorId; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private NotificationType type; + + @Embedded + private Target target; + + @Column(name = "title") + private String title; + + @Column(name = "body", nullable = false) + private String body; + + @Column(name = "thumb_url", length = 255) + private String thumbUrl; + + @Column(name = "profile_image_url", length = 255) + private String profileImageUrl; + + @Column(name = "is_read", nullable = false) + private boolean isRead; + + @Column(name = "event_at", nullable = false) + private LocalDateTime eventAt; + + public static Optional create( + Long userId, + Long actorId, + NotificationType type, + TargetType targetType, + Long targetId, + String title, + String body, + String thumbUrl, + String profileImageUrl, + LocalDateTime eventAt + ) { + if (checkMine(actorId, userId)) { + return Optional.empty(); + } + return Optional.of(Notification.builder() + .userId(userId) + .actorId(actorId) + .type(type) + .target(new Target(targetType, targetId)) + .title(title) + .body(body) + .thumbUrl(thumbUrl) + .profileImageUrl(profileImageUrl) + .isRead(false) + .eventAt(eventAt) + .build()); + } + private static boolean checkMine(Long actorId, Long userId) { + return actorId != null && actorId.equals(userId); + } + + public void markRead() { + if (!isRead) { + this.isRead = true; + } + } +} diff --git a/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java b/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java new file mode 100644 index 0000000..2c364db --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java @@ -0,0 +1,10 @@ +package com.chooz.notification.domain; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +import java.util.Optional; + +public interface NotificationQueryRepository { + Slice findNotifications(Long userId, Long cursor, Pageable pageable); +} diff --git a/src/main/java/com/chooz/notification/domain/NotificationRepository.java b/src/main/java/com/chooz/notification/domain/NotificationRepository.java new file mode 100644 index 0000000..c4235d0 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/NotificationRepository.java @@ -0,0 +1,7 @@ +package com.chooz.notification.domain; + +import java.util.Optional; + +public interface NotificationRepository { + Notification save(Notification notification); +} diff --git a/src/main/java/com/chooz/notification/domain/NotificationType.java b/src/main/java/com/chooz/notification/domain/NotificationType.java new file mode 100644 index 0000000..31b83c2 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/NotificationType.java @@ -0,0 +1,7 @@ +package com.chooz.notification.domain; + +public enum NotificationType { + POST_CLOSED, + COMMENT_LIKED, + VOTE_PARTICIPATED +} diff --git a/src/main/java/com/chooz/notification/domain/Target.java b/src/main/java/com/chooz/notification/domain/Target.java new file mode 100644 index 0000000..0a76287 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/Target.java @@ -0,0 +1,23 @@ +package com.chooz.notification.domain; + + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Target { + @Enumerated(EnumType.STRING) + @Column(name = "target_type", nullable = false) + private TargetType type; + + @Column(name = "target_id", nullable = false) + private Long id; +} diff --git a/src/main/java/com/chooz/notification/domain/TargetType.java b/src/main/java/com/chooz/notification/domain/TargetType.java new file mode 100644 index 0000000..87f5a68 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/TargetType.java @@ -0,0 +1,7 @@ +package com.chooz.notification.domain; + +public enum TargetType { + POST, + COMMENT, + VOTE, +} diff --git a/src/main/java/com/chooz/notification/domain/event/CommentLikedEvent.java b/src/main/java/com/chooz/notification/domain/event/CommentLikedEvent.java new file mode 100644 index 0000000..857de50 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/event/CommentLikedEvent.java @@ -0,0 +1,11 @@ +package com.chooz.notification.domain.event; + +import java.time.LocalDateTime; + +public record CommentLikedEvent( + Long commentId, + Long commentLikeId, + Long likerId, + LocalDateTime eventAt +) {} + diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java b/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java new file mode 100644 index 0000000..3774f68 --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java @@ -0,0 +1,19 @@ +package com.chooz.notification.infrastructure.adapter; + +import com.chooz.notification.application.port.PostReadPort; +import com.chooz.notification.application.port.view.PostView; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class PostReadAdapter implements PostReadPort { + + private final PostViewRepository postViewRepository; + + @Override + public Optional getPost(Long commentId) { + return postViewRepository.findViewById(commentId); + } +} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java b/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java new file mode 100644 index 0000000..ac9dc7f --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java @@ -0,0 +1,18 @@ +package com.chooz.notification.infrastructure.adapter; + +import com.chooz.notification.application.port.view.PostView; +import com.chooz.user.domain.User; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +import java.util.Optional; + +interface PostViewRepository extends Repository { + @Query(""" + select new com.chooz.notification.application.port.view.PostView(p.id, p.imageUrl) + from Comment c + join Post p on p.id = c.postId + where c.id = :commentId + """) + Optional findViewById(Long commentId); +} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java b/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java new file mode 100644 index 0000000..03a9514 --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java @@ -0,0 +1,27 @@ +package com.chooz.notification.infrastructure.adapter; + +import com.chooz.notification.application.port.UserReadPort; +import com.chooz.notification.application.port.view.UserView; +import com.chooz.user.domain.User; +import com.chooz.user.domain.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class UserReadAdapter implements UserReadPort { + + private final UserViewRepository userViewRepository; + + @Override + public Optional getUserByCommentId(Long commentId) { + return userViewRepository.findViewByCommentId(commentId); + } + + @Override + public Optional getUser(Long commentId) { + return userViewRepository.findViewById(commentId); + } +} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java b/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java new file mode 100644 index 0000000..a0d429d --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java @@ -0,0 +1,25 @@ +package com.chooz.notification.infrastructure.adapter; + +import com.chooz.notification.application.port.view.UserView; +import com.chooz.user.domain.User; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; + +import java.util.Optional; + +interface UserViewRepository extends Repository { + @Query(""" + select new com.chooz.notification.application.port.view.UserView(u.id, u.nickname, u.profileUrl) + from Comment c + join User u on u.id = c.userId + where c.id = :commentId + """) + Optional findViewByCommentId(Long commentId); + + @Query(""" + select new com.chooz.notification.application.port.view.UserView(u.id, u.nickname, u.profileUrl) + from User u + where u.id = :userId + """) + Optional findViewById(Long userId); +} diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java new file mode 100644 index 0000000..5fa3b4f --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java @@ -0,0 +1,27 @@ +package com.chooz.notification.infrastructure.persistence; + +import com.chooz.notification.domain.Notification; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface NotificationJpaRepository extends JpaRepository { + + @Query(""" + SELECT n + FROM Notification n + WHERE n.userId = :userId + AND (:cursor is null OR n.id < :cursor) + ORDER BY + n.id DESC + """) + Slice findByUserId( + @Param("userId") Long userId, + @Param("cursor") Long cursor, + Pageable pageable + ); +} diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java new file mode 100644 index 0000000..aada614 --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java @@ -0,0 +1,20 @@ +package com.chooz.notification.infrastructure.persistence; + +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class NotificationQueryRepositoryImpl implements NotificationQueryRepository { + + private final NotificationJpaRepository notificationJpaRepository; + + @Override + public Slice findNotifications(Long userId, Long cursor, Pageable pageable) { + return notificationJpaRepository.findByUserId(userId, cursor, pageable); + } +} \ No newline at end of file diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java new file mode 100644 index 0000000..ac37b41 --- /dev/null +++ b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java @@ -0,0 +1,20 @@ +package com.chooz.notification.infrastructure.persistence; + +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + + +@Repository +@RequiredArgsConstructor +public class NotificationRepositoryImpl implements NotificationRepository { + + private final NotificationJpaRepository notificationJpaRepository; + + @Override + public Notification save(Notification notification) { + return notificationJpaRepository.save(notification); + } + +} \ No newline at end of file diff --git a/src/main/java/com/chooz/notification/presentation/NotificationController.java b/src/main/java/com/chooz/notification/presentation/NotificationController.java new file mode 100644 index 0000000..7a37f65 --- /dev/null +++ b/src/main/java/com/chooz/notification/presentation/NotificationController.java @@ -0,0 +1,30 @@ +package com.chooz.notification.presentation; + +import com.chooz.auth.domain.UserInfo; +import com.chooz.common.dto.CursorBasePaginatedResponse; +import com.chooz.notification.application.NotificationQueryService; +import com.chooz.notification.presentation.dto.NotificationResponse; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/notifications") +public class NotificationController { + private final NotificationQueryService notificationQueryService; + + @GetMapping("") + public ResponseEntity> findNotifications( + @RequestParam(name = "cursor", required = false) @Min(0) Long cursor, + @RequestParam(name = "size", required = false, defaultValue = "10") @Min(1) int size, + @AuthenticationPrincipal UserInfo userInfo + ) { + return ResponseEntity.ok(notificationQueryService.findNotifications(userInfo.userId(), cursor, size)); + } +} diff --git a/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java b/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java new file mode 100644 index 0000000..fdf95ad --- /dev/null +++ b/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java @@ -0,0 +1,43 @@ +package com.chooz.notification.presentation.dto; + +import com.chooz.common.dto.CursorDto; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.Target; +import com.chooz.notification.domain.TargetType; + +import java.time.LocalDateTime; + +public record NotificationResponse ( + Long id, + Long userId, + Long actorId, + NotificationType type, + Target target, + String title, + String body, + String thumbUrl, + String profileImageUrl, + boolean isRead, + LocalDateTime eventAt +)implements CursorDto{ + + public static NotificationResponse of (Notification notification){ + return new NotificationResponse( + notification.getId(), + notification.getUserId(), + notification.getActorId(), + notification.getType(), + notification.getTarget(), + notification.getTitle(), + notification.getBody(), + notification.getThumbUrl(), + notification.getProfileImageUrl(), + notification.isRead(), + notification.getEventAt() + ); + } + + @Override + public long getId() { return this.id; } +} From 3d3472c1e6ac9dd11f77d049b8a9d1563f41cfd2 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Thu, 18 Sep 2025 20:31:36 +0900 Subject: [PATCH 2/9] =?UTF-8?q?test=20:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=95=8C=EB=A6=BC=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeNotificationListenerTest.java | 77 ++++++++++++++++ .../notification/domain/NotificationTest.java | 90 +++++++++++++++++++ .../NotificationControllerTest.java | 83 +++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java create mode 100644 src/test/java/com/chooz/notification/domain/NotificationTest.java create mode 100644 src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java diff --git a/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java new file mode 100644 index 0000000..33e9ffb --- /dev/null +++ b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java @@ -0,0 +1,77 @@ +package com.chooz.notification.application; + +import com.chooz.comment.domain.Comment; +import com.chooz.comment.domain.CommentRepository; +import com.chooz.commentLike.application.CommentLikeService; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationQueryRepository; +import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.TargetType; +import com.chooz.post.domain.Post; +import com.chooz.post.domain.PostRepository; +import com.chooz.support.IntegrationTest; +import com.chooz.support.fixture.CommentFixture; +import com.chooz.support.fixture.PostFixture; +import com.chooz.support.fixture.UserFixture; +import com.chooz.user.domain.User; +import com.chooz.user.domain.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.test.context.transaction.TestTransaction; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class CommentLikeNotificationListenerTest extends IntegrationTest { + + @Autowired + UserRepository userRepository; + + @Autowired + PostRepository postRepository; + + @Autowired + CommentRepository commentRepository; + + @Autowired + NotificationQueryRepository notificationQueryRepository; + + @Autowired + CommentLikeService commentLikeService; + + @Test + @DisplayName("댓글좋아요 알림") + void onCommentLiked() throws Exception { + //given + User receiver = userRepository.save(UserFixture.createDefaultUser()); + User actor = userRepository.save(UserFixture.createDefaultUser()); + Post post = postRepository.save(PostFixture.createPostBuilder().userId(receiver.getId()).build()); + Comment comment = commentRepository.save(CommentFixture.createCommentBuilder() + .userId(receiver.getId()) + .postId(post.getId()) + .build()); + + //when + commentLikeService.createCommentLike(comment.getId(), actor.getId()); + TestTransaction.flagForCommit(); + TestTransaction.end(); + + //then + Slice notificationSlice = notificationQueryRepository.findNotifications( + receiver.getId(), + null, + PageRequest.ofSize(10)); + + assertAll( + () -> assertThat(notificationSlice.getContent().size()).isEqualTo(1), + () -> assertThat(notificationSlice.getContent().getFirst().getUserId()).isEqualTo(receiver.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().getActorId()).isEqualTo(actor.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().getTarget().getType()).isEqualTo(TargetType.COMMENT), + () -> assertThat(notificationSlice.getContent().getFirst().getTarget().getId()).isEqualTo(comment.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().getType()).isEqualTo(NotificationType.COMMENT_LIKED) + ); + } +} diff --git a/src/test/java/com/chooz/notification/domain/NotificationTest.java b/src/test/java/com/chooz/notification/domain/NotificationTest.java new file mode 100644 index 0000000..5194bd9 --- /dev/null +++ b/src/test/java/com/chooz/notification/domain/NotificationTest.java @@ -0,0 +1,90 @@ +package com.chooz.notification.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class NotificationTest { + + @Test + @DisplayName("알림 생성") + void create() throws Exception { + //given + Long userId = 1L; + Long actorId = 2L; + NotificationType type = NotificationType.COMMENT_LIKED; + TargetType targetType = TargetType.COMMENT; + Long targetId = 3L; + String title = "숨겨진 츄님이 당신의 댓글에 좋아요를 눌렀습니다."; + String body = "지금 바로 확인해보세요!"; + String thumbUrl = "https://cdn.chooz.site/thumbnail.png"; + String profileImageUrl = "https://cdn.chooz.site/default_profile.png"; + LocalDateTime eventAt = LocalDateTime.now(); + //when + Notification notification = Notification.create( + userId, + actorId, + type, + targetType, + targetId, + title, + body, + thumbUrl, + profileImageUrl, + eventAt + ).get(); + + //then + assertAll( + () -> assertThat(notification.getUserId()).isEqualTo(userId), + () -> assertThat(notification.getActorId()).isEqualTo(actorId), + () -> assertThat(notification.getType()).isEqualTo(type), + () -> assertThat(notification.getTarget().getId()).isEqualTo(targetId), + () -> assertThat(notification.getTarget().getType()).isEqualTo(targetType), + () -> assertThat(notification.getTitle()).isEqualTo(title), + () -> assertThat(notification.getBody()).isEqualTo(body), + () -> assertThat(notification.getThumbUrl()).isEqualTo(thumbUrl), + () -> assertThat(notification.getProfileImageUrl()).isEqualTo(profileImageUrl), + () -> assertThat(notification.getEventAt()).isEqualTo(eventAt) + ); + } + @Test + @DisplayName("알림 읽음 확인") + void markRead() throws Exception { + //given + Long userId = 1L; + Long actorId = 2L; + NotificationType type = NotificationType.COMMENT_LIKED; + TargetType targetType = TargetType.COMMENT; + Long targetId = 3L; + String title = "숨겨진 츄님이 당신의 댓글에 좋아요를 눌렀습니다."; + String body = "지금 바로 확인해보세요!"; + String thumbUrl = "https://cdn.chooz.site/thumbnail.png"; + String profileImageUrl = "https://cdn.chooz.site/default_profile.png"; + LocalDateTime eventAt = LocalDateTime.now(); + //when + Notification notification = Notification.create( + userId, + actorId, + type, + targetType, + targetId, + title, + body, + thumbUrl, + profileImageUrl, + eventAt + ).get(); + + notification.markRead(); + + //then + assertAll( + () -> assertThat(notification.isRead()).isTrue() + ); + } +} diff --git a/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java b/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java new file mode 100644 index 0000000..f8d98a7 --- /dev/null +++ b/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java @@ -0,0 +1,83 @@ +package com.chooz.notification.presentation; + +import com.chooz.common.dto.CursorBasePaginatedResponse; +import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.Target; +import com.chooz.notification.domain.TargetType; +import com.chooz.notification.presentation.dto.NotificationResponse; +import com.chooz.support.RestDocsTest; +import com.chooz.support.WithMockUserInfo; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.JsonFieldType; +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class NotificationControllerTest extends RestDocsTest { + + @Test + @WithMockUserInfo + @DisplayName("알림 목록 조회") + void findNotifications() throws Exception { + //given + var response = new CursorBasePaginatedResponse<>( + 1L, + false, + List.of( + new NotificationResponse( + 1L, + 1L, + 3L, + NotificationType.COMMENT_LIKED, + new Target(TargetType.COMMENT, 4L), + "숨겨진 츄 님이 당신의 댓글에 좋아요를 눌렀어요!", + "지금 확인해보세요.", + "https://cdn.chooz.site/thumbnail.png", + "https://cdn.chooz.site/default_profile.png", + false, + LocalDateTime.now() + ) + ) + ); + given(notificationQueryService.findNotifications(1L, null, 10)).willReturn(response); + + //when then + mockMvc.perform(get("/notifications") + .header(HttpHeaders.AUTHORIZATION, "Bearer token")) + .andExpect(status().isOk()) + .andExpect(content().json(objectMapper.writeValueAsString(response))) + .andDo(restDocs.document( + requestHeaders(authorizationHeader()), + queryParameters(cursorQueryParams()), + responseFields( + fieldWithPath("nextCursor").type(JsonFieldType.NUMBER).optional().description("다음 조회 커서 값"), + fieldWithPath("hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부 (기본 값 10)"), + fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("알림 데이터"), + fieldWithPath("data[].id").type(JsonFieldType.NUMBER).description("알림 ID"), + fieldWithPath("data[].userId").type(JsonFieldType.NUMBER).description("알림 받는 유저 ID"), + fieldWithPath("data[].actorId").type(JsonFieldType.NUMBER).description("알림을 발생시킨 유저 ID"), + fieldWithPath("data[].type").type(JsonFieldType.STRING).description("알림 발생 유형"), + fieldWithPath("data[].target.type").type(JsonFieldType.STRING).description("알림 타겟 유형"), + fieldWithPath("data[].target.id").type(JsonFieldType.NUMBER).description("알림 타겟 ID"), + fieldWithPath("data[].title").type(JsonFieldType.STRING).description("알림 제목"), + fieldWithPath("data[].body").type(JsonFieldType.STRING).description("알림 내용"), + fieldWithPath("data[].thumbUrl").type(JsonFieldType.STRING).description("썸네일 이미지 url"), + fieldWithPath("data[].profileImageUrl").type(JsonFieldType.STRING).description("알림을 발생시킨 유저 썸네일 url"), + fieldWithPath("data[].isRead").type(JsonFieldType.BOOLEAN).description("읽음 여부"), + fieldWithPath("data[].eventAt").type(JsonFieldType.STRING).description("이벤트 발생 시간") + ) + )); + } +} From 4b4ceb0cc700cd7dc679acb605ea85f225fb35ce Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Thu, 18 Sep 2025 20:32:18 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat=20:=20=EB=8C=93=EA=B8=80=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentLikeCommandService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/chooz/commentLike/application/CommentLikeCommandService.java b/src/main/java/com/chooz/commentLike/application/CommentLikeCommandService.java index f356d69..0c88522 100644 --- a/src/main/java/com/chooz/commentLike/application/CommentLikeCommandService.java +++ b/src/main/java/com/chooz/commentLike/application/CommentLikeCommandService.java @@ -3,13 +3,17 @@ import com.chooz.commentLike.domain.CommentLike; import com.chooz.commentLike.domain.CommentLikeRepository; import com.chooz.commentLike.presentation.dto.CommentLikeIdResponse; +import com.chooz.common.event.EventPublisher; import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; +import com.chooz.notification.domain.event.CommentLikedEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -17,14 +21,22 @@ public class CommentLikeCommandService { private final CommentLikeRepository commentLikeRepository; + private final EventPublisher eventPublisher; public CommentLikeIdResponse createCommentLike(Long commentId, Long userId) { if(commentLikeRepository.existsByCommentIdAndUserId(commentId, userId)){ throw new BadRequestException(ErrorCode.COMMENT_LIKE_NOT_FOUND); } + CommentLike commentLike = commentLikeRepository.save(CommentLike.create(commentId, userId)); + eventPublisher.publish(new CommentLikedEvent( + commentId, + commentLike.getId(), + userId, + LocalDateTime.now() + )); return new CommentLikeIdResponse( - commentLikeRepository.save(CommentLike.create(commentId, userId)).getId(), + commentLike.getId(), commentLikeRepository.countByCommentId(commentId) ); } From f95186688043e0d84b0a92e4c6c6913af6f8a2e4 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Thu, 18 Sep 2025 20:33:45 +0900 Subject: [PATCH 4/9] =?UTF-8?q?test=20:=20test=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/com/chooz/support/WebUnitTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/chooz/support/WebUnitTest.java b/src/test/java/com/chooz/support/WebUnitTest.java index 1717393..d8c329c 100644 --- a/src/test/java/com/chooz/support/WebUnitTest.java +++ b/src/test/java/com/chooz/support/WebUnitTest.java @@ -1,6 +1,7 @@ package com.chooz.support; import com.chooz.image.application.ImageService; +import com.chooz.notification.application.NotificationQueryService; import com.fasterxml.jackson.databind.ObjectMapper; import com.chooz.auth.application.AuthService; import com.chooz.auth.presentation.RefreshTokenCookieGenerator; @@ -10,7 +11,6 @@ import com.chooz.post.application.PostService; import com.chooz.user.application.UserService; import com.chooz.vote.application.VoteService; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.annotation.Import; @@ -53,4 +53,7 @@ public abstract class WebUnitTest { @MockitoBean protected DiscordMessageSender discordMessageSender; + + @MockitoBean + protected NotificationQueryService notificationQueryService; } From 57fbd9ab3090be000a40af92a4c6a8e2e4bf4e22 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Thu, 18 Sep 2025 20:34:49 +0900 Subject: [PATCH 5/9] =?UTF-8?q?docs=20:=20restdocs=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 4 +++- src/docs/asciidoc/notifications.adoc | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 src/docs/asciidoc/notifications.adoc diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index b3be91b..4c59269 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -109,4 +109,6 @@ include::votes.adoc[] include::comments.adoc[] -include::comment-likes.adoc[] \ No newline at end of file +include::comment-likes.adoc[] + +include::notifications.adoc[] \ No newline at end of file diff --git a/src/docs/asciidoc/notifications.adoc b/src/docs/asciidoc/notifications.adoc new file mode 100644 index 0000000..dd84ba9 --- /dev/null +++ b/src/docs/asciidoc/notifications.adoc @@ -0,0 +1,7 @@ +[[알림-API]] +== 알림 API + +[[알림-조회]] +=== `GET` 알림 조회 + +operation::comment-controller-test/find-notifications[snippets='http-request,curl-request,path-parameters,request-headers,query-parameters,http-response,response-fields'] \ No newline at end of file From 48d708e9386e62254c253fa2835d48df5a71a75a Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sat, 20 Sep 2025 20:24:33 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor=20:=20=EC=97=94=ED=8B=B0=ED=8B=B0?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/chooz/notification/domain/Actor.java | 25 ++++++++ .../notification/domain/Notification.java | 57 ++++++------------- .../notification/domain/NotificationType.java | 7 --- .../chooz/notification/domain/Receiver.java | 23 ++++++++ .../com/chooz/notification/domain/Target.java | 7 ++- 5 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/chooz/notification/domain/Actor.java delete mode 100644 src/main/java/com/chooz/notification/domain/NotificationType.java create mode 100644 src/main/java/com/chooz/notification/domain/Receiver.java diff --git a/src/main/java/com/chooz/notification/domain/Actor.java b/src/main/java/com/chooz/notification/domain/Actor.java new file mode 100644 index 0000000..746ebc9 --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/Actor.java @@ -0,0 +1,25 @@ +package com.chooz.notification.domain; + + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Actor { + @Column(name = "actor_id", nullable = false) + private Long id; + + @Column(name = "actor_nickname", nullable = false) + private String nickname; + + @Column(name = "actor_profile_url", nullable = false) + private String profileUrl; +} diff --git a/src/main/java/com/chooz/notification/domain/Notification.java b/src/main/java/com/chooz/notification/domain/Notification.java index 3dba137..b9820f3 100644 --- a/src/main/java/com/chooz/notification/domain/Notification.java +++ b/src/main/java/com/chooz/notification/domain/Notification.java @@ -4,12 +4,9 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.Index; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -32,31 +29,15 @@ public class Notification extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "user_id", nullable = false) - private Long userId; - - @Column(name = "actor_id") - private Long actorId; + @Embedded + private Receiver receiver; - @Enumerated(EnumType.STRING) - @Column(name = "type", nullable = false) - private NotificationType type; + @Embedded + private Actor actor; @Embedded private Target target; - @Column(name = "title") - private String title; - - @Column(name = "body", nullable = false) - private String body; - - @Column(name = "thumb_url", length = 255) - private String thumbUrl; - - @Column(name = "profile_image_url", length = 255) - private String profileImageUrl; - @Column(name = "is_read", nullable = false) private boolean isRead; @@ -64,35 +45,29 @@ public class Notification extends BaseEntity { private LocalDateTime eventAt; public static Optional create( - Long userId, + Long receiverId, + String receiverNickname, Long actorId, - NotificationType type, - TargetType targetType, + String actorNickname, + String actorProfileUrl, Long targetId, - String title, - String body, - String thumbUrl, - String profileImageUrl, + TargetType targetType, + String targetImageUrl, LocalDateTime eventAt ) { - if (checkMine(actorId, userId)) { + if (checkMine(actorId, receiverId)) { return Optional.empty(); } return Optional.of(Notification.builder() - .userId(userId) - .actorId(actorId) - .type(type) - .target(new Target(targetType, targetId)) - .title(title) - .body(body) - .thumbUrl(thumbUrl) - .profileImageUrl(profileImageUrl) + .receiver(new Receiver(receiverId, receiverNickname)) + .actor(new Actor(actorId, actorNickname, actorProfileUrl)) + .target(new Target(targetId, targetType, targetImageUrl)) .isRead(false) .eventAt(eventAt) .build()); } - private static boolean checkMine(Long actorId, Long userId) { - return actorId != null && actorId.equals(userId); + private static boolean checkMine(Long actorId, Long receiverId) { + return actorId != null && actorId.equals(receiverId); } public void markRead() { diff --git a/src/main/java/com/chooz/notification/domain/NotificationType.java b/src/main/java/com/chooz/notification/domain/NotificationType.java deleted file mode 100644 index 31b83c2..0000000 --- a/src/main/java/com/chooz/notification/domain/NotificationType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.chooz.notification.domain; - -public enum NotificationType { - POST_CLOSED, - COMMENT_LIKED, - VOTE_PARTICIPATED -} diff --git a/src/main/java/com/chooz/notification/domain/Receiver.java b/src/main/java/com/chooz/notification/domain/Receiver.java new file mode 100644 index 0000000..6a02e8d --- /dev/null +++ b/src/main/java/com/chooz/notification/domain/Receiver.java @@ -0,0 +1,23 @@ +package com.chooz.notification.domain; + + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class Receiver { + @Column(name = "receiver_id", nullable = false) + private Long id; + + @Column(name = "receiver_nickname", nullable = false) + private String nickname; + +} diff --git a/src/main/java/com/chooz/notification/domain/Target.java b/src/main/java/com/chooz/notification/domain/Target.java index 0a76287..b4aae82 100644 --- a/src/main/java/com/chooz/notification/domain/Target.java +++ b/src/main/java/com/chooz/notification/domain/Target.java @@ -14,10 +14,13 @@ @NoArgsConstructor @AllArgsConstructor public class Target { + @Column(name = "target_id", nullable = false) + private Long id; + @Enumerated(EnumType.STRING) @Column(name = "target_type", nullable = false) private TargetType type; - @Column(name = "target_id", nullable = false) - private Long id; + @Column(name = "target_image_url", nullable = false) + private String imageUrl; } From d78452fceed51e2ea5639ae4b189e95ed17583a1 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sat, 20 Sep 2025 20:31:04 +0900 Subject: [PATCH 7/9] =?UTF-8?q?refactor=20:=20view=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20QDSL=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/TargetPostDto.java | 10 ++ .../application/dto/TargetUserDto.java | 10 ++ .../application/port/PostReadPort.java | 8 -- .../application/port/UserReadPort.java | 9 -- .../application/port/view/PostView.java | 6 -- .../application/port/view/UserView.java | 7 -- .../adapter/PostReadAdapter.java | 19 ---- .../adapter/PostViewRepository.java | 18 ---- .../adapter/UserReadAdapter.java | 27 ------ .../adapter/UserViewRepository.java | 25 ----- .../NotificationJpaRepository.java | 27 ++++++ .../NotificationQueryDslRepository.java | 94 +++++++++++++++++++ .../NotificationQueryRepositoryImpl.java | 47 ++++++++++ .../NotificationRepositoryImpl.java | 20 ++++ 14 files changed, 208 insertions(+), 119 deletions(-) create mode 100644 src/main/java/com/chooz/notification/application/dto/TargetPostDto.java create mode 100644 src/main/java/com/chooz/notification/application/dto/TargetUserDto.java delete mode 100644 src/main/java/com/chooz/notification/application/port/PostReadPort.java delete mode 100644 src/main/java/com/chooz/notification/application/port/UserReadPort.java delete mode 100644 src/main/java/com/chooz/notification/application/port/view/PostView.java delete mode 100644 src/main/java/com/chooz/notification/application/port/view/UserView.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java create mode 100644 src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java create mode 100644 src/main/java/com/chooz/notification/persistence/NotificationQueryDslRepository.java create mode 100644 src/main/java/com/chooz/notification/persistence/NotificationQueryRepositoryImpl.java create mode 100644 src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java diff --git a/src/main/java/com/chooz/notification/application/dto/TargetPostDto.java b/src/main/java/com/chooz/notification/application/dto/TargetPostDto.java new file mode 100644 index 0000000..1728347 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/dto/TargetPostDto.java @@ -0,0 +1,10 @@ +package com.chooz.notification.application.dto; + + +import com.querydsl.core.annotations.QueryProjection; + +@QueryProjection +public record TargetPostDto( + Long id, + String imageUrl +) {} diff --git a/src/main/java/com/chooz/notification/application/dto/TargetUserDto.java b/src/main/java/com/chooz/notification/application/dto/TargetUserDto.java new file mode 100644 index 0000000..285c4c6 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/dto/TargetUserDto.java @@ -0,0 +1,10 @@ +package com.chooz.notification.application.dto; + +import com.querydsl.core.annotations.QueryProjection; + +@QueryProjection +public record TargetUserDto( + Long id, + String nickname, + String profileUrl +) {} diff --git a/src/main/java/com/chooz/notification/application/port/PostReadPort.java b/src/main/java/com/chooz/notification/application/port/PostReadPort.java deleted file mode 100644 index cbec683..0000000 --- a/src/main/java/com/chooz/notification/application/port/PostReadPort.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.chooz.notification.application.port; - -import com.chooz.notification.application.port.view.PostView; -import java.util.Optional; - -public interface PostReadPort { - Optional getPost(Long commentId); -} diff --git a/src/main/java/com/chooz/notification/application/port/UserReadPort.java b/src/main/java/com/chooz/notification/application/port/UserReadPort.java deleted file mode 100644 index eefae76..0000000 --- a/src/main/java/com/chooz/notification/application/port/UserReadPort.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.chooz.notification.application.port; - -import com.chooz.notification.application.port.view.UserView; -import java.util.Optional; - -public interface UserReadPort { - Optional getUserByCommentId(Long commentId); - Optional getUser(Long userId); -} diff --git a/src/main/java/com/chooz/notification/application/port/view/PostView.java b/src/main/java/com/chooz/notification/application/port/view/PostView.java deleted file mode 100644 index 395c98d..0000000 --- a/src/main/java/com/chooz/notification/application/port/view/PostView.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.chooz.notification.application.port.view; - -public record PostView( - Long id, - String imageUrl -) {} diff --git a/src/main/java/com/chooz/notification/application/port/view/UserView.java b/src/main/java/com/chooz/notification/application/port/view/UserView.java deleted file mode 100644 index ca5d8b5..0000000 --- a/src/main/java/com/chooz/notification/application/port/view/UserView.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.chooz.notification.application.port.view; - -public record UserView( - Long id, - String nickname, - String profileUrl -) {} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java b/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java deleted file mode 100644 index 3774f68..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/adapter/PostReadAdapter.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.chooz.notification.infrastructure.adapter; - -import com.chooz.notification.application.port.PostReadPort; -import com.chooz.notification.application.port.view.PostView; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import java.util.Optional; - -@Component -@RequiredArgsConstructor -public class PostReadAdapter implements PostReadPort { - - private final PostViewRepository postViewRepository; - - @Override - public Optional getPost(Long commentId) { - return postViewRepository.findViewById(commentId); - } -} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java b/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java deleted file mode 100644 index ac9dc7f..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/adapter/PostViewRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.chooz.notification.infrastructure.adapter; - -import com.chooz.notification.application.port.view.PostView; -import com.chooz.user.domain.User; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; - -import java.util.Optional; - -interface PostViewRepository extends Repository { - @Query(""" - select new com.chooz.notification.application.port.view.PostView(p.id, p.imageUrl) - from Comment c - join Post p on p.id = c.postId - where c.id = :commentId - """) - Optional findViewById(Long commentId); -} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java b/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java deleted file mode 100644 index 03a9514..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/adapter/UserReadAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.chooz.notification.infrastructure.adapter; - -import com.chooz.notification.application.port.UserReadPort; -import com.chooz.notification.application.port.view.UserView; -import com.chooz.user.domain.User; -import com.chooz.user.domain.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.util.Optional; - -@Component -@RequiredArgsConstructor -public class UserReadAdapter implements UserReadPort { - - private final UserViewRepository userViewRepository; - - @Override - public Optional getUserByCommentId(Long commentId) { - return userViewRepository.findViewByCommentId(commentId); - } - - @Override - public Optional getUser(Long commentId) { - return userViewRepository.findViewById(commentId); - } -} diff --git a/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java b/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java deleted file mode 100644 index a0d429d..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/adapter/UserViewRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.chooz.notification.infrastructure.adapter; - -import com.chooz.notification.application.port.view.UserView; -import com.chooz.user.domain.User; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; - -import java.util.Optional; - -interface UserViewRepository extends Repository { - @Query(""" - select new com.chooz.notification.application.port.view.UserView(u.id, u.nickname, u.profileUrl) - from Comment c - join User u on u.id = c.userId - where c.id = :commentId - """) - Optional findViewByCommentId(Long commentId); - - @Query(""" - select new com.chooz.notification.application.port.view.UserView(u.id, u.nickname, u.profileUrl) - from User u - where u.id = :userId - """) - Optional findViewById(Long userId); -} diff --git a/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java new file mode 100644 index 0000000..f30cdbe --- /dev/null +++ b/src/main/java/com/chooz/notification/persistence/NotificationJpaRepository.java @@ -0,0 +1,27 @@ +package com.chooz.notification.persistence; + +import com.chooz.notification.domain.Notification; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface NotificationJpaRepository extends JpaRepository { + + @Query(""" + SELECT n + FROM Notification n + WHERE n.receiver.id = :userId + AND (:cursor is null OR n.id < :cursor) + ORDER BY + n.id DESC + """) + Slice findByUserId( + @Param("userId") Long userId, + @Param("cursor") Long cursor, + Pageable pageable + ); +} diff --git a/src/main/java/com/chooz/notification/persistence/NotificationQueryDslRepository.java b/src/main/java/com/chooz/notification/persistence/NotificationQueryDslRepository.java new file mode 100644 index 0000000..11c02df --- /dev/null +++ b/src/main/java/com/chooz/notification/persistence/NotificationQueryDslRepository.java @@ -0,0 +1,94 @@ +package com.chooz.notification.persistence; + +import com.chooz.notification.application.dto.NotificationDto; +import com.chooz.notification.application.dto.QNotificationDto; +import com.chooz.notification.application.dto.QTargetPostDto; +import com.chooz.notification.application.dto.QTargetUserDto; +import com.chooz.notification.application.dto.TargetPostDto; +import com.chooz.notification.application.dto.TargetUserDto; +import com.chooz.notification.domain.TargetType; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +import static com.chooz.comment.domain.QComment.comment; +import static com.chooz.notification.domain.QNotification.notification; +import static com.chooz.post.domain.QPost.post; +import static com.chooz.user.domain.QUser.user; + +@Repository +@RequiredArgsConstructor +public class NotificationQueryDslRepository { + + private final JPAQueryFactory queryFactory; + + public Slice findNotifications(Long userId, Long cursor, Pageable pageable) { + List notifications = queryFactory + .select(new QNotificationDto( + notification.id, + post.id, + notification.receiver.id, + notification.receiver.nickname, + notification.actor.id, + notification.actor.nickname, + notification.actor.profileUrl, + notification.target.id, + notification.target.type, + notification.target.imageUrl, + notification.isRead, + notification.eventAt + ) + ) + .from(notification) + .leftJoin(comment) + .on(notification.target.type.eq(TargetType.COMMENT) + .and(comment.id.eq(notification.target.id))) + .leftJoin(post) + .on(post.id.eq(comment.postId)) + .where( + notification.receiver.id.eq(userId), + cursor != null ? notification.id.lt(cursor) : null + ) + .orderBy(notification.id.desc()) + .limit(pageable.getPageSize() + 1) + .fetch(); + + boolean hasNext = notifications.size() > pageable.getPageSize(); + if (hasNext) notifications.removeLast(); + return new SliceImpl<>(notifications, pageable, hasNext); + } + + Optional getPost(Long commentId) { + return Optional.ofNullable( + queryFactory.select(new QTargetPostDto(post.id, post.imageUrl)) + .from(comment) + .join(post).on(post.id.eq(comment.postId)) + .where(comment.id.eq(commentId)) + .limit(1) + .fetchFirst()); + } + Optional getUserByCommentId(Long commentId) { + return Optional.ofNullable( + queryFactory.select(new QTargetUserDto(user.id, user.nickname, user.profileUrl)) + .from(comment) + .join(user).on(user.id.eq(comment.userId)) + .where(comment.id.eq(commentId)) + .limit(1) + .fetchFirst()); + } + Optional getUser(Long userId) { + return Optional.ofNullable( + queryFactory.select(new QTargetUserDto(user.id, user.nickname, user.profileUrl)) + .from(user) + .where(user.id.eq(userId)) + .limit(1) + .fetchFirst()); + } + +} diff --git a/src/main/java/com/chooz/notification/persistence/NotificationQueryRepositoryImpl.java b/src/main/java/com/chooz/notification/persistence/NotificationQueryRepositoryImpl.java new file mode 100644 index 0000000..736e881 --- /dev/null +++ b/src/main/java/com/chooz/notification/persistence/NotificationQueryRepositoryImpl.java @@ -0,0 +1,47 @@ +package com.chooz.notification.persistence; + +import com.chooz.notification.application.dto.NotificationDto; +import com.chooz.notification.application.dto.TargetPostDto; +import com.chooz.notification.application.dto.TargetUserDto; +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class NotificationQueryRepositoryImpl implements NotificationQueryRepository { + + private final NotificationJpaRepository notificationJpaRepository; + private final NotificationQueryDslRepository notificationQueryDslRepository; + +// @Override +// public Slice findNotifications(Long userId, Long cursor, Pageable pageable) { +// return notificationJpaRepository.findByUserId(userId, cursor, pageable); +// } + + @Override + public Slice findNotifications(Long userId, Long cursor, Pageable pageable) { + return notificationQueryDslRepository.findNotifications(userId, cursor, pageable); + } + + @Override + public Optional getPost(Long commentId) { + return notificationQueryDslRepository.getPost(commentId); + } + + @Override + public Optional getUserByCommentId(Long commentId) { + return notificationQueryDslRepository.getUserByCommentId(commentId); + } + + @Override + public Optional getUser(Long userId) { + return notificationQueryDslRepository.getUser(userId); + } +} \ No newline at end of file diff --git a/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java new file mode 100644 index 0000000..fb55a13 --- /dev/null +++ b/src/main/java/com/chooz/notification/persistence/NotificationRepositoryImpl.java @@ -0,0 +1,20 @@ +package com.chooz.notification.persistence; + +import com.chooz.notification.domain.Notification; +import com.chooz.notification.domain.NotificationRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + + +@Repository +@RequiredArgsConstructor +public class NotificationRepositoryImpl implements NotificationRepository { + + private final NotificationJpaRepository notificationJpaRepository; + + @Override + public Notification save(Notification notification) { + return notificationJpaRepository.save(notification); + } + +} \ No newline at end of file From a86a11b67aebd5da40c884b1f69ee89787f52976 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sat, 20 Sep 2025 20:32:45 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor=20:=20QDSL=20=EB=B0=8F=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeNotificationListener.java | 20 ++++----- .../NotificationContentAssembler.java | 26 ++++++----- .../application/NotificationQueryService.java | 7 ++- .../application/dto/CommentLikedContent.java | 14 +++--- .../application/dto/NotificationContent.java | 8 ++-- .../application/dto/NotificationDto.java | 23 ++++++++++ .../domain/NotificationQueryRepository.java | 8 +++- .../domain/NotificationRepository.java | 2 - .../NotificationJpaRepository.java | 27 ------------ .../NotificationQueryRepositoryImpl.java | 20 --------- .../NotificationRepositoryImpl.java | 20 --------- .../dto/NotificationResponse.java | 43 ++++++++++--------- 12 files changed, 91 insertions(+), 127 deletions(-) create mode 100644 src/main/java/com/chooz/notification/application/dto/NotificationDto.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java delete mode 100644 src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java diff --git a/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java b/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java index 6d176f8..a940cbf 100644 --- a/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java +++ b/src/main/java/com/chooz/notification/application/CommentLikeNotificationListener.java @@ -2,7 +2,6 @@ import com.chooz.notification.application.dto.CommentLikedContent; import com.chooz.notification.domain.Notification; -import com.chooz.notification.domain.NotificationType; import com.chooz.notification.domain.TargetType; import com.chooz.notification.domain.event.CommentLikedEvent; import lombok.RequiredArgsConstructor; @@ -21,16 +20,15 @@ public class CommentLikeNotificationListener { public void onCommentLiked(CommentLikedEvent e) { CommentLikedContent commentLikedContent = notificationContentAssembler.forCommentLiked(e.commentId(), e.likerId()); Notification.create( - commentLikedContent.getCommentAuthorId(), - e.likerId(), - NotificationType.COMMENT_LIKED, - TargetType.COMMENT, - e.commentId(), - commentLikedContent.getTitle(), - commentLikedContent.getBody(), - commentLikedContent.getThumbnailUrl(), - commentLikedContent.getProfileImageUrl(), - e.eventAt() + commentLikedContent.getCommentAuthorId(), + commentLikedContent.getCommentAuthorName(), + e.likerId(), + commentLikedContent.getActorName(), + commentLikedContent.getActorProfileImageUrl(), + e.commentId(), + TargetType.COMMENT, + commentLikedContent.getTargetThumbnailUrl(), + e.eventAt() ).ifPresent(notificationCommandService::create); } } diff --git a/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java b/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java index c16b382..ab49dff 100644 --- a/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java +++ b/src/main/java/com/chooz/notification/application/NotificationContentAssembler.java @@ -3,10 +3,9 @@ import com.chooz.common.exception.BadRequestException; import com.chooz.common.exception.ErrorCode; import com.chooz.notification.application.dto.CommentLikedContent; -import com.chooz.notification.application.port.PostReadPort; -import com.chooz.notification.application.port.UserReadPort; -import com.chooz.notification.application.port.view.PostView; -import com.chooz.notification.application.port.view.UserView; +import com.chooz.notification.application.dto.TargetPostDto; +import com.chooz.notification.application.dto.TargetUserDto; +import com.chooz.notification.domain.NotificationQueryRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,23 +13,22 @@ @RequiredArgsConstructor public class NotificationContentAssembler { - private final UserReadPort userReadPort; - private final PostReadPort postReadPort; + private final NotificationQueryRepository notificationQueryDslRepository; public CommentLikedContent forCommentLiked(Long commentId, Long likerId) { - UserView likerUserView = userReadPort.getUser(likerId) + TargetUserDto targetUserDto = notificationQueryDslRepository.getUser(likerId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - UserView commentAuthorView = userReadPort.getUserByCommentId(commentId) + TargetUserDto commentAuthorDto = notificationQueryDslRepository.getUserByCommentId(commentId) .orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND)); - PostView postView = postReadPort.getPost(commentId) + TargetPostDto targetPostDto = notificationQueryDslRepository.getPost(commentId) .orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND)); return new CommentLikedContent( - likerUserView.nickname() + " 님이 당신의 댓글에 좋아요를 눌렀어요!", - "지금 확인해보세요.", - postView.imageUrl(), - likerUserView.profileUrl(), - commentAuthorView.id() + targetUserDto.nickname(), + targetUserDto.profileUrl(), + targetPostDto.imageUrl(), + commentAuthorDto.id(), + commentAuthorDto.nickname() ); } diff --git a/src/main/java/com/chooz/notification/application/NotificationQueryService.java b/src/main/java/com/chooz/notification/application/NotificationQueryService.java index 094161a..e4c0b70 100644 --- a/src/main/java/com/chooz/notification/application/NotificationQueryService.java +++ b/src/main/java/com/chooz/notification/application/NotificationQueryService.java @@ -1,6 +1,7 @@ package com.chooz.notification.application; import com.chooz.common.dto.CursorBasePaginatedResponse; +import com.chooz.notification.application.dto.NotificationDto; import com.chooz.notification.domain.Notification; import com.chooz.notification.domain.NotificationQueryRepository; import com.chooz.notification.presentation.dto.NotificationResponse; @@ -17,8 +18,12 @@ public class NotificationQueryService { private final NotificationQueryRepository notificationQueryRepository; +// public CursorBasePaginatedResponse findNotifications(Long userId, Long cursor, int size) { +// Slice notificationSlice = notificationQueryRepository.findNotifications(userId, cursor, PageRequest.ofSize(size)); +// return CursorBasePaginatedResponse.of(notificationSlice.map(NotificationResponse::of)); +// } public CursorBasePaginatedResponse findNotifications(Long userId, Long cursor, int size) { - Slice notificationSlice = notificationQueryRepository.findNotifications(userId, cursor, PageRequest.ofSize(size)); + Slice notificationSlice = notificationQueryRepository.findNotifications(userId, cursor, PageRequest.ofSize(size)); return CursorBasePaginatedResponse.of(notificationSlice.map(NotificationResponse::of)); } } diff --git a/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java b/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java index ed64266..082009e 100644 --- a/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java +++ b/src/main/java/com/chooz/notification/application/dto/CommentLikedContent.java @@ -6,15 +6,17 @@ public class CommentLikedContent extends NotificationContent { private final Long commentAuthorId; + private final String commentAuthorName; public CommentLikedContent( - String title, - String body, - String thumbnailUrl, - String profileImageUrl, - Long commentAuthorId + String actorName, + String actorProfileImageUrl, + String targetThumbnailUrl, + Long commentAuthorId, + String commentAuthorName ) { - super(title, body, thumbnailUrl, profileImageUrl); + super(actorName, targetThumbnailUrl, actorProfileImageUrl); this.commentAuthorId = commentAuthorId; + this.commentAuthorName = commentAuthorName; } } diff --git a/src/main/java/com/chooz/notification/application/dto/NotificationContent.java b/src/main/java/com/chooz/notification/application/dto/NotificationContent.java index 406e2d8..f401520 100644 --- a/src/main/java/com/chooz/notification/application/dto/NotificationContent.java +++ b/src/main/java/com/chooz/notification/application/dto/NotificationContent.java @@ -6,8 +6,8 @@ @Getter @RequiredArgsConstructor public abstract class NotificationContent { - private final String title; - private final String body; - private final String thumbnailUrl; - private final String profileImageUrl; + private final String actorName; + private final String actorProfileImageUrl; + private final String targetThumbnailUrl; + } diff --git a/src/main/java/com/chooz/notification/application/dto/NotificationDto.java b/src/main/java/com/chooz/notification/application/dto/NotificationDto.java new file mode 100644 index 0000000..b0ab162 --- /dev/null +++ b/src/main/java/com/chooz/notification/application/dto/NotificationDto.java @@ -0,0 +1,23 @@ +package com.chooz.notification.application.dto; + + +import com.chooz.notification.domain.TargetType; +import com.querydsl.core.annotations.QueryProjection; + +import java.time.LocalDateTime; + +@QueryProjection +public record NotificationDto( + Long id, + Long postId, + Long receiverId, + String receiverNickname, + Long actorId, + String actorNickname, + String actorProfileUrl, + Long targetId, + TargetType targetType, + String targetImageUrl, + boolean isRead, + LocalDateTime eventAt +) {} diff --git a/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java b/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java index 2c364db..c009150 100644 --- a/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java +++ b/src/main/java/com/chooz/notification/domain/NotificationQueryRepository.java @@ -1,10 +1,16 @@ package com.chooz.notification.domain; +import com.chooz.notification.application.dto.NotificationDto; +import com.chooz.notification.application.dto.TargetPostDto; +import com.chooz.notification.application.dto.TargetUserDto; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import java.util.Optional; public interface NotificationQueryRepository { - Slice findNotifications(Long userId, Long cursor, Pageable pageable); + Slice findNotifications(Long userId, Long cursor, Pageable pageable); + Optional getPost(Long commentId); + Optional getUserByCommentId(Long commentId); + Optional getUser(Long userId); } diff --git a/src/main/java/com/chooz/notification/domain/NotificationRepository.java b/src/main/java/com/chooz/notification/domain/NotificationRepository.java index c4235d0..859367f 100644 --- a/src/main/java/com/chooz/notification/domain/NotificationRepository.java +++ b/src/main/java/com/chooz/notification/domain/NotificationRepository.java @@ -1,7 +1,5 @@ package com.chooz.notification.domain; -import java.util.Optional; - public interface NotificationRepository { Notification save(Notification notification); } diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java deleted file mode 100644 index 5fa3b4f..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationJpaRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.chooz.notification.infrastructure.persistence; - -import com.chooz.notification.domain.Notification; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -@Repository -public interface NotificationJpaRepository extends JpaRepository { - - @Query(""" - SELECT n - FROM Notification n - WHERE n.userId = :userId - AND (:cursor is null OR n.id < :cursor) - ORDER BY - n.id DESC - """) - Slice findByUserId( - @Param("userId") Long userId, - @Param("cursor") Long cursor, - Pageable pageable - ); -} diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java deleted file mode 100644 index aada614..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationQueryRepositoryImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.chooz.notification.infrastructure.persistence; - -import com.chooz.notification.domain.Notification; -import com.chooz.notification.domain.NotificationQueryRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class NotificationQueryRepositoryImpl implements NotificationQueryRepository { - - private final NotificationJpaRepository notificationJpaRepository; - - @Override - public Slice findNotifications(Long userId, Long cursor, Pageable pageable) { - return notificationJpaRepository.findByUserId(userId, cursor, pageable); - } -} \ No newline at end of file diff --git a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java b/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java deleted file mode 100644 index ac37b41..0000000 --- a/src/main/java/com/chooz/notification/infrastructure/persistence/NotificationRepositoryImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.chooz.notification.infrastructure.persistence; - -import com.chooz.notification.domain.Notification; -import com.chooz.notification.domain.NotificationRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - - -@Repository -@RequiredArgsConstructor -public class NotificationRepositoryImpl implements NotificationRepository { - - private final NotificationJpaRepository notificationJpaRepository; - - @Override - public Notification save(Notification notification) { - return notificationJpaRepository.save(notification); - } - -} \ No newline at end of file diff --git a/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java b/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java index fdf95ad..64b7706 100644 --- a/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java +++ b/src/main/java/com/chooz/notification/presentation/dto/NotificationResponse.java @@ -1,40 +1,41 @@ package com.chooz.notification.presentation.dto; import com.chooz.common.dto.CursorDto; +import com.chooz.notification.application.dto.NotificationDto; +import com.chooz.notification.domain.Actor; import com.chooz.notification.domain.Notification; -import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.Receiver; import com.chooz.notification.domain.Target; -import com.chooz.notification.domain.TargetType; import java.time.LocalDateTime; public record NotificationResponse ( Long id, - Long userId, - Long actorId, - NotificationType type, + Long postId, + Receiver receiver, + Actor actor, Target target, - String title, - String body, - String thumbUrl, - String profileImageUrl, boolean isRead, LocalDateTime eventAt )implements CursorDto{ - public static NotificationResponse of (Notification notification){ + public static NotificationResponse of (NotificationDto notificationDto){ return new NotificationResponse( - notification.getId(), - notification.getUserId(), - notification.getActorId(), - notification.getType(), - notification.getTarget(), - notification.getTitle(), - notification.getBody(), - notification.getThumbUrl(), - notification.getProfileImageUrl(), - notification.isRead(), - notification.getEventAt() + notificationDto.id(), + notificationDto.postId(), + new Receiver(notificationDto.receiverId(), notificationDto.receiverNickname()), + new Actor( + notificationDto.actorId(), + notificationDto.actorNickname(), + notificationDto.actorProfileUrl() + ), + new Target( + notificationDto.targetId(), + notificationDto.targetType(), + notificationDto.targetImageUrl() + ), + notificationDto.isRead(), + notificationDto.eventAt() ); } From c933633c5db17697e7afdc36a8e391b404ea3153 Mon Sep 17 00:00:00 2001 From: yunseongoh Date: Sat, 20 Sep 2025 20:33:27 +0900 Subject: [PATCH 9/9] =?UTF-8?q?test=20:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeNotificationListenerTest.java | 17 ++--- .../notification/domain/NotificationTest.java | 65 +++++++++---------- .../NotificationControllerTest.java | 62 ++++++++++-------- 3 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java index 33e9ffb..52d5e47 100644 --- a/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java +++ b/src/test/java/com/chooz/notification/application/CommentLikeNotificationListenerTest.java @@ -3,9 +3,9 @@ import com.chooz.comment.domain.Comment; import com.chooz.comment.domain.CommentRepository; import com.chooz.commentLike.application.CommentLikeService; +import com.chooz.notification.application.dto.NotificationDto; import com.chooz.notification.domain.Notification; import com.chooz.notification.domain.NotificationQueryRepository; -import com.chooz.notification.domain.NotificationType; import com.chooz.notification.domain.TargetType; import com.chooz.post.domain.Post; import com.chooz.post.domain.PostRepository; @@ -60,18 +60,19 @@ void onCommentLiked() throws Exception { TestTransaction.end(); //then - Slice notificationSlice = notificationQueryRepository.findNotifications( + Slice notificationSlice = notificationQueryRepository.findNotifications( receiver.getId(), null, - PageRequest.ofSize(10)); + PageRequest.ofSize(10) + ); assertAll( () -> assertThat(notificationSlice.getContent().size()).isEqualTo(1), - () -> assertThat(notificationSlice.getContent().getFirst().getUserId()).isEqualTo(receiver.getId()), - () -> assertThat(notificationSlice.getContent().getFirst().getActorId()).isEqualTo(actor.getId()), - () -> assertThat(notificationSlice.getContent().getFirst().getTarget().getType()).isEqualTo(TargetType.COMMENT), - () -> assertThat(notificationSlice.getContent().getFirst().getTarget().getId()).isEqualTo(comment.getId()), - () -> assertThat(notificationSlice.getContent().getFirst().getType()).isEqualTo(NotificationType.COMMENT_LIKED) + () -> assertThat(notificationSlice.getContent().getFirst().receiverId()).isEqualTo(receiver.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().actorId()).isEqualTo(actor.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().targetType()).isEqualTo(TargetType.COMMENT), + () -> assertThat(notificationSlice.getContent().getFirst().targetId()).isEqualTo(comment.getId()), + () -> assertThat(notificationSlice.getContent().getFirst().postId()).isEqualTo(post.getId()) ); } } diff --git a/src/test/java/com/chooz/notification/domain/NotificationTest.java b/src/test/java/com/chooz/notification/domain/NotificationTest.java index 5194bd9..43867d7 100644 --- a/src/test/java/com/chooz/notification/domain/NotificationTest.java +++ b/src/test/java/com/chooz/notification/domain/NotificationTest.java @@ -14,41 +14,38 @@ class NotificationTest { @DisplayName("알림 생성") void create() throws Exception { //given - Long userId = 1L; + Long receiverId = 1L; + String receiverNickname = "공개된 츄"; Long actorId = 2L; - NotificationType type = NotificationType.COMMENT_LIKED; - TargetType targetType = TargetType.COMMENT; + String actorNickname = "숨겨진 츄"; + String actorProfileUrl = "https://cdn.chooz.site/default_profile.png"; Long targetId = 3L; - String title = "숨겨진 츄님이 당신의 댓글에 좋아요를 눌렀습니다."; - String body = "지금 바로 확인해보세요!"; - String thumbUrl = "https://cdn.chooz.site/thumbnail.png"; - String profileImageUrl = "https://cdn.chooz.site/default_profile.png"; + TargetType targetType = TargetType.COMMENT; + String targetImageUrl = "https://cdn.chooz.site/default_target.png"; LocalDateTime eventAt = LocalDateTime.now(); //when Notification notification = Notification.create( - userId, + receiverId, + receiverNickname, actorId, - type, - targetType, + actorNickname, + actorProfileUrl, targetId, - title, - body, - thumbUrl, - profileImageUrl, + targetType, + targetImageUrl, eventAt ).get(); //then assertAll( - () -> assertThat(notification.getUserId()).isEqualTo(userId), - () -> assertThat(notification.getActorId()).isEqualTo(actorId), - () -> assertThat(notification.getType()).isEqualTo(type), + () -> assertThat(notification.getReceiver().getId()).isEqualTo(receiverId), + () -> assertThat(notification.getReceiver().getNickname()).isEqualTo(receiverNickname), + () -> assertThat(notification.getActor().getId()).isEqualTo(actorId), + () -> assertThat(notification.getActor().getNickname()).isEqualTo(actorNickname), + () -> assertThat(notification.getActor().getProfileUrl()).isEqualTo(actorProfileUrl), () -> assertThat(notification.getTarget().getId()).isEqualTo(targetId), () -> assertThat(notification.getTarget().getType()).isEqualTo(targetType), - () -> assertThat(notification.getTitle()).isEqualTo(title), - () -> assertThat(notification.getBody()).isEqualTo(body), - () -> assertThat(notification.getThumbUrl()).isEqualTo(thumbUrl), - () -> assertThat(notification.getProfileImageUrl()).isEqualTo(profileImageUrl), + () -> assertThat(notification.getTarget().getImageUrl()).isEqualTo(targetImageUrl), () -> assertThat(notification.getEventAt()).isEqualTo(eventAt) ); } @@ -56,27 +53,25 @@ void create() throws Exception { @DisplayName("알림 읽음 확인") void markRead() throws Exception { //given - Long userId = 1L; + Long receiverId = 1L; + String receiverNickname = "공개된 츄"; Long actorId = 2L; - NotificationType type = NotificationType.COMMENT_LIKED; - TargetType targetType = TargetType.COMMENT; + String actorNickname = "숨겨진 츄"; + String actorProfileUrl = "https://cdn.chooz.site/default_profile.png"; Long targetId = 3L; - String title = "숨겨진 츄님이 당신의 댓글에 좋아요를 눌렀습니다."; - String body = "지금 바로 확인해보세요!"; - String thumbUrl = "https://cdn.chooz.site/thumbnail.png"; - String profileImageUrl = "https://cdn.chooz.site/default_profile.png"; + TargetType targetType = TargetType.COMMENT; + String targetImageUrl = "https://cdn.chooz.site/default_target.png"; LocalDateTime eventAt = LocalDateTime.now(); //when Notification notification = Notification.create( - userId, + receiverId, + receiverNickname, actorId, - type, - targetType, + actorNickname, + actorProfileUrl, targetId, - title, - body, - thumbUrl, - profileImageUrl, + targetType, + targetImageUrl, eventAt ).get(); diff --git a/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java b/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java index f8d98a7..b25d525 100644 --- a/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java +++ b/src/test/java/com/chooz/notification/presentation/NotificationControllerTest.java @@ -1,7 +1,8 @@ package com.chooz.notification.presentation; import com.chooz.common.dto.CursorBasePaginatedResponse; -import com.chooz.notification.domain.NotificationType; +import com.chooz.notification.domain.Actor; +import com.chooz.notification.domain.Receiver; import com.chooz.notification.domain.Target; import com.chooz.notification.domain.TargetType; import com.chooz.notification.presentation.dto.NotificationResponse; @@ -10,12 +11,10 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.restdocs.payload.JsonFieldType; import java.time.LocalDateTime; import java.util.List; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; @@ -38,14 +37,10 @@ void findNotifications() throws Exception { List.of( new NotificationResponse( 1L, - 1L, - 3L, - NotificationType.COMMENT_LIKED, - new Target(TargetType.COMMENT, 4L), - "숨겨진 츄 님이 당신의 댓글에 좋아요를 눌렀어요!", - "지금 확인해보세요.", - "https://cdn.chooz.site/thumbnail.png", - "https://cdn.chooz.site/default_profile.png", + 2L, + new Receiver(1L, "숨겨진 츄"), + new Actor(2L, "공개된 츄", "https://cdn.chooz.site/default_profile.png"), + new Target(3L, TargetType.COMMENT, "https://cdn.chooz.site/thumbnail.png"), false, LocalDateTime.now() ) @@ -62,21 +57,36 @@ void findNotifications() throws Exception { requestHeaders(authorizationHeader()), queryParameters(cursorQueryParams()), responseFields( - fieldWithPath("nextCursor").type(JsonFieldType.NUMBER).optional().description("다음 조회 커서 값"), - fieldWithPath("hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부 (기본 값 10)"), - fieldWithPath("data[]").type(JsonFieldType.ARRAY).description("알림 데이터"), - fieldWithPath("data[].id").type(JsonFieldType.NUMBER).description("알림 ID"), - fieldWithPath("data[].userId").type(JsonFieldType.NUMBER).description("알림 받는 유저 ID"), - fieldWithPath("data[].actorId").type(JsonFieldType.NUMBER).description("알림을 발생시킨 유저 ID"), - fieldWithPath("data[].type").type(JsonFieldType.STRING).description("알림 발생 유형"), - fieldWithPath("data[].target.type").type(JsonFieldType.STRING).description("알림 타겟 유형"), - fieldWithPath("data[].target.id").type(JsonFieldType.NUMBER).description("알림 타겟 ID"), - fieldWithPath("data[].title").type(JsonFieldType.STRING).description("알림 제목"), - fieldWithPath("data[].body").type(JsonFieldType.STRING).description("알림 내용"), - fieldWithPath("data[].thumbUrl").type(JsonFieldType.STRING).description("썸네일 이미지 url"), - fieldWithPath("data[].profileImageUrl").type(JsonFieldType.STRING).description("알림을 발생시킨 유저 썸네일 url"), - fieldWithPath("data[].isRead").type(JsonFieldType.BOOLEAN).description("읽음 여부"), - fieldWithPath("data[].eventAt").type(JsonFieldType.STRING).description("이벤트 발생 시간") + fieldWithPath("nextCursor").type(JsonFieldType.NUMBER).optional() + .description("다음 조회 커서 값"), + fieldWithPath("hasNext") + .type(JsonFieldType.BOOLEAN).description("다음 페이지 존재 여부 (기본 값 10)"), + fieldWithPath("data[]") + .type(JsonFieldType.ARRAY).description("알림 데이터"), + fieldWithPath("data[].id") + .type(JsonFieldType.NUMBER).description("알림 ID"), + fieldWithPath("data[].postId") + .type(JsonFieldType.NUMBER).description("게시물 ID"), + fieldWithPath("data[].receiver.id") + .type(JsonFieldType.NUMBER).description("receiver ID"), + fieldWithPath("data[].receiver.nickname") + .type(JsonFieldType.STRING).description("receiver 닉네임"), + fieldWithPath("data[].actor.id") + .type(JsonFieldType.NUMBER).description("actor ID"), + fieldWithPath("data[].actor.nickname") + .type(JsonFieldType.STRING).description("actor 닉네임"), + fieldWithPath("data[].actor.profileUrl") + .type(JsonFieldType.STRING).description("actor 프로필 이미지 url"), + fieldWithPath("data[].target.id") + .type(JsonFieldType.NUMBER).description("알림 타겟 ID"), + fieldWithPath("data[].target.type") + .type(JsonFieldType.STRING).description("알림 타겟 유형"), + fieldWithPath("data[].target.imageUrl") + .type(JsonFieldType.STRING).description("알림 타겟 썸네일 이미지 url"), + fieldWithPath("data[].isRead") + .type(JsonFieldType.BOOLEAN).description("읽음 여부"), + fieldWithPath("data[].eventAt") + .type(JsonFieldType.STRING).description("이벤트 발생 시간") ) )); }