diff --git a/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java index 3dc1fb46..6a142f5d 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java @@ -48,6 +48,15 @@ public Optional findByNickname(final String nickname) { return memberEntity.map(memberPersistenceMapper::toDomain); } + // 검토자인 담당자들 조회 + @Override + public List findReviewers() { + List memberEntities = memberRepository.findByIsReviewerTrue(); + return memberEntities.stream() + .map(memberPersistenceMapper::toDomain) + .collect(Collectors.toList()); + } + @Override public void save(final Member member) { MemberEntity memberEntity = memberPersistenceMapper.toEntity(member); diff --git a/src/main/java/clap/server/adapter/outbound/persistense/NotificationPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/NotificationPersistenceAdapter.java index c2b0d0a1..8aad00a0 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/NotificationPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/NotificationPersistenceAdapter.java @@ -4,6 +4,7 @@ import clap.server.adapter.outbound.persistense.mapper.NotificationPersistenceMapper; import clap.server.adapter.outbound.persistense.repository.notification.NotificationRepository; import clap.server.application.mapper.NotificationMapper; +import clap.server.application.port.outbound.notification.CommandNotificationPort; import clap.server.application.port.outbound.notification.LoadNotificationPort; import clap.server.common.annotation.architecture.PersistenceAdapter; import clap.server.domain.model.notification.Notification; @@ -13,7 +14,7 @@ @PersistenceAdapter @RequiredArgsConstructor -public class NotificationPersistenceAdapter implements LoadNotificationPort { +public class NotificationPersistenceAdapter implements LoadNotificationPort, CommandNotificationPort { private final NotificationRepository notificationRepository; private final NotificationPersistenceMapper notificationPersistenceMapper; @@ -25,4 +26,9 @@ public Page findAllByReceiverId(Long receiverId, P .map(notificationPersistenceMapper::toDomain); return notificationList.map(NotificationMapper::toFindNoticeListResponse); } + + @Override + public void save(Notification notification) { + notificationRepository.save(notificationPersistenceMapper.toEntity(notification)); + } } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/entity/notification/NotificationEntity.java b/src/main/java/clap/server/adapter/outbound/persistense/entity/notification/NotificationEntity.java index 4fd79f5d..00e5b3ed 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/entity/notification/NotificationEntity.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/entity/notification/NotificationEntity.java @@ -34,12 +34,9 @@ public class NotificationEntity extends BaseTimeEntity { @JoinColumn(name = "receiver_id", nullable = false) private MemberEntity receiver; - @Column(nullable = false) + @Column(nullable = true) private String message; - @Column - private String taskTitle; - @Column(nullable = false) private boolean isRead; } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java new file mode 100644 index 00000000..3a7ba0a0 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -0,0 +1,23 @@ +package clap.server.adapter.outbound.persistense.evenlistener; + +import clap.server.application.service.notification.CreateNotificationService; +import clap.server.domain.model.notification.Notification; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +// 이벤트 발행 시 실행 +@RequiredArgsConstructor +@Component +public class CustomEventListener { + private final CreateNotificationService createNotificationService; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void handler(Notification request) { + createNotificationService.createNotification(request); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java index 892f0a88..0bbec88e 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java @@ -19,5 +19,7 @@ public interface MemberRepository extends JpaRepository { Optional findByStatusAndMemberId(MemberStatus memberStatus, Long memberId); Optional findByNickname(String nickname); + + List findByIsReviewerTrue(); } diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index c490d067..67adcacf 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -3,6 +3,7 @@ import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateAndUpdateTaskResponse; +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.mapper.TaskMapper; import clap.server.application.port.inbound.domain.CategoryService; import clap.server.application.port.inbound.domain.MemberService; @@ -12,11 +13,13 @@ import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; +import clap.server.domain.model.notification.Notification; import clap.server.domain.model.task.Attachment; import clap.server.domain.model.task.Category; import clap.server.domain.model.task.Task; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -30,10 +33,11 @@ public class CreateTaskService implements CreateTaskUsecase { private final CategoryService categoryService; private final CommandTaskPort commandTaskPort; private final CommandAttachmentPort commandAttachmentPort; + private final ApplicationEventPublisher applicationEventPublisher; @Override @Transactional - public CreateAndUpdateTaskResponse createTask(Long requesterId, CreateTaskRequest createTaskRequest) { + public CreateTaskResponse createTask(Long requesterId, CreateTaskRequest createTaskRequest) { Member member = memberService.findActiveMember(requesterId); Category category = categoryService.findById(createTaskRequest.categoryId()); Task task = Task.createTask(member, category, createTaskRequest.title(), createTaskRequest.description()); @@ -42,6 +46,24 @@ public CreateAndUpdateTaskResponse createTask(Long requesterId, CreateTaskReques List attachments = Attachment.createAttachments(savedTask, createTaskRequest.fileUrls()); commandAttachmentPort.saveAll(attachments); - return TaskMapper.toCreateAndUpdateTaskResponse(savedTask); + + // requestDto에 알림 데이터 mapping + + List reviewers = memberService.findReviewers(); + + + // 검토자들 각각에 대한 알림 생성 후 event 발행 + for (Member reviewer : reviewers) { + Notification notification = Notification.builder() + .task(savedTask) + .type(NotificationType.TASK_REQUESTED) + .receiver(reviewer) + .message(null) + .build(); + // publish event로 event 발행 + applicationEventPublisher.publishEvent(notification); + } + + return TaskMapper.toCreateTaskResponse(savedTask); } } \ No newline at end of file diff --git a/src/main/java/clap/server/application/port/inbound/domain/MemberService.java b/src/main/java/clap/server/application/port/inbound/domain/MemberService.java index a3d31fd3..d8d3e8d8 100644 --- a/src/main/java/clap/server/application/port/inbound/domain/MemberService.java +++ b/src/main/java/clap/server/application/port/inbound/domain/MemberService.java @@ -52,4 +52,8 @@ public List findActiveManagers() { } return activeManagers; } + + public List findReviewers() { + return loadMemberPort.findReviewers(); + } } diff --git a/src/main/java/clap/server/application/port/inbound/notification/CreateNotificationUsecase.java b/src/main/java/clap/server/application/port/inbound/notification/CreateNotificationUsecase.java new file mode 100644 index 00000000..147ced9e --- /dev/null +++ b/src/main/java/clap/server/application/port/inbound/notification/CreateNotificationUsecase.java @@ -0,0 +1,7 @@ +package clap.server.application.port.inbound.notification; + +import clap.server.domain.model.notification.Notification; + +public interface CreateNotificationUsecase { + void createNotification(Notification notification); +} diff --git a/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java b/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java index 2d719663..1eb3e576 100644 --- a/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java +++ b/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java @@ -19,4 +19,6 @@ public interface LoadMemberPort { Optional findByNickname(String nickname); + List findReviewers(); + } diff --git a/src/main/java/clap/server/application/port/outbound/notification/CommandNotificationPort.java b/src/main/java/clap/server/application/port/outbound/notification/CommandNotificationPort.java index 1b549fea..7761d367 100644 --- a/src/main/java/clap/server/application/port/outbound/notification/CommandNotificationPort.java +++ b/src/main/java/clap/server/application/port/outbound/notification/CommandNotificationPort.java @@ -4,5 +4,5 @@ import java.util.Optional; public interface CommandNotificationPort { - Optional save(Notification notification); + void save(Notification notification); } \ No newline at end of file diff --git a/src/main/java/clap/server/application/service/notification/CreateNotificationService.java b/src/main/java/clap/server/application/service/notification/CreateNotificationService.java new file mode 100644 index 00000000..840acb69 --- /dev/null +++ b/src/main/java/clap/server/application/service/notification/CreateNotificationService.java @@ -0,0 +1,20 @@ +package clap.server.application.service.notification; + +import clap.server.application.port.inbound.notification.CreateNotificationUsecase; +import clap.server.application.port.outbound.notification.CommandNotificationPort; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.domain.model.notification.Notification; +import lombok.RequiredArgsConstructor; + +@ApplicationService +@RequiredArgsConstructor +public class CreateNotificationService implements CreateNotificationUsecase { + + private final CommandNotificationPort commandNotificationPort; + + @Override + public void createNotification(Notification request) { + + commandNotificationPort.save(request); + } +} diff --git a/src/main/java/clap/server/domain/model/notification/Notification.java b/src/main/java/clap/server/domain/model/notification/Notification.java index 73feeaea..91ae10f9 100644 --- a/src/main/java/clap/server/domain/model/notification/Notification.java +++ b/src/main/java/clap/server/domain/model/notification/Notification.java @@ -1,14 +1,20 @@ package clap.server.domain.model.notification; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import clap.server.domain.model.common.BaseTime; import clap.server.domain.model.member.Member; +import clap.server.domain.model.task.Category; import clap.server.domain.model.task.Task; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -18,6 +24,14 @@ public class Notification extends BaseTime { private NotificationType type; private Member receiver; private String message; - private String taskTitle; private boolean isRead; + + @Builder + public Notification(Task task, NotificationType type, Member receiver, String message) { + this.task = task; + this.type = type; + this.receiver = receiver; + this.message = message; + this.isRead = false; + } } diff --git a/src/test/java/clap/server/notification/NotificationServiceTest.java b/src/test/java/clap/server/notification/NotificationServiceTest.java index dec1f21e..ae79627b 100644 --- a/src/test/java/clap/server/notification/NotificationServiceTest.java +++ b/src/test/java/clap/server/notification/NotificationServiceTest.java @@ -13,13 +13,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.testcontainers.junit.jupiter.Testcontainers; import java.time.LocalDateTime; import java.util.List; -import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; +@Testcontainers @ExtendWith(MockitoExtension.class) public class NotificationServiceTest {