diff --git a/src/main/java/com/prgrms/nabmart/domain/delivery/Delivery.java b/src/main/java/com/prgrms/nabmart/domain/delivery/Delivery.java index f3cbe23e..29f10fff 100644 --- a/src/main/java/com/prgrms/nabmart/domain/delivery/Delivery.java +++ b/src/main/java/com/prgrms/nabmart/domain/delivery/Delivery.java @@ -69,6 +69,9 @@ public class Delivery extends BaseTimeEntity { @Column private Integer deliveryFee; + @Column + private Long userId; + @Builder public Delivery(final Order order, final int estimateMinutes) { validateOrderStatus(order); @@ -80,6 +83,7 @@ public Delivery(final Order order, final int estimateMinutes) { this.riderRequest = order.getRiderRequest(); this.deliveryFee = order.getDeliveryFee(); this.arrivedAt = LocalDateTime.now().plusMinutes(estimateMinutes); + this.userId = order.getUser().getUserId(); order.updateOrderStatus(OrderStatus.DELIVERING); } diff --git a/src/main/java/com/prgrms/nabmart/domain/delivery/service/DeliveryService.java b/src/main/java/com/prgrms/nabmart/domain/delivery/service/DeliveryService.java index 93d5c331..4e4d7e3f 100644 --- a/src/main/java/com/prgrms/nabmart/domain/delivery/service/DeliveryService.java +++ b/src/main/java/com/prgrms/nabmart/domain/delivery/service/DeliveryService.java @@ -1,5 +1,9 @@ package com.prgrms.nabmart.domain.delivery.service; +import static com.prgrms.nabmart.domain.notification.NotificationMessage.COMPLETE_DELIVERY; +import static com.prgrms.nabmart.domain.notification.NotificationMessage.REGISTER_DELIVERY; +import static com.prgrms.nabmart.domain.notification.NotificationMessage.START_DELIVERY; + import com.prgrms.nabmart.domain.delivery.Delivery; import com.prgrms.nabmart.domain.delivery.Rider; import com.prgrms.nabmart.domain.delivery.exception.AlreadyRegisteredDeliveryException; @@ -18,6 +22,9 @@ import com.prgrms.nabmart.domain.delivery.service.response.FindDeliveryDetailResponse; import com.prgrms.nabmart.domain.delivery.service.response.FindRiderDeliveriesResponse; import com.prgrms.nabmart.domain.delivery.service.response.FindWaitingDeliveriesResponse; +import com.prgrms.nabmart.domain.notification.NotificationType; +import com.prgrms.nabmart.domain.notification.service.NotificationService; +import com.prgrms.nabmart.domain.notification.service.request.SendNotificationCommand; import com.prgrms.nabmart.domain.order.Order; import com.prgrms.nabmart.domain.order.exception.NotFoundOrderException; import com.prgrms.nabmart.domain.order.repository.OrderRepository; @@ -37,6 +44,7 @@ public class DeliveryService { private final UserRepository userRepository; private final RiderRepository riderRepository; private final OrderRepository orderRepository; + private final NotificationService notificationService; @Transactional public Long registerDelivery(RegisterDeliveryCommand registerDeliveryCommand) { @@ -45,12 +53,29 @@ public Long registerDelivery(RegisterDeliveryCommand registerDeliveryCommand) { checkAlreadyRegisteredDelivery(order); Delivery delivery = new Delivery(order, registerDeliveryCommand.estimateMinutes()); deliveryRepository.save(delivery); + + sendRegisterDeliveryNotification(registerDeliveryCommand, delivery, order); + return delivery.getDeliveryId(); } + private void sendRegisterDeliveryNotification( + RegisterDeliveryCommand registerDeliveryCommand, + Delivery delivery, + Order order) { + SendNotificationCommand notificationCommand = SendNotificationCommand.of( + delivery.getUserId(), + REGISTER_DELIVERY.getTitle(), + REGISTER_DELIVERY.getContentFromFormat( + order.getName(), + registerDeliveryCommand.estimateMinutes()), + NotificationType.DELIVERY); + notificationService.sendNotification(notificationCommand); + } + private void checkUserHasRegisterDeliveryAuthority(final Long userId) { User user = findUserByUserId(userId); - if(!user.isEmployee()) { + if (!user.isEmployee()) { throw new UnauthorizedDeliveryException("권한이 없습니다."); } } @@ -61,7 +86,7 @@ private Order findOrderByOrderIdPessimistic(RegisterDeliveryCommand registerDeli } private void checkAlreadyRegisteredDelivery(final Order order) { - if(deliveryRepository.existsByOrder(order)) { + if (deliveryRepository.existsByOrder(order)) { throw new AlreadyRegisteredDeliveryException("이미 배달이 생성된 주문입니다."); } } @@ -99,6 +124,19 @@ public void startDelivery(StartDeliveryCommand startDeliveryCommand) { Delivery delivery = findDeliveryByDeliveryId(startDeliveryCommand.deliveryId()); delivery.checkAuthority(rider); delivery.startDelivery(startDeliveryCommand.deliveryEstimateMinutes()); + + sendStartDeliveryNotification(startDeliveryCommand, delivery); + } + + private void sendStartDeliveryNotification( + StartDeliveryCommand startDeliveryCommand, + Delivery delivery) { + SendNotificationCommand notificationCommand = SendNotificationCommand.of( + delivery.getUserId(), + START_DELIVERY.getTitle(), + START_DELIVERY.getContentFromFormat(startDeliveryCommand.deliveryEstimateMinutes()), + NotificationType.DELIVERY); + notificationService.sendNotification(notificationCommand); } @Transactional @@ -107,6 +145,17 @@ public void completeDelivery(CompleteDeliveryCommand completeDeliveryCommand) { Delivery delivery = findDeliveryByDeliveryId(completeDeliveryCommand.deliveryId()); delivery.checkAuthority(rider); delivery.completeDelivery(); + + sendCompleteDeliveryNotification(delivery); + } + + private void sendCompleteDeliveryNotification(Delivery delivery) { + SendNotificationCommand notificationCommand = SendNotificationCommand.of( + delivery.getUserId(), + COMPLETE_DELIVERY.getTitle(), + COMPLETE_DELIVERY.getContentFromFormat(), + NotificationType.DELIVERY); + notificationService.sendNotification(notificationCommand); } private Rider findRiderByRiderId(final Long riderId) { diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/Notification.java b/src/main/java/com/prgrms/nabmart/domain/notification/Notification.java index 5f258431..59b21fc5 100644 --- a/src/main/java/com/prgrms/nabmart/domain/notification/Notification.java +++ b/src/main/java/com/prgrms/nabmart/domain/notification/Notification.java @@ -2,52 +2,46 @@ import com.prgrms.nabmart.domain.notification.exception.InvalidNotificationException; import com.prgrms.nabmart.global.BaseTimeEntity; -import jakarta.persistence.Column; -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 java.util.Objects; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -@Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Notification extends BaseTimeEntity { - private static final int CONTENT_LENGTH = 50; + private static final int TITLE_LENGTH = 30; + private static final int CONTENT_LENGTH = 100; - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long notificationId; - - @Column(nullable = false) + private String title; private String content; - - @Enumerated(EnumType.STRING) private NotificationType notificationType; - - @Column(nullable = false) private Long userId; @Builder public Notification( + String title, String content, Long userId, NotificationType notificationType) { + validateTitle(title); validateContent(content); + this.title = title; this.content = content; this.notificationType = notificationType; this.userId = userId; } + private void validateTitle(String title) { + if (Objects.nonNull(title) && title.length() > TITLE_LENGTH) { + throw new InvalidNotificationException("제목의 길이는 20자 이하여야 합니다."); + } + } + private void validateContent(String content) { - if(Objects.nonNull(content) && content.length() > CONTENT_LENGTH) { + if (Objects.nonNull(content) && content.length() > CONTENT_LENGTH) { throw new InvalidNotificationException("내용의 길이는 50자 이하여야 합니다."); } } diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/NotificationMessage.java b/src/main/java/com/prgrms/nabmart/domain/notification/NotificationMessage.java new file mode 100644 index 00000000..441a540d --- /dev/null +++ b/src/main/java/com/prgrms/nabmart/domain/notification/NotificationMessage.java @@ -0,0 +1,21 @@ +package com.prgrms.nabmart.domain.notification; + +import java.text.MessageFormat; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum NotificationMessage { + REGISTER_DELIVERY("주문이 접수되었습니다.", + "주문하신 {0} 이(가) {1}분 내에 도착할 예정입니다."), + START_DELIVERY("라이더가 주문을 픽업하였습니다.", "약 {0}분 후에 도착할 예정입니다."), + COMPLETE_DELIVERY("배달이 완료되었습니다.", "네이B마트를 이용해주셔서 감사합니다."); + + private final String title; + private final String contentFormat; + + public String getContentFromFormat(Object... arguments) { + return MessageFormat.format(contentFormat, arguments); + } +} diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/repository/NotificationRepository.java b/src/main/java/com/prgrms/nabmart/domain/notification/repository/NotificationRepository.java deleted file mode 100644 index 0c87b21c..00000000 --- a/src/main/java/com/prgrms/nabmart/domain/notification/repository/NotificationRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.prgrms.nabmart.domain.notification.repository; - -import com.prgrms.nabmart.domain.notification.Notification; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface NotificationRepository extends JpaRepository { - -} diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/service/NotificationService.java b/src/main/java/com/prgrms/nabmart/domain/notification/service/NotificationService.java index f28b5dd1..f4b792da 100644 --- a/src/main/java/com/prgrms/nabmart/domain/notification/service/NotificationService.java +++ b/src/main/java/com/prgrms/nabmart/domain/notification/service/NotificationService.java @@ -6,12 +6,10 @@ import com.prgrms.nabmart.domain.notification.NotificationType; import com.prgrms.nabmart.domain.notification.controller.request.ConnectNotificationCommand; import com.prgrms.nabmart.domain.notification.repository.EmitterRepository; -import com.prgrms.nabmart.domain.notification.repository.NotificationRepository; import com.prgrms.nabmart.domain.notification.service.request.SendNotificationCommand; import com.prgrms.nabmart.domain.notification.service.response.NotificationResponse; import com.prgrms.nabmart.domain.user.exception.NotFoundUserException; import com.prgrms.nabmart.domain.user.repository.UserRepository; -import jakarta.transaction.Transactional; import java.io.IOException; import java.util.Map; import lombok.RequiredArgsConstructor; @@ -27,7 +25,6 @@ public class NotificationService { private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 120; private final EmitterRepository emitterRepository; - private final NotificationRepository notificationRepository; private final UserRepository userRepository; public SseEmitter connectNotification(ConnectNotificationCommand connectNotificationCommand) { @@ -65,20 +62,20 @@ private void send(SseEmitter emitter, String emitterId, Object data) { log.error("알림 전송에 실패했습니다.", ex); } } - - @Transactional + public void sendNotification(SendNotificationCommand sendNotificationCommand) { Long userId = sendNotificationCommand.userId(); + String title = sendNotificationCommand.title(); String content = sendNotificationCommand.content(); NotificationType notificationType = sendNotificationCommand.notificationType(); verifyExistsUser(userId); Notification notification = Notification.builder() + .title(title) .content(content) .userId(userId) .notificationType(notificationType) .build(); - notificationRepository.save(notification); Map emitters = emitterRepository.findAllByIdStartWith(userId); emitters.forEach((key, emitter) -> { diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/service/request/SendNotificationCommand.java b/src/main/java/com/prgrms/nabmart/domain/notification/service/request/SendNotificationCommand.java index 3a02512d..f460e50d 100644 --- a/src/main/java/com/prgrms/nabmart/domain/notification/service/request/SendNotificationCommand.java +++ b/src/main/java/com/prgrms/nabmart/domain/notification/service/request/SendNotificationCommand.java @@ -4,13 +4,15 @@ public record SendNotificationCommand( Long userId, + String title, String content, NotificationType notificationType) { public static SendNotificationCommand of( final Long userId, + final String title, final String content, final NotificationType notificationType) { - return new SendNotificationCommand(userId, content, notificationType); + return new SendNotificationCommand(userId, title, content, notificationType); } } diff --git a/src/main/java/com/prgrms/nabmart/domain/notification/service/response/NotificationResponse.java b/src/main/java/com/prgrms/nabmart/domain/notification/service/response/NotificationResponse.java index 43e1753b..6c04b5c2 100644 --- a/src/main/java/com/prgrms/nabmart/domain/notification/service/response/NotificationResponse.java +++ b/src/main/java/com/prgrms/nabmart/domain/notification/service/response/NotificationResponse.java @@ -5,7 +5,7 @@ import java.time.LocalDateTime; public record NotificationResponse( - Long notificationId, + String title, String content, NotificationType notificationType, Long userId, @@ -13,7 +13,7 @@ public record NotificationResponse( public static NotificationResponse from(Notification notification) { return new NotificationResponse( - notification.getNotificationId(), + notification.getTitle(), notification.getContent(), notification.getNotificationType(), notification.getUserId(), diff --git a/src/test/java/com/prgrms/nabmart/domain/delivery/service/DeliveryServiceTest.java b/src/test/java/com/prgrms/nabmart/domain/delivery/service/DeliveryServiceTest.java index 8950ffa2..7fdc7c25 100644 --- a/src/test/java/com/prgrms/nabmart/domain/delivery/service/DeliveryServiceTest.java +++ b/src/test/java/com/prgrms/nabmart/domain/delivery/service/DeliveryServiceTest.java @@ -31,6 +31,7 @@ import com.prgrms.nabmart.domain.delivery.service.response.FindRiderDeliveriesResponse.FindRiderDeliveryResponse; import com.prgrms.nabmart.domain.delivery.service.response.FindWaitingDeliveriesResponse; import com.prgrms.nabmart.domain.delivery.support.DeliveryFixture; +import com.prgrms.nabmart.domain.notification.service.NotificationService; import com.prgrms.nabmart.domain.order.Order; import com.prgrms.nabmart.domain.order.exception.NotFoundOrderException; import com.prgrms.nabmart.domain.order.repository.OrderRepository; @@ -75,6 +76,9 @@ class DeliveryServiceTest { @Mock OrderRepository orderRepository; + @Mock + NotificationService notificationService; + User user = UserFixture.user(); Order order = deliveringOrder(1L, user); Rider rider = DeliveryFixture.rider(); @@ -106,6 +110,7 @@ void success() { //then then(deliveryRepository).should().save(any()); + then(notificationService).should().sendNotification(any()); } @Test @@ -262,6 +267,24 @@ void success() { assertThat(delivery.getDeliveryStatus()).isEqualTo(DeliveryStatus.START_DELIVERY); } + @Test + @DisplayName("성공: 배달 시작 알림 전송") + void successThenNotify() { + //given + int deliveryEstimateMinutes = 20; + StartDeliveryCommand startDeliveryCommand + = StartDeliveryCommand.of(1L, deliveryEstimateMinutes, 1L); + + given(riderRepository.findById(any())).willReturn(Optional.ofNullable(rider)); + given(deliveryRepository.findById(any())).willReturn(Optional.ofNullable(delivery)); + + //when + deliveryService.startDelivery(startDeliveryCommand); + + //then + then(notificationService).should().sendNotification(any()); + } + @Test @DisplayName("예외: 존재하지 않는 라이더") void throwExceptionWhenNotFoundRider() { @@ -349,6 +372,20 @@ void success() { assertThat(delivery.getDeliveryStatus()).isEqualTo(DeliveryStatus.DELIVERED); } + @Test + @DisplayName("성공: 배달 완료 알림 전송") + void successThenNotify() { + //given + given(riderRepository.findById(any())).willReturn(Optional.ofNullable(rider)); + given(deliveryRepository.findById(any())).willReturn(Optional.ofNullable(delivery)); + + //when + deliveryService.completeDelivery(completeDeliveryCommand); + + //then + then(notificationService).should().sendNotification(any()); + } + @Test @DisplayName("예외: 존재하지 않는 라이더") void throwExceptionWhenNotFoundRider() { diff --git a/src/test/java/com/prgrms/nabmart/domain/notification/NotificationTest.java b/src/test/java/com/prgrms/nabmart/domain/notification/NotificationTest.java new file mode 100644 index 00000000..d592311f --- /dev/null +++ b/src/test/java/com/prgrms/nabmart/domain/notification/NotificationTest.java @@ -0,0 +1,71 @@ +package com.prgrms.nabmart.domain.notification; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.prgrms.nabmart.domain.notification.exception.InvalidNotificationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class NotificationTest { + + @Nested + @DisplayName("notification 생성 시") + class NewNotificationTest { + + @Test + @DisplayName("성공") + void success() { + //given + String title = "제목"; + String content = "내용"; + + //when + Notification notification = Notification.builder() + .title(title) + .content(content) + .userId(1L) + .notificationType(NotificationType.DELIVERY) + .build(); + + //then + assertThat(notification.getTitle()).isEqualTo(title); + assertThat(notification.getContent()).isEqualTo(content); + } + + @Test + @DisplayName("예외: 제목 길이가 30자를 초과") + void throwExceptionWhenTitleLengthGGraterThan31() { + //given + String titleGT30 = "a".repeat(31); + + //when + //then + assertThatThrownBy(() -> Notification.builder() + .title(titleGT30) + .content("내용") + .userId(1L) + .notificationType(NotificationType.DELIVERY) + .build()) + .isInstanceOf(InvalidNotificationException.class); + } + + @Test + @DisplayName("예외: 내용 길이가 100자를 초과") + void throwExceptionWhenContentLengthGraterThan100() { + //given + String contentGT100 = "a".repeat(101); + + //when + //then + assertThatThrownBy(() -> Notification.builder() + .title("title") + .content(contentGT100) + .userId(1L) + .notificationType(NotificationType.DELIVERY) + .build()) + .isInstanceOf(InvalidNotificationException.class); + } + } +}