From 7065d7cacf66b6e2cc1889f134574cccfc775cfb Mon Sep 17 00:00:00 2001 From: parkjaehak Date: Sun, 26 Jan 2025 20:16:38 +0900 Subject: [PATCH 01/24] =?UTF-8?q?CLAP-149=20HotFix:=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20secured=20=EC=98=A4=ED=83=88=EC=9E=90=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 --- .../inbound/web/task/ManagementTaskController.java | 8 ++++---- .../server/domain/model/notification/Notification.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java index 3b20d9ee..f3df7958 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java @@ -33,8 +33,8 @@ public class ManagementTaskController { private final ApprovalTaskUsecase approvalTaskUsecase; @Operation(summary = "작업 요청 생성") - @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) - @Secured({"ROLE_MANAGER, ROLE_USER"}) + @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Secured({"ROLE_MANAGER", "ROLE_USER"}) public ResponseEntity createTask( @RequestPart(name = "taskInfo") @Valid CreateTaskRequest createTaskRequest, @RequestPart(name = "attachment") @NotNull List attachments, @@ -44,8 +44,8 @@ public ResponseEntity createTask( } @Operation(summary = "작업 수정") - @PatchMapping(value = "/{taskId}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) - @Secured({"ROLE_MANAGER, ROLE_USER"}) + @PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @Secured({"ROLE_MANAGER", "ROLE_USER"}) public ResponseEntity updateTask( @PathVariable @NotNull Long taskId, @RequestPart(name = "taskInfo") @Valid UpdateTaskRequest updateTaskRequest, 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 46bd4f26..e96f9718 100644 --- a/src/main/java/clap/server/domain/model/notification/Notification.java +++ b/src/main/java/clap/server/domain/model/notification/Notification.java @@ -44,7 +44,7 @@ public static Notification createTaskNotification(Task task, Member reviewer, No .task(task) .type(type) .receiver(reviewer) - .message(null) + .message("테스트메세지") .build(); } } From d6d66b707fdf657334126f56b6b3204cf318efab Mon Sep 17 00:00:00 2001 From: parkjaehak Date: Sun, 26 Jan 2025 20:27:58 +0900 Subject: [PATCH 02/24] =?UTF-8?q?CLAP-149=20HotFix:=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20null=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clap/server/domain/model/notification/Notification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e96f9718..46bd4f26 100644 --- a/src/main/java/clap/server/domain/model/notification/Notification.java +++ b/src/main/java/clap/server/domain/model/notification/Notification.java @@ -44,7 +44,7 @@ public static Notification createTaskNotification(Task task, Member reviewer, No .task(task) .type(type) .receiver(reviewer) - .message("테스트메세지") + .message(null) .build(); } } From 2a96a08ffb298b366f4ab46c3bb52c55aa461080 Mon Sep 17 00:00:00 2001 From: parkjaehak Date: Sun, 26 Jan 2025 20:31:50 +0900 Subject: [PATCH 03/24] =?UTF-8?q?CLAP-149=20HotFix:=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20mediatype=20json=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/inbound/web/task/ManagementTaskController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java index f3df7958..7a4c0f19 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java @@ -33,7 +33,7 @@ public class ManagementTaskController { private final ApprovalTaskUsecase approvalTaskUsecase; @Operation(summary = "작업 요청 생성") - @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @Secured({"ROLE_MANAGER", "ROLE_USER"}) public ResponseEntity createTask( @RequestPart(name = "taskInfo") @Valid CreateTaskRequest createTaskRequest, @@ -44,7 +44,7 @@ public ResponseEntity createTask( } @Operation(summary = "작업 수정") - @PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + @PatchMapping(value = "/{taskId}", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE}) @Secured({"ROLE_MANAGER", "ROLE_USER"}) public ResponseEntity updateTask( @PathVariable @NotNull Long taskId, From 4ee201e7b2233efa0f3c6bb78451f6993d41bcc9 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 01:22:53 +0900 Subject: [PATCH 04/24] =?UTF-8?q?CLAP-150=20Feature:=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EC=B5=9C=EC=B4=88=20=EC=A0=91=EC=86=8D=20=EC=8B=9C=20Sse=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20logic=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubscribeEmitterController.java | 31 ++++++++++++++ .../persistense/SsePersistenceAdapter.java | 30 +++++++++++++ .../notification/EmitterRepository.java | 12 ++++++ .../notification/EmitterRepositoryImpl.java | 30 +++++++++++++ .../outbound/notification/CommandSsePort.java | 12 ++++++ .../notification/SubscribeSseService.java | 42 +++++++++++++++++++ 6 files changed, 157 insertions(+) create mode 100644 src/main/java/clap/server/adapter/inbound/web/notification/SubscribeEmitterController.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java create mode 100644 src/main/java/clap/server/application/port/outbound/notification/CommandSsePort.java create mode 100644 src/main/java/clap/server/application/service/notification/SubscribeSseService.java diff --git a/src/main/java/clap/server/adapter/inbound/web/notification/SubscribeEmitterController.java b/src/main/java/clap/server/adapter/inbound/web/notification/SubscribeEmitterController.java new file mode 100644 index 00000000..d2503ed7 --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/notification/SubscribeEmitterController.java @@ -0,0 +1,31 @@ +package clap.server.adapter.inbound.web.notification; + + +import clap.server.adapter.inbound.security.SecurityUserDetails; +import clap.server.application.port.inbound.notification.SubscribeSseUsecase; +import clap.server.common.annotation.architecture.WebAdapter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +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.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Tag(name = "SSE 관리 - 회원 등록(최초 접속시)") +@WebAdapter +@RestController +@RequestMapping("/api/sse") +@RequiredArgsConstructor +public class SubscribeEmitterController { + + private final SubscribeSseUsecase subscribeSseUsecase; + + @Operation(summary = "회원이 최초 접속 시 SSE(실시간 알림)에 연결하는 API") + @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter subscribe(@AuthenticationPrincipal SecurityUserDetails userInfo) { + return subscribeSseUsecase.subscribe(userInfo.getUserId()); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java new file mode 100644 index 00000000..a1eb4244 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java @@ -0,0 +1,30 @@ +package clap.server.adapter.outbound.persistense; + +import clap.server.adapter.outbound.persistense.repository.notification.EmitterRepository; +import clap.server.application.port.outbound.notification.CommandSsePort; +import clap.server.application.port.outbound.notification.LoadSsePort; +import clap.server.common.annotation.architecture.PersistenceAdapter; +import lombok.RequiredArgsConstructor; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@PersistenceAdapter +@RequiredArgsConstructor +public class SsePersistenceAdapter implements LoadSsePort, CommandSsePort { + + private final EmitterRepository emitterRepository; + + @Override + public void save(Long receiverId, SseEmitter emitter) { + emitterRepository.save(receiverId, emitter); + } + + @Override + public void delete(Long receiverId) { + emitterRepository.delete(receiverId); + } + + @Override + public SseEmitter get(Long receiverId) { + return emitterRepository.get(receiverId); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java new file mode 100644 index 00000000..e207d509 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java @@ -0,0 +1,12 @@ +package clap.server.adapter.outbound.persistense.repository.notification; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface EmitterRepository { + void save(Long id, SseEmitter emitter); + + void delete(Long id); + + SseEmitter get(Long id); + +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java new file mode 100644 index 00000000..9868595e --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java @@ -0,0 +1,30 @@ +package clap.server.adapter.outbound.persistense.repository.notification; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@RequiredArgsConstructor +public class EmitterRepositoryImpl implements EmitterRepository{ + + private final Map emitters = new ConcurrentHashMap<>(); + + @Override + public void save(Long id, SseEmitter emitter) { + emitters.put(id, emitter); + } + + @Override + public void delete(Long id) { + emitters.remove(id); + } + + @Override + public SseEmitter get(Long id) { + return emitters.get(id); + } +} diff --git a/src/main/java/clap/server/application/port/outbound/notification/CommandSsePort.java b/src/main/java/clap/server/application/port/outbound/notification/CommandSsePort.java new file mode 100644 index 00000000..465095f7 --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/notification/CommandSsePort.java @@ -0,0 +1,12 @@ +package clap.server.application.port.outbound.notification; + + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface CommandSsePort { + + void save(Long receiverId, SseEmitter emitter); + + void delete(Long receiverId); + +} diff --git a/src/main/java/clap/server/application/service/notification/SubscribeSseService.java b/src/main/java/clap/server/application/service/notification/SubscribeSseService.java new file mode 100644 index 00000000..7481ac59 --- /dev/null +++ b/src/main/java/clap/server/application/service/notification/SubscribeSseService.java @@ -0,0 +1,42 @@ +package clap.server.application.service.notification; + +import clap.server.application.port.inbound.notification.SubscribeSseUsecase; +import clap.server.application.port.outbound.notification.CommandSsePort; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.NotificationErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import java.io.IOException; + +@ApplicationService +@RequiredArgsConstructor +public class SubscribeSseService implements SubscribeSseUsecase { + + private final CommandSsePort commandSsePort; + + // SSE 연결 지속 시간 + private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60; + + @Override + public SseEmitter subscribe(Long memberId) { + SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); + + commandSsePort.save(memberId, emitter); + + // SSE가 작업을 완료하거나 종료되었을때 emitterRepository에서 해당 연결 값 삭제 + emitter.onCompletion(() -> commandSsePort.delete(memberId)); + emitter.onTimeout(() -> commandSsePort.delete(memberId)); + + try { + emitter.send(SseEmitter.event() + .id(String.valueOf(memberId)) + .data("Sse 연결 성공. [memberId = " + memberId + "]")); + } catch (IOException e) { + throw new ApplicationException(NotificationErrorCode.SSE_SEND_FAILED); + } + return emitter; + } + + +} From 774442a000ff8c3c05d504e63260e7b96f83cef2 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 01:23:44 +0900 Subject: [PATCH 05/24] =?UTF-8?q?CLAP-150=20Feature:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=ED=91=B8=EC=8B=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=EC=9C=BC=EB=A1=9C=20receiver=EC=97=90?= =?UTF-8?q?=EA=B2=8C=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=ED=95=98=EB=8A=94=20logic=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/notification/SseRequest.java | 11 +++++++ .../evenlistener/CustomEventListener.java | 8 +++++ .../application/Task/CreateTaskService.java | 11 +++++++ .../inbound/notification/SendSseUsecase.java | 8 +++++ .../service/notification/SendSseService.java | 31 +++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 src/main/java/clap/server/adapter/inbound/web/dto/notification/SseRequest.java create mode 100644 src/main/java/clap/server/application/port/inbound/notification/SendSseUsecase.java create mode 100644 src/main/java/clap/server/application/service/notification/SendSseService.java diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/notification/SseRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/notification/SseRequest.java new file mode 100644 index 00000000..4dd9224c --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/dto/notification/SseRequest.java @@ -0,0 +1,11 @@ +package clap.server.adapter.inbound.web.dto.notification; + +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; + +public record SseRequest( + String taskTitle, + NotificationType notificationType, + Long receiverId, + String message +) { +} 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 index 3a7ba0a0..fc473be9 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -1,6 +1,8 @@ package clap.server.adapter.outbound.persistense.evenlistener; +import clap.server.adapter.inbound.web.dto.notification.SseRequest; import clap.server.application.service.notification.CreateNotificationService; +import clap.server.application.service.notification.SendSseService; import clap.server.domain.model.notification.Notification; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -14,10 +16,16 @@ @Component public class CustomEventListener { private final CreateNotificationService createNotificationService; + private final SendSseService sendSseService; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Transactional(propagation = Propagation.REQUIRES_NEW) public void handler(Notification request) { createNotificationService.createNotification(request); } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleSseNotification(SseRequest sseRequest) { + sendSseService.send(sseRequest); + } } diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index e529fc4e..ccb2133b 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -1,5 +1,6 @@ package clap.server.application.Task; +import clap.server.adapter.inbound.web.dto.notification.SseRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskResponse; @@ -66,8 +67,18 @@ private void publishNotification(Task task){ // 검토자들 각각에 대한 알림 생성 후 event 발행 for (Member reviewer : reviewers) { + // 알림 저장 Notification notification = createTaskNotification(task, reviewer, NotificationType.TASK_REQUESTED); applicationEventPublisher.publishEvent(notification); + + // SSE 실시간 알림 전송 + SseRequest sseRequest = new SseRequest( + notification.getTask().getTitle(), + notification.getType(), + reviewer.getMemberId(), + null + ); + applicationEventPublisher.publishEvent(sseRequest); } } diff --git a/src/main/java/clap/server/application/port/inbound/notification/SendSseUsecase.java b/src/main/java/clap/server/application/port/inbound/notification/SendSseUsecase.java new file mode 100644 index 00000000..c7b4e5a3 --- /dev/null +++ b/src/main/java/clap/server/application/port/inbound/notification/SendSseUsecase.java @@ -0,0 +1,8 @@ +package clap.server.application.port.inbound.notification; + +import clap.server.adapter.inbound.web.dto.notification.SseRequest; + +public interface SendSseUsecase { + + void send(SseRequest request); +} diff --git a/src/main/java/clap/server/application/service/notification/SendSseService.java b/src/main/java/clap/server/application/service/notification/SendSseService.java new file mode 100644 index 00000000..2c37fc72 --- /dev/null +++ b/src/main/java/clap/server/application/service/notification/SendSseService.java @@ -0,0 +1,31 @@ +package clap.server.application.service.notification; + +import clap.server.adapter.inbound.web.dto.notification.SseRequest; +import clap.server.application.port.inbound.notification.SendSseUsecase; +import clap.server.application.port.outbound.notification.LoadSsePort; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.NotificationErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +@ApplicationService +@RequiredArgsConstructor +public class SendSseService implements SendSseUsecase { + + private final LoadSsePort loadSsePort; + + @Override + public void send(SseRequest request) { + SseEmitter sseEmitter = loadSsePort.get(request.receiverId()); + try { + sseEmitter.send(SseEmitter.event() + .id(String.valueOf(request.receiverId())) + .data(request)); + } catch (IOException e) { + throw new ApplicationException(NotificationErrorCode.SSE_SEND_FAILED); + } + } +} From 30916e228845e0863d7b2b1664b2fc83110c97b0 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 01:24:12 +0900 Subject: [PATCH 06/24] =?UTF-8?q?CLAP-150=20Fix:=20=EC=95=8C=EB=A6=BC=20Er?= =?UTF-8?q?rorCode=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/clap/server/exception/code/NotificationErrorCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/clap/server/exception/code/NotificationErrorCode.java b/src/main/java/clap/server/exception/code/NotificationErrorCode.java index f6805e61..c23d371c 100644 --- a/src/main/java/clap/server/exception/code/NotificationErrorCode.java +++ b/src/main/java/clap/server/exception/code/NotificationErrorCode.java @@ -9,6 +9,7 @@ public enum NotificationErrorCode implements BaseErrorCode { NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION_001", "알림을 찾을 수 없습니다"), + SSE_SEND_FAILED(HttpStatus.BAD_REQUEST, "NOTIFICATION_002", "SSE 초기 연결에 실패하였습니다"), ; private final HttpStatus httpStatus; From e19b5cc49867888469e341ea6b499ac9c92d1f3c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 01:38:39 +0900 Subject: [PATCH 07/24] =?UTF-8?q?CLAP-150=20Feature:=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20sseRepository=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=EC=9D=84=20=EC=A0=84=EC=86=A1=ED=95=98=EB=8A=94=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/outbound/notification/LoadSsePort.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/clap/server/application/port/outbound/notification/LoadSsePort.java diff --git a/src/main/java/clap/server/application/port/outbound/notification/LoadSsePort.java b/src/main/java/clap/server/application/port/outbound/notification/LoadSsePort.java new file mode 100644 index 00000000..88a085c4 --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/notification/LoadSsePort.java @@ -0,0 +1,8 @@ +package clap.server.application.port.outbound.notification; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface LoadSsePort { + + SseEmitter get(Long receiverId); +} From 80607c603c2c67f804b2180fa1222c42129470aa Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 02:33:42 +0900 Subject: [PATCH 08/24] =?UTF-8?q?CLAP-150=20Feature:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20webhook=20=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9E=90=EB=8F=99=20=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/webhook/SendWebhookRequest.java | 14 +++ .../persistense/EmailPersistenceAdapter.java | 86 +++++++++++++++++++ .../evenlistener/CustomEventListener.java | 8 ++ .../application/Task/CreateTaskService.java | 12 +++ .../port/outbound/webhook/SendEmailPort.java | 8 ++ .../service/webhook/SendEmailService.java | 17 ++++ 6 files changed, 145 insertions(+) create mode 100644 src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java create mode 100644 src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java create mode 100644 src/main/java/clap/server/application/service/webhook/SendEmailService.java diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java new file mode 100644 index 00000000..3c7a16e5 --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java @@ -0,0 +1,14 @@ +package clap.server.adapter.inbound.web.dto.webhook; + +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; + +public record SendWebhookRequest( + + String email, + NotificationType notificationType, + String taskName, + String senderName, + String message, + String commenterName +) { +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java new file mode 100644 index 00000000..971842f2 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java @@ -0,0 +1,86 @@ +package clap.server.adapter.outbound.persistense; + +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; +import clap.server.application.port.outbound.webhook.SendEmailPort; +import clap.server.common.annotation.architecture.PersistenceAdapter; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.NotificationErrorCode; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +@PersistenceAdapter +@RequiredArgsConstructor +public class EmailPersistenceAdapter implements SendEmailPort { + + private final SpringTemplateEngine templateEngine; + private final JavaMailSender mailSender; + + @Override + public void sendEmail(SendWebhookRequest request) { + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + String body; + Context context = new Context(); + + if (request.notificationType() == NotificationType.TASK_REQUESTED) { + helper.setTo(request.email()); + helper.setSubject("[TaskFlow 알림] 신규 작업이 요청되었습니다."); + + context.setVariable("receiverName", request.senderName()); + context.setVariable("title", request.taskName()); + + body = templateEngine.process("task-request", context); + } + else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { + helper.setTo(request.email()); + helper.setSubject("[TaskFlow 알림] 작업 상태가 변경되었습니다."); + + context.setVariable("status", request.message()); + context.setVariable("title", request.taskName()); + + body = templateEngine.process("status-switch", context); + } + + else if (request.notificationType() == NotificationType.PROCESSOR_CHANGED) { + helper.setTo(request.email()); + helper.setSubject("[TaskFlow 알림] 작업 담당자가 변경되었습니다."); + + context.setVariable("processorName", request.message()); + context.setVariable("title", request.taskName()); + + body = templateEngine.process("processor-change", context); + } + + else if (request.notificationType() == NotificationType.PROCESSOR_ASSIGNED) { + helper.setTo(request.email()); + helper.setSubject("[TaskFlow 알림] 작업 담당자가 지정되었습니다."); + + context.setVariable("processorName", request.message()); + context.setVariable("title", request.taskName()); + + body = templateEngine.process("processor-assign", context); + } + + else { + helper.setTo(request.email()); + helper.setSubject("[TaskFlow 알림] 댓글이 작성되었습니다."); + + context.setVariable("comment", request.message()); + context.setVariable("title", request.taskName()); + + body = templateEngine.process("comment", context); + } + + helper.setText(body, true); + mailSender.send(mimeMessage); + } catch (Exception e) { + throw new ApplicationException(NotificationErrorCode.EMAIL_SEND_FAILED); + } + } +} 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 index fc473be9..ddd6cc33 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -1,8 +1,10 @@ package clap.server.adapter.outbound.persistense.evenlistener; import clap.server.adapter.inbound.web.dto.notification.SseRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.application.service.notification.CreateNotificationService; import clap.server.application.service.notification.SendSseService; +import clap.server.application.service.webhook.SendEmailService; import clap.server.domain.model.notification.Notification; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -17,6 +19,7 @@ public class CustomEventListener { private final CreateNotificationService createNotificationService; private final SendSseService sendSseService; + private final SendEmailService sendEmailService; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -28,4 +31,9 @@ public void handler(Notification request) { public void handleSseNotification(SseRequest sseRequest) { sendSseService.send(sseRequest); } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleEmailSend(SendWebhookRequest request) { + sendEmailService.sendEmail(request); + } } diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index ccb2133b..27d5148f 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -4,6 +4,7 @@ import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskResponse; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.infrastructure.s3.S3UploadAdapter; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.mapper.AttachmentMapper; @@ -79,6 +80,17 @@ private void publishNotification(Task task){ null ); applicationEventPublisher.publishEvent(sseRequest); + + //Email Webhook 실시간 전송 + SendWebhookRequest sendWebhookRequest = new SendWebhookRequest( + reviewer.getMemberInfo().getEmail(), + NotificationType.TASK_REQUESTED, + task.getTitle(), + task.getRequester().getNickname(), + null, + null + ); + applicationEventPublisher.publishEvent(sendWebhookRequest); } } diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java new file mode 100644 index 00000000..6114bff5 --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java @@ -0,0 +1,8 @@ +package clap.server.application.port.outbound.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; + +public interface SendEmailPort { + + void sendEmail(SendWebhookRequest request); +} \ No newline at end of file diff --git a/src/main/java/clap/server/application/service/webhook/SendEmailService.java b/src/main/java/clap/server/application/service/webhook/SendEmailService.java new file mode 100644 index 00000000..725989b7 --- /dev/null +++ b/src/main/java/clap/server/application/service/webhook/SendEmailService.java @@ -0,0 +1,17 @@ +package clap.server.application.service.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.application.port.outbound.webhook.SendEmailPort; +import clap.server.common.annotation.architecture.ApplicationService; +import lombok.RequiredArgsConstructor; + +@ApplicationService +@RequiredArgsConstructor +public class SendEmailService { + + private final SendEmailPort port; + + public void sendEmail(SendWebhookRequest request) { + port.sendEmail(request); + } +} From e47531f7b84a6c5e4bdba254396d03434306df19 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 02:34:23 +0900 Subject: [PATCH 09/24] =?UTF-8?q?CLAP-150=20Feature:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20webhook=20=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=EC=9E=90=20=EC=84=A4=EC=A0=95=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=B4=88=EA=B8=B0=20=EC=84=B8=ED=8C=85(?= =?UTF-8?q?=EB=85=BC=EC=9D=98=20=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f4527cd4..617a18d5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,20 @@ spring: name: taskflow web.resources.add-mappings: false + # 구글 알림 이메일 발신자 정보 설정(논의 필요) + mail: + host: smtp.gmail.com + port: 587 + username: leegd120@gmail.com + password: znlictzarqurxlla + properties: + mail: + smtp: + auth: true + starttls: + enable: true + + server: port: ${APPLICATION_PORT:8080} From 84279a20e3cdc89c889189bdeaaa9e3104b382ee Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 03:41:19 +0900 Subject: [PATCH 10/24] =?UTF-8?q?CLAP-150=20Feature:=20KakaoWork=20?= =?UTF-8?q?=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KakaoWorkPersistenceAdapter.java | 64 +++ .../ObjectBlockPersistenceAdapter.java | 374 ++++++++++++++++++ .../evenlistener/CustomEventListener.java | 9 +- .../application/Task/CreateTaskService.java | 15 + .../outbound/webhook/MakeObjectBlockPort.java | 17 + 5 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java create mode 100644 src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java diff --git a/src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java new file mode 100644 index 00000000..d9cf0503 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java @@ -0,0 +1,64 @@ +package clap.server.adapter.outbound.persistense; + +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; +import clap.server.application.port.outbound.webhook.SendKaKaoWorkPort; +import clap.server.common.annotation.architecture.PersistenceAdapter; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.NotificationErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.web.client.RestTemplate; + +@PersistenceAdapter +@RequiredArgsConstructor +public class KakaoWorkPersistenceAdapter implements SendKaKaoWorkPort { + + private static final String KAKAOWORK_URL = "https://api.kakaowork.com/v1/messages.send_by_email"; + private static final String KAKAOWORK_AUTH = "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d"; + + private final ObjectBlockPersistenceAdapter makeObjectBlock; + + @Override + public void sendKakaoWord(SendKakaoWorkRequest request) { + RestTemplate restTemplate = new RestTemplate(); + + // Payload 생성 + String payload = null; + if (request.notificationType() == NotificationType.TASK_REQUESTED) { + payload = makeObjectBlock.makeTaskRequestBlock(request); + } + else if (request.notificationType() == NotificationType.PROCESSOR_ASSIGNED) { + payload = makeObjectBlock.makeNewProcessorBlock(request); + } + else if (request.notificationType() == NotificationType.PROCESSOR_CHANGED) { + payload = makeObjectBlock.makeProcessorChangeBlock(request); + } + else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { + payload = makeObjectBlock.makeTaskStatusBlock(request); + } + else { + payload = makeObjectBlock.makeCommentBlock(request); + } + + // HTTP 요청 헤더 설정 + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + headers.add("Authorization", KAKAOWORK_AUTH); + + // HTTP 요청 엔터티 생성 + HttpEntity entity = new HttpEntity<>(payload, headers); + + try { + // Post 요청 전송 + restTemplate.exchange( + KAKAOWORK_URL, HttpMethod.POST, entity, String.class + ); + + } catch (Exception e) { + throw new ApplicationException(NotificationErrorCode.KAKAO_SEND_FAILED); + } + } +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java new file mode 100644 index 00000000..ea0fd077 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java @@ -0,0 +1,374 @@ +package clap.server.adapter.outbound.persistense; + + +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.persistense.repository.notification.NotificationRepository; +import clap.server.application.port.outbound.webhook.MakeObjectBlockPort; +import clap.server.common.annotation.architecture.PersistenceAdapter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +@PersistenceAdapter +@RequiredArgsConstructor +public class ObjectBlockPersistenceAdapter implements MakeObjectBlockPort { + + private final ObjectMapper objectMapper; + private final NotificationRepository notificationRepository; + + @Override + public String makeTaskRequestBlock(SendKakaoWorkRequest request) { + // Blocks 데이터 생성 + Object[] blocks = new Object[]{ + // Header 블록 + Map.of( + "type", "header", + "text", "TaskFlow 작업 요청 알림", + "style", "blue" + ), + // Text 블록 1 + Map.of( + "type", "text", + "text", "TaskFlow 작업 요청 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", "새로운 작업이 요청되었습니다.", + "bold", true + ) + } + ), + // Text 블록 2: 제목 변수 사용 + Map.of( + "type", "text", + "text", "TaskFlow 작업 요청 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - Task 제목 : " + request.taskName(), + "bold", false + ) + } + ), + // Text 블록 3: 요청자 변수 사용 + Map.of( + "type", "text", + "text", "TaskFlow 작업 요청 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 요청자 : " + request.senderName(), + "bold", false + ) + } + ), + // Button 블록 + Map.of( + "type", "button", + "text", "확인하기", + "style", "default", + "action", Map.of( + "type", "open_system_browser", + "name", "button1", + "value", "http://example.com/details/999" + ) + ) + }; + + String payload; + try { + payload = "{" + + "\"email\":\"" + request.email() + "\"," + + "\"text\": \"신규 작업 요청 알림\"," + // fallback 메시지 + "\"blocks\":" + objectMapper.writeValueAsString(blocks) + + "}"; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return payload; + } + + @Override + public String makeNewProcessorBlock(SendKakaoWorkRequest request) { + Object[] blocks = new Object[]{ + Map.of( + "type", "header", + "text", "TaskFlow 알림 서비스", + "style", "blue" + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 선정 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", "TaskFlow 담당자 선정 알림", + "bold", true + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 선정 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - Task 제목 : " + request.taskName(), + "bold", false + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 선정 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 담당자 : " + request.message(), + "bold", false + ) + } + ), + Map.of( + "type", "button", + "text", "확인하기", + "style", "default", + "action", Map.of( + "type", "open_system_browser", + "name", "button1", + "value", "http://example.com/details/999" + ) + ) + }; + + String payload; + try { + payload = "{" + + "\"email\":\"" + request.email() + "\"," + + "\"text\":\"작업 담당자 할당 알림\"," + // fallback 메시지 + "\"blocks\":" + objectMapper.writeValueAsString(blocks) + + "}"; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return payload; + } + + @Override + public String makeProcessorChangeBlock(SendKakaoWorkRequest request) { + Object[] blocks = new Object[]{ + Map.of( + "type", "header", + "text", "TaskFlow 알림 서비스", + "style", "blue" + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", "TaskFlow 담당자 변경 알림", + "bold", true + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - Task 제목 : " + request.taskName(), + "bold", false + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 담당자 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 담당자 : " + request.message(), + "bold", false + ) + } + ), + Map.of( + "type", "button", + "text", "확인하기", + "style", "default", + "action", Map.of( + "type", "open_system_browser", + "name", "button1", + "value", "http://example.com/details/999" + ) + ) + }; + + String payload; + try { + payload = "{" + + "\"email\":\"" + request.email() + "\"," + + "\"text\":\"작업 담당자 변경 알림\"," + // fallback 메시지 + "\"blocks\":" + objectMapper.writeValueAsString(blocks) + + "}"; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + + return payload; + } + + @Override + public String makeCommentBlock(SendKakaoWorkRequest request) { + Object[] blocks = new Object[]{ + Map.of( + "type", "header", + "text", "TaskFlow 알림 서비스", + "style", "blue" + ), + Map.of( + "type", "text", + "text", "TaskFlow 댓글 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", "TaskFlow 댓글 알림", + "bold", true + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 댓글 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - Task Title : " + request.taskName(), + "bold", false + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 댓글 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 작성자 : " + request.commenterName(), + "bold", false + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 댓글 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 내용 : " + request.message(), + "bold", false + ) + } + ), + Map.of( + "type", "button", + "text", "확인하기", + "style", "default", + "action", Map.of( + "type", "open_system_browser", + "name", "button1", + "value", "http://example.com/details/999" + ) + ) + }; + + String payload; + try { + payload = "{" + + "\"email\":\"" + request.email() + "\"," + + "\"text\":\"댓글 알림\"," + // fallback 메시지 + "\"blocks\":" + objectMapper.writeValueAsString(blocks) + + "}"; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return payload; + } + + @Override + public String makeTaskStatusBlock(SendKakaoWorkRequest request) { + Object[] blocks = new Object[]{ + Map.of( + "type", "header", + "text", "TaskFlow 알림 서비스", + "style", "blue" + ), + Map.of( + "type", "text", + "text", "TaskFlow 작업 상태 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", "TaskFlow 작업 상태 변경 알림", + "bold", true + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 상태 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - Task Title : " + request.taskName(), + "bold", false + ) + } + ), + Map.of( + "type", "text", + "text", "TaskFlow 작업 상태 변경 알림", + "inlines", new Object[]{ + Map.of( + "type", "styled", + "text", " - 상태 : " + request.message(), + "bold", false + ) + } + ), + Map.of( + "type", "button", + "text", "확인하기", + "style", "default", + "action", Map.of( + "type", "open_system_browser", + "name", "button1", + "value", "http://example.com/details/999" + ) + ) + }; + + String payload; + try { + payload = "{" + + "\"email\":\"" + request.email() + "\"," + + "\"text\":\"작업 상태 변경 알림\"," + // fallback 메시지 + "\"blocks\":" + objectMapper.writeValueAsString(blocks) + + "}"; + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return payload; + } +} 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 index ddd6cc33..8d86f892 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -1,10 +1,12 @@ package clap.server.adapter.outbound.persistense.evenlistener; import clap.server.adapter.inbound.web.dto.notification.SseRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.application.service.notification.CreateNotificationService; import clap.server.application.service.notification.SendSseService; import clap.server.application.service.webhook.SendEmailService; +import clap.server.application.service.webhook.SendKaKaoWorkService; import clap.server.domain.model.notification.Notification; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -20,9 +22,9 @@ public class CustomEventListener { private final CreateNotificationService createNotificationService; private final SendSseService sendSseService; private final SendEmailService sendEmailService; + private final SendKaKaoWorkService sendKaKaoWorkService; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - @Transactional(propagation = Propagation.REQUIRES_NEW) public void handler(Notification request) { createNotificationService.createNotification(request); } @@ -36,4 +38,9 @@ public void handleSseNotification(SseRequest sseRequest) { public void handleEmailSend(SendWebhookRequest request) { sendEmailService.sendEmail(request); } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleKakaoWorkSend(SendKakaoWorkRequest kakaoWork) { + sendKaKaoWorkService.sendKaKaoWork(kakaoWork); + } } diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index 27d5148f..f4297a6f 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -4,6 +4,7 @@ import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskResponse; +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.infrastructure.s3.S3UploadAdapter; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; @@ -15,6 +16,7 @@ import clap.server.application.port.outbound.task.CommandAttachmentPort; import clap.server.application.port.outbound.task.CommandTaskPort; +import clap.server.application.service.webhook.SendKaKaoWorkService; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; import clap.server.domain.model.notification.Notification; @@ -42,6 +44,7 @@ public class CreateTaskService implements CreateTaskUsecase { private final CommandTaskPort commandTaskPort; private final CommandAttachmentPort commandAttachmentPort; private final S3UploadAdapter s3UploadAdapter; + private final SendKaKaoWorkService sendKaKaoWorkService; private final ApplicationEventPublisher applicationEventPublisher; @Override @@ -91,6 +94,18 @@ private void publishNotification(Task task){ null ); applicationEventPublisher.publishEvent(sendWebhookRequest); + + //Kakao Webhook 실시간 전송 + SendKakaoWorkRequest sendKakaoWorkRequest = new SendKakaoWorkRequest( + reviewer.getMemberInfo().getEmail(), + NotificationType.TASK_REQUESTED, + task.getTitle(), + task.getRequester().getNickname(), + null, + null + ); + //Kakao Webhook 실시간 전송 + applicationEventPublisher.publishEvent(sendKakaoWorkRequest); } } diff --git a/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java b/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java new file mode 100644 index 00000000..45bbeec8 --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java @@ -0,0 +1,17 @@ +package clap.server.application.port.outbound.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; + +public interface MakeObjectBlockPort { + + String makeTaskRequestBlock(SendKakaoWorkRequest request); + + String makeNewProcessorBlock(SendKakaoWorkRequest request); + + String makeProcessorChangeBlock(SendKakaoWorkRequest request); + + String makeCommentBlock(SendKakaoWorkRequest request); + + String makeTaskStatusBlock(SendKakaoWorkRequest request); +} From 3c0dbc72158ca5559f5f248459415fc88b5faa74 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 03:42:13 +0900 Subject: [PATCH 11/24] =?UTF-8?q?CLAP-150=20Feature:=20KakaoWork=20?= =?UTF-8?q?=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/webhook/SendKaKaoWorkService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java diff --git a/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java new file mode 100644 index 00000000..9080d572 --- /dev/null +++ b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java @@ -0,0 +1,19 @@ +package clap.server.application.service.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.persistense.KakaoWorkPersistenceAdapter; +import clap.server.application.port.outbound.webhook.SendKaKaoWorkPort; +import clap.server.common.annotation.architecture.ApplicationService; +import lombok.RequiredArgsConstructor; + +@ApplicationService +@RequiredArgsConstructor +public class SendKaKaoWorkService { + + private final SendKaKaoWorkPort sendKaKaoWorkPort; + + public void sendKaKaoWork(SendKakaoWorkRequest request) { + sendKaKaoWorkPort.sendKakaoWord(request); + } +} From 81370982465b35c58d92fe9874223a151cc9a4b7 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 03:42:25 +0900 Subject: [PATCH 12/24] =?UTF-8?q?CLAP-150=20Feature:=20KakaoWork=20?= =?UTF-8?q?=ED=91=B8=EC=8B=9C=20=EC=95=8C=EB=A6=BC=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/webhook/SendKakaoWorkRequest.java | 14 ++++++++++++++ .../port/outbound/webhook/SendKaKaoWorkPort.java | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java create mode 100644 src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java new file mode 100644 index 00000000..11c0c841 --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java @@ -0,0 +1,14 @@ +package clap.server.adapter.inbound.web.dto.webhook; + +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; + +public record SendKakaoWorkRequest( + + String email, + NotificationType notificationType, + String taskName, + String senderName, + String message, + String commenterName +) { +} diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java new file mode 100644 index 00000000..15d6c54b --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java @@ -0,0 +1,9 @@ +package clap.server.application.port.outbound.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; + +public interface SendKaKaoWorkPort { + + void sendKakaoWord(SendKakaoWorkRequest request); +} From a519d068f65f8c9cecfd1443d124494c5e005343 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 04:05:21 +0900 Subject: [PATCH 13/24] =?UTF-8?q?CLAP-150=20Feature:=20Agit=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20Webhook=20=EC=95=8C=EB=A6=BC=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/dto/webhook/SendAgitRequest.java | 13 +++++ .../persistense/AgitPersistenceAdapter.java | 54 +++++++++++++++++++ .../evenlistener/CustomEventListener.java | 12 ++++- .../application/Task/CreateTaskService.java | 13 ++++- .../port/outbound/webhook/SendAgitPort.java | 8 +++ .../service/webhook/SendAgitService.java | 17 ++++++ 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java create mode 100644 src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java create mode 100644 src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java create mode 100644 src/main/java/clap/server/application/service/webhook/SendAgitService.java diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java new file mode 100644 index 00000000..8ac3b4de --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java @@ -0,0 +1,13 @@ +package clap.server.adapter.inbound.web.dto.webhook; + +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; + +public record SendAgitRequest( + String email, + NotificationType notificationType, + String taskName, + String senderName, + String message, + String commenterName +) { +} diff --git a/src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java new file mode 100644 index 00000000..a88c6d72 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java @@ -0,0 +1,54 @@ +package clap.server.adapter.outbound.persistense; + +import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; +import clap.server.application.port.outbound.webhook.SendAgitPort; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.common.annotation.architecture.PersistenceAdapter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + + +@PersistenceAdapter +@RequiredArgsConstructor +public class AgitPersistenceAdapter implements SendAgitPort { + + private static final String AGITWEBHOOK_URL = "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1"; + + @Override + public void sendAgit(SendAgitRequest request) { + RestTemplate restTemplate = new RestTemplate(); + + String message = null; + if (request.notificationType() == NotificationType.TASK_REQUESTED) { + message = request.taskName() + " 작업이 요청되었습니다."; + } + else if (request.notificationType() == NotificationType.COMMENT) { + message = request.taskName() + " 작업에 " + request.commenterName() + "님이 댓글을 남기셨습니다."; + } + else if (request.notificationType() == NotificationType.PROCESSOR_ASSIGNED) { + message = request.taskName() + " 작업에 담당자(" + request.message() + ")가 배정되었습니다."; + } + else if (request.notificationType() == NotificationType.PROCESSOR_CHANGED) { + message = request.taskName() + " 작업의 담당자가 " + request.message() + "로 변경되었습니다."; + } + else { + message = request.taskName() + " 작업의 상태가 " + request.message() + "로 변경되었습니다"; + } + + String payload = "{\"text\":\"" + message + "\"}"; + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + + HttpEntity entity = new HttpEntity<>(payload, headers); + + // Post 요청 + restTemplate.exchange(AGITWEBHOOK_URL, HttpMethod.POST, entity, String.class); + } +} 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 index 8d86f892..d2fbed26 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -1,10 +1,12 @@ package clap.server.adapter.outbound.persistense.evenlistener; import clap.server.adapter.inbound.web.dto.notification.SseRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.application.service.notification.CreateNotificationService; import clap.server.application.service.notification.SendSseService; +import clap.server.application.service.webhook.SendAgitService; import clap.server.application.service.webhook.SendEmailService; import clap.server.application.service.webhook.SendKaKaoWorkService; import clap.server.domain.model.notification.Notification; @@ -23,6 +25,7 @@ public class CustomEventListener { private final SendSseService sendSseService; private final SendEmailService sendEmailService; private final SendKaKaoWorkService sendKaKaoWorkService; + private final SendAgitService agitService; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handler(Notification request) { @@ -35,12 +38,17 @@ public void handleSseNotification(SseRequest sseRequest) { } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void handleEmailSend(SendWebhookRequest request) { - sendEmailService.sendEmail(request); + public void handleEmailSend(SendWebhookRequest email) { + sendEmailService.sendEmail(email); } @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleKakaoWorkSend(SendKakaoWorkRequest kakaoWork) { sendKaKaoWorkService.sendKaKaoWork(kakaoWork); } + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void handleAgitSend(SendAgitRequest agit) { + agitService.sendAgit(agit); + } } diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index f4297a6f..cac075d7 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -4,6 +4,7 @@ import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskResponse; +import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.infrastructure.s3.S3UploadAdapter; @@ -104,8 +105,18 @@ private void publishNotification(Task task){ null, null ); - //Kakao Webhook 실시간 전송 applicationEventPublisher.publishEvent(sendKakaoWorkRequest); + + //아지트 Webhook 실시간 전송 + SendAgitRequest sendAgitRequest = new SendAgitRequest( + reviewer.getMemberInfo().getEmail(), + NotificationType.TASK_REQUESTED, + task.getTitle(), + task.getRequester().getNickname(), + null, + null + ); + applicationEventPublisher.publishEvent(sendAgitRequest); } } diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java new file mode 100644 index 00000000..a807ff9d --- /dev/null +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java @@ -0,0 +1,8 @@ +package clap.server.application.port.outbound.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; +import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; + +public interface SendAgitPort { + void sendAgit(SendAgitRequest request); +} diff --git a/src/main/java/clap/server/application/service/webhook/SendAgitService.java b/src/main/java/clap/server/application/service/webhook/SendAgitService.java new file mode 100644 index 00000000..6a135eae --- /dev/null +++ b/src/main/java/clap/server/application/service/webhook/SendAgitService.java @@ -0,0 +1,17 @@ +package clap.server.application.service.webhook; + +import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; +import clap.server.application.port.outbound.webhook.SendAgitPort; +import clap.server.common.annotation.architecture.ApplicationService; +import lombok.RequiredArgsConstructor; + +@ApplicationService +@RequiredArgsConstructor +public class SendAgitService { + + private final SendAgitPort agitPort; + + public void sendAgit(SendAgitRequest request) { + agitPort.sendAgit(request); + } +} From 47d45841f0051c7bb608e3271886d5503d3916d9 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 04:06:03 +0900 Subject: [PATCH 14/24] =?UTF-8?q?CLAP-150=20Feature:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=A0=84=EC=86=A1=EC=97=90=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20html=20=ED=8C=8C=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/comment.html | 87 +++++++++++++++++++ .../resources/templates/processor-assign.html | 87 +++++++++++++++++++ .../resources/templates/processor-change.html | 87 +++++++++++++++++++ .../resources/templates/status-switch.html | 87 +++++++++++++++++++ .../resources/templates/task-request.html | 87 +++++++++++++++++++ 5 files changed, 435 insertions(+) create mode 100644 src/main/resources/templates/comment.html create mode 100644 src/main/resources/templates/processor-assign.html create mode 100644 src/main/resources/templates/processor-change.html create mode 100644 src/main/resources/templates/status-switch.html create mode 100644 src/main/resources/templates/task-request.html diff --git a/src/main/resources/templates/comment.html b/src/main/resources/templates/comment.html new file mode 100644 index 00000000..08f63efc --- /dev/null +++ b/src/main/resources/templates/comment.html @@ -0,0 +1,87 @@ + + + + + Notion Notification + + + + + + diff --git a/src/main/resources/templates/processor-assign.html b/src/main/resources/templates/processor-assign.html new file mode 100644 index 00000000..0de7fd85 --- /dev/null +++ b/src/main/resources/templates/processor-assign.html @@ -0,0 +1,87 @@ + + + + + Notion Notification + + + + + + diff --git a/src/main/resources/templates/processor-change.html b/src/main/resources/templates/processor-change.html new file mode 100644 index 00000000..7806a74b --- /dev/null +++ b/src/main/resources/templates/processor-change.html @@ -0,0 +1,87 @@ + + + + + Notion Notification + + + + + + diff --git a/src/main/resources/templates/status-switch.html b/src/main/resources/templates/status-switch.html new file mode 100644 index 00000000..374233d2 --- /dev/null +++ b/src/main/resources/templates/status-switch.html @@ -0,0 +1,87 @@ + + + + + Notion Notification + + + + + + diff --git a/src/main/resources/templates/task-request.html b/src/main/resources/templates/task-request.html new file mode 100644 index 00000000..5c349ca5 --- /dev/null +++ b/src/main/resources/templates/task-request.html @@ -0,0 +1,87 @@ + + + + + Notion Notification + + + + + + From 342c9d85d91230fa9925e5d1dd2d5ccf71812870 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 04:06:31 +0900 Subject: [PATCH 15/24] =?UTF-8?q?CLAP-150=20Fix:=20Error=20Code=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/clap/server/exception/code/NotificationErrorCode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/clap/server/exception/code/NotificationErrorCode.java b/src/main/java/clap/server/exception/code/NotificationErrorCode.java index c23d371c..237b3c96 100644 --- a/src/main/java/clap/server/exception/code/NotificationErrorCode.java +++ b/src/main/java/clap/server/exception/code/NotificationErrorCode.java @@ -10,6 +10,9 @@ public enum NotificationErrorCode implements BaseErrorCode { NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION_001", "알림을 찾을 수 없습니다"), SSE_SEND_FAILED(HttpStatus.BAD_REQUEST, "NOTIFICATION_002", "SSE 초기 연결에 실패하였습니다"), + EMAIL_SEND_FAILED(HttpStatus.BAD_REQUEST, "NOTIFICATION_003", "이메일 알림 전송에 실패하였습니다"), + KAKAO_SEND_FAILED(HttpStatus.BAD_REQUEST, "NOTIFICATION_004", "카카오워크 알림 전송에 실패하였습니다"), + AGIT_SEND_FAILED(HttpStatus.BAD_REQUEST, "NOTIFICATION_005", "아지트 알림 전송에 실패하였습니다"), ; private final HttpStatus httpStatus; From 71af703668f30cd86c8e165ea0635fc10bfbedbd Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 04:06:56 +0900 Subject: [PATCH 16/24] CLAP-150 Fix: Git Conflict --- .../port/inbound/notification/SubscribeSseUsecase.java | 8 ++++++++ .../application/service/notification/SendSseService.java | 2 +- .../service/notification/SubscribeSseService.java | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/main/java/clap/server/application/port/inbound/notification/SubscribeSseUsecase.java diff --git a/src/main/java/clap/server/application/port/inbound/notification/SubscribeSseUsecase.java b/src/main/java/clap/server/application/port/inbound/notification/SubscribeSseUsecase.java new file mode 100644 index 00000000..20a270c4 --- /dev/null +++ b/src/main/java/clap/server/application/port/inbound/notification/SubscribeSseUsecase.java @@ -0,0 +1,8 @@ +package clap.server.application.port.inbound.notification; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface SubscribeSseUsecase { + + SseEmitter subscribe(Long memberId); +} diff --git a/src/main/java/clap/server/application/service/notification/SendSseService.java b/src/main/java/clap/server/application/service/notification/SendSseService.java index 2c37fc72..4139b53d 100644 --- a/src/main/java/clap/server/application/service/notification/SendSseService.java +++ b/src/main/java/clap/server/application/service/notification/SendSseService.java @@ -24,7 +24,7 @@ public void send(SseRequest request) { sseEmitter.send(SseEmitter.event() .id(String.valueOf(request.receiverId())) .data(request)); - } catch (IOException e) { + } catch (Exception e) { throw new ApplicationException(NotificationErrorCode.SSE_SEND_FAILED); } } diff --git a/src/main/java/clap/server/application/service/notification/SubscribeSseService.java b/src/main/java/clap/server/application/service/notification/SubscribeSseService.java index 7481ac59..558e4aa5 100644 --- a/src/main/java/clap/server/application/service/notification/SubscribeSseService.java +++ b/src/main/java/clap/server/application/service/notification/SubscribeSseService.java @@ -32,7 +32,7 @@ public SseEmitter subscribe(Long memberId) { emitter.send(SseEmitter.event() .id(String.valueOf(memberId)) .data("Sse 연결 성공. [memberId = " + memberId + "]")); - } catch (IOException e) { + } catch (Exception e) { throw new ApplicationException(NotificationErrorCode.SSE_SEND_FAILED); } return emitter; From d0a9ce9234c4203b4243226c200c96af3b2b5d70 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 04:17:43 +0900 Subject: [PATCH 17/24] =?UTF-8?q?CLAP-150=20Fix:=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=95=8C=EB=A6=BC=20=EC=A0=84=EC=86=A1=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20thymeleaf=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index c17d55e7..962217f7 100644 --- a/build.gradle +++ b/build.gradle @@ -90,6 +90,9 @@ dependencies { // Email Sender implementation 'org.springframework.boot:spring-boot-starter-mail' + // Thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + // Spring aop implementation 'org.springframework.boot:spring-boot-starter-aop' From 00dcadd05d576ecda635c00bc9816e54de6fa1c4 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 12:59:48 +0900 Subject: [PATCH 18/24] =?UTF-8?q?CLAP-150=20Fix:=20test=20yml=EC=97=90=20s?= =?UTF-8?q?pring=20mailsender=20=EC=B4=88=EA=B8=B0=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 683f2af6..75c4575f 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -14,6 +14,18 @@ spring: hibernate: ddl-auto: create + mail: + host: smtp.gmail.com + port: 587 + username: leegd120@gmail.com + password: znlictzarqurxlla + properties: + mail: + smtp: + auth: true + starttls: + enable: true + testcontainers: beans: startup: parallel From 261a21c2e915055f59920328470035d3a947bd34 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 15:13:51 +0900 Subject: [PATCH 19/24] =?UTF-8?q?CLAP-150=20Fix:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20usecase=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgitPersistenceAdapter.java => api/AgitClient.java} | 7 ++----- .../EmailPersistenceAdapter.java => api/EmailClient.java} | 4 ++-- .../KakaoWorkClient.java} | 6 +++--- .../ObjectBlockService.java} | 5 ++--- .../inbound/notification/CreateNotificationUsecase.java | 7 ------- .../service/notification/CreateNotificationService.java | 4 +--- .../application/service/webhook/SendKaKaoWorkService.java | 2 -- 7 files changed, 10 insertions(+), 25 deletions(-) rename src/main/java/clap/server/adapter/outbound/{persistense/AgitPersistenceAdapter.java => api/AgitClient.java} (87%) rename src/main/java/clap/server/adapter/outbound/{persistense/EmailPersistenceAdapter.java => api/EmailClient.java} (96%) rename src/main/java/clap/server/adapter/outbound/{persistense/KakaoWorkPersistenceAdapter.java => api/KakaoWorkClient.java} (92%) rename src/main/java/clap/server/adapter/outbound/{persistense/ObjectBlockPersistenceAdapter.java => api/ObjectBlockService.java} (98%) delete mode 100644 src/main/java/clap/server/application/port/inbound/notification/CreateNotificationUsecase.java diff --git a/src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java similarity index 87% rename from src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/api/AgitClient.java index a88c6d72..cf625f91 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/AgitPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java @@ -1,22 +1,19 @@ -package clap.server.adapter.outbound.persistense; +package clap.server.adapter.outbound.api; import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.port.outbound.webhook.SendAgitPort; -import clap.server.common.annotation.architecture.ApplicationService; import clap.server.common.annotation.architecture.PersistenceAdapter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; @PersistenceAdapter @RequiredArgsConstructor -public class AgitPersistenceAdapter implements SendAgitPort { +public class AgitClient implements SendAgitPort { private static final String AGITWEBHOOK_URL = "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1"; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/api/EmailClient.java similarity index 96% rename from src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/api/EmailClient.java index 971842f2..cd8b8ba7 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/EmailPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/api/EmailClient.java @@ -1,4 +1,4 @@ -package clap.server.adapter.outbound.persistense; +package clap.server.adapter.outbound.api; import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; @@ -15,7 +15,7 @@ @PersistenceAdapter @RequiredArgsConstructor -public class EmailPersistenceAdapter implements SendEmailPort { +public class EmailClient implements SendEmailPort { private final SpringTemplateEngine templateEngine; private final JavaMailSender mailSender; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java similarity index 92% rename from src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java index d9cf0503..d6a05643 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/KakaoWorkPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java @@ -1,4 +1,4 @@ -package clap.server.adapter.outbound.persistense; +package clap.server.adapter.outbound.api; import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; @@ -14,12 +14,12 @@ @PersistenceAdapter @RequiredArgsConstructor -public class KakaoWorkPersistenceAdapter implements SendKaKaoWorkPort { +public class KakaoWorkClient implements SendKaKaoWorkPort { private static final String KAKAOWORK_URL = "https://api.kakaowork.com/v1/messages.send_by_email"; private static final String KAKAOWORK_AUTH = "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d"; - private final ObjectBlockPersistenceAdapter makeObjectBlock; + private final ObjectBlockService makeObjectBlock; @Override public void sendKakaoWord(SendKakaoWorkRequest request) { diff --git a/src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java similarity index 98% rename from src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java index ea0fd077..7d487d32 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/ObjectBlockPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java @@ -1,8 +1,7 @@ -package clap.server.adapter.outbound.persistense; +package clap.server.adapter.outbound.api; import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; import clap.server.adapter.outbound.persistense.repository.notification.NotificationRepository; import clap.server.application.port.outbound.webhook.MakeObjectBlockPort; import clap.server.common.annotation.architecture.PersistenceAdapter; @@ -14,7 +13,7 @@ @PersistenceAdapter @RequiredArgsConstructor -public class ObjectBlockPersistenceAdapter implements MakeObjectBlockPort { +public class ObjectBlockService implements MakeObjectBlockPort { private final ObjectMapper objectMapper; private final NotificationRepository notificationRepository; 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 deleted file mode 100644 index 147ced9e..00000000 --- a/src/main/java/clap/server/application/port/inbound/notification/CreateNotificationUsecase.java +++ /dev/null @@ -1,7 +0,0 @@ -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/service/notification/CreateNotificationService.java b/src/main/java/clap/server/application/service/notification/CreateNotificationService.java index 840acb69..8071eba1 100644 --- a/src/main/java/clap/server/application/service/notification/CreateNotificationService.java +++ b/src/main/java/clap/server/application/service/notification/CreateNotificationService.java @@ -1,6 +1,5 @@ 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; @@ -8,11 +7,10 @@ @ApplicationService @RequiredArgsConstructor -public class CreateNotificationService implements CreateNotificationUsecase { +public class CreateNotificationService{ private final CommandNotificationPort commandNotificationPort; - @Override public void createNotification(Notification request) { commandNotificationPort.save(request); diff --git a/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java index 9080d572..d218c26b 100644 --- a/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java +++ b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java @@ -1,8 +1,6 @@ package clap.server.application.service.webhook; import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; -import clap.server.adapter.outbound.persistense.KakaoWorkPersistenceAdapter; import clap.server.application.port.outbound.webhook.SendKaKaoWorkPort; import clap.server.common.annotation.architecture.ApplicationService; import lombok.RequiredArgsConstructor; From 6fb5e95a817710172461f9b5a5116ef5ffe361eb Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 15:39:40 +0900 Subject: [PATCH 20/24] =?UTF-8?q?CLAP-150=20Fix:=20Agit,=20KakaoWork=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clap/server/adapter/outbound/api/AgitClient.java | 4 +++- .../server/adapter/outbound/api/KakaoWorkClient.java | 12 ++++++++---- src/main/resources/application.yml | 11 +++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/clap/server/adapter/outbound/api/AgitClient.java b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java index cf625f91..5e9292fa 100644 --- a/src/main/java/clap/server/adapter/outbound/api/AgitClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java @@ -5,6 +5,7 @@ import clap.server.application.port.outbound.webhook.SendAgitPort; import clap.server.common.annotation.architecture.PersistenceAdapter; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -15,7 +16,8 @@ @RequiredArgsConstructor public class AgitClient implements SendAgitPort { - private static final String AGITWEBHOOK_URL = "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1"; + @Value("${agit.url}") + private String AGITWEBHOOK_URL; @Override public void sendAgit(SendAgitRequest request) { diff --git a/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java index d6a05643..06f267ff 100644 --- a/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java @@ -7,6 +7,7 @@ import clap.server.exception.ApplicationException; import clap.server.exception.code.NotificationErrorCode; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -16,8 +17,11 @@ @RequiredArgsConstructor public class KakaoWorkClient implements SendKaKaoWorkPort { - private static final String KAKAOWORK_URL = "https://api.kakaowork.com/v1/messages.send_by_email"; - private static final String KAKAOWORK_AUTH = "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d"; + @Value("${kakaowork.url}") + private String kakaworkUrl; + + @Value("${kakaowork.auth}") + private String kakaworkAuth; private final ObjectBlockService makeObjectBlock; @@ -46,7 +50,7 @@ else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { // HTTP 요청 헤더 설정 HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "application/json"); - headers.add("Authorization", KAKAOWORK_AUTH); + headers.add("Authorization", kakaworkAuth); // HTTP 요청 엔터티 생성 HttpEntity entity = new HttpEntity<>(payload, headers); @@ -54,7 +58,7 @@ else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { try { // Post 요청 전송 restTemplate.exchange( - KAKAOWORK_URL, HttpMethod.POST, entity, String.class + kakaworkUrl, HttpMethod.POST, entity, String.class ); } catch (Exception e) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 617a18d5..dee57fe5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -43,6 +43,17 @@ web: domain: local: ${TASKFLOW_LOCAL_WEB:127.0.0.1:3O00} service: ${TASKFLOW_SERVICE_WEB:127.0.0.1:3000} + + +# 카카오워크 및 agit url, accessKey값 환경 변수로 설정 +kakaowork: + url: "https://api.kakaowork.com/v1/messages.send_by_email"; + auth: "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d" + +agit: + url: "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1" + + #logging: # level: # root: INFO From eb4ec3862639aca2ce8adae54c6bef769a4b7fe2 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 16:03:08 +0900 Subject: [PATCH 21/24] =?UTF-8?q?CLAP-150=20Fix:=20SSE=20files=20infrastru?= =?UTF-8?q?cture=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/clap/server/adapter/outbound/api/AgitClient.java | 2 +- .../clap/server/adapter/outbound/api/EmailClient.java | 2 +- .../clap/server/adapter/outbound/api/KakaoWorkClient.java | 2 +- .../server/adapter/outbound/api/ObjectBlockService.java | 2 +- .../dto/webhook => outbound/api/dto}/SendAgitRequest.java | 2 +- .../api/dto}/SendKakaoWorkRequest.java | 2 +- .../webhook => outbound/api/dto}/SendWebhookRequest.java | 2 +- .../sse}/SsePersistenceAdapter.java | 4 ++-- .../sse/repository}/EmitterRepository.java | 2 +- .../sse/repository}/EmitterRepositoryImpl.java | 2 +- .../persistense/evenlistener/CustomEventListener.java | 8 +++----- .../clap/server/application/Task/CreateTaskService.java | 6 +++--- .../port/outbound/webhook/MakeObjectBlockPort.java | 3 +-- .../application/port/outbound/webhook/SendAgitPort.java | 3 +-- .../application/port/outbound/webhook/SendEmailPort.java | 2 +- .../port/outbound/webhook/SendKaKaoWorkPort.java | 3 +-- .../application/service/webhook/SendAgitService.java | 2 +- .../application/service/webhook/SendEmailService.java | 2 +- .../application/service/webhook/SendKaKaoWorkService.java | 2 +- 19 files changed, 24 insertions(+), 29 deletions(-) rename src/main/java/clap/server/adapter/{inbound/web/dto/webhook => outbound/api/dto}/SendAgitRequest.java (85%) rename src/main/java/clap/server/adapter/{inbound/web/dto/webhook => outbound/api/dto}/SendKakaoWorkRequest.java (85%) rename src/main/java/clap/server/adapter/{inbound/web/dto/webhook => outbound/api/dto}/SendWebhookRequest.java (85%) rename src/main/java/clap/server/adapter/outbound/{persistense => infrastructure/sse}/SsePersistenceAdapter.java (85%) rename src/main/java/clap/server/adapter/outbound/{persistense/repository/notification => infrastructure/sse/repository}/EmitterRepository.java (74%) rename src/main/java/clap/server/adapter/outbound/{persistense/repository/notification => infrastructure/sse/repository}/EmitterRepositoryImpl.java (90%) diff --git a/src/main/java/clap/server/adapter/outbound/api/AgitClient.java b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java index 5e9292fa..026018cd 100644 --- a/src/main/java/clap/server/adapter/outbound/api/AgitClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/AgitClient.java @@ -1,6 +1,6 @@ package clap.server.adapter.outbound.api; -import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; +import clap.server.adapter.outbound.api.dto.SendAgitRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.port.outbound.webhook.SendAgitPort; import clap.server.common.annotation.architecture.PersistenceAdapter; diff --git a/src/main/java/clap/server/adapter/outbound/api/EmailClient.java b/src/main/java/clap/server/adapter/outbound/api/EmailClient.java index cd8b8ba7..ee5fb2f9 100644 --- a/src/main/java/clap/server/adapter/outbound/api/EmailClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/EmailClient.java @@ -1,6 +1,6 @@ package clap.server.adapter.outbound.api; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.port.outbound.webhook.SendEmailPort; import clap.server.common.annotation.architecture.PersistenceAdapter; diff --git a/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java index 06f267ff..9557f973 100644 --- a/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java @@ -1,6 +1,6 @@ package clap.server.adapter.outbound.api; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.port.outbound.webhook.SendKaKaoWorkPort; import clap.server.common.annotation.architecture.PersistenceAdapter; diff --git a/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java b/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java index 7d487d32..77c67eef 100644 --- a/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java +++ b/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java @@ -1,7 +1,7 @@ package clap.server.adapter.outbound.api; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; import clap.server.adapter.outbound.persistense.repository.notification.NotificationRepository; import clap.server.application.port.outbound.webhook.MakeObjectBlockPort; import clap.server.common.annotation.architecture.PersistenceAdapter; diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java b/src/main/java/clap/server/adapter/outbound/api/dto/SendAgitRequest.java similarity index 85% rename from src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java rename to src/main/java/clap/server/adapter/outbound/api/dto/SendAgitRequest.java index 8ac3b4de..65add4ab 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java +++ b/src/main/java/clap/server/adapter/outbound/api/dto/SendAgitRequest.java @@ -1,4 +1,4 @@ -package clap.server.adapter.inbound.web.dto.webhook; +package clap.server.adapter.outbound.api.dto; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java b/src/main/java/clap/server/adapter/outbound/api/dto/SendKakaoWorkRequest.java similarity index 85% rename from src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java rename to src/main/java/clap/server/adapter/outbound/api/dto/SendKakaoWorkRequest.java index 11c0c841..2bf6252d 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java +++ b/src/main/java/clap/server/adapter/outbound/api/dto/SendKakaoWorkRequest.java @@ -1,4 +1,4 @@ -package clap.server.adapter.inbound.web.dto.webhook; +package clap.server.adapter.outbound.api.dto; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java b/src/main/java/clap/server/adapter/outbound/api/dto/SendWebhookRequest.java similarity index 85% rename from src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java rename to src/main/java/clap/server/adapter/outbound/api/dto/SendWebhookRequest.java index 3c7a16e5..81bfe62c 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java +++ b/src/main/java/clap/server/adapter/outbound/api/dto/SendWebhookRequest.java @@ -1,4 +1,4 @@ -package clap.server.adapter.inbound.web.dto.webhook; +package clap.server.adapter.outbound.api.dto; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java similarity index 85% rename from src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java index a1eb4244..18e04210 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/SsePersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java @@ -1,6 +1,6 @@ -package clap.server.adapter.outbound.persistense; +package clap.server.adapter.outbound.infrastructure.sse; -import clap.server.adapter.outbound.persistense.repository.notification.EmitterRepository; +import clap.server.adapter.outbound.infrastructure.sse.repository.EmitterRepository; import clap.server.application.port.outbound.notification.CommandSsePort; import clap.server.application.port.outbound.notification.LoadSsePort; import clap.server.common.annotation.architecture.PersistenceAdapter; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepository.java similarity index 74% rename from src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java rename to src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepository.java index e207d509..6d201b25 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepository.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepository.java @@ -1,4 +1,4 @@ -package clap.server.adapter.outbound.persistense.repository.notification; +package clap.server.adapter.outbound.infrastructure.sse.repository; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepositoryImpl.java similarity index 90% rename from src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java rename to src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepositoryImpl.java index 9868595e..2651022f 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/notification/EmitterRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/repository/EmitterRepositoryImpl.java @@ -1,4 +1,4 @@ -package clap.server.adapter.outbound.persistense.repository.notification; +package clap.server.adapter.outbound.infrastructure.sse.repository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; 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 index d2fbed26..5e88dcad 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/evenlistener/CustomEventListener.java @@ -1,9 +1,9 @@ package clap.server.adapter.outbound.persistense.evenlistener; import clap.server.adapter.inbound.web.dto.notification.SseRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendAgitRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; import clap.server.application.service.notification.CreateNotificationService; import clap.server.application.service.notification.SendSseService; import clap.server.application.service.webhook.SendAgitService; @@ -12,8 +12,6 @@ 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; diff --git a/src/main/java/clap/server/application/Task/CreateTaskService.java b/src/main/java/clap/server/application/Task/CreateTaskService.java index cac075d7..8425d1c9 100644 --- a/src/main/java/clap/server/application/Task/CreateTaskService.java +++ b/src/main/java/clap/server/application/Task/CreateTaskService.java @@ -4,9 +4,9 @@ import clap.server.adapter.inbound.web.dto.task.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.CreateTaskResponse; -import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendAgitRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; import clap.server.adapter.outbound.infrastructure.s3.S3UploadAdapter; import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import clap.server.application.mapper.AttachmentMapper; diff --git a/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java b/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java index 45bbeec8..9ca0ac86 100644 --- a/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java +++ b/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java @@ -1,7 +1,6 @@ package clap.server.application.port.outbound.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; public interface MakeObjectBlockPort { diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java index a807ff9d..f6e7cfd3 100644 --- a/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendAgitPort.java @@ -1,7 +1,6 @@ package clap.server.application.port.outbound.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendAgitRequest; public interface SendAgitPort { void sendAgit(SendAgitRequest request); diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java index 6114bff5..e69f7094 100644 --- a/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendEmailPort.java @@ -1,6 +1,6 @@ package clap.server.application.port.outbound.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; public interface SendEmailPort { diff --git a/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java b/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java index 15d6c54b..03550a57 100644 --- a/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java +++ b/src/main/java/clap/server/application/port/outbound/webhook/SendKaKaoWorkPort.java @@ -1,7 +1,6 @@ package clap.server.application.port.outbound.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; public interface SendKaKaoWorkPort { diff --git a/src/main/java/clap/server/application/service/webhook/SendAgitService.java b/src/main/java/clap/server/application/service/webhook/SendAgitService.java index 6a135eae..958ce52c 100644 --- a/src/main/java/clap/server/application/service/webhook/SendAgitService.java +++ b/src/main/java/clap/server/application/service/webhook/SendAgitService.java @@ -1,6 +1,6 @@ package clap.server.application.service.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendAgitRequest; +import clap.server.adapter.outbound.api.dto.SendAgitRequest; import clap.server.application.port.outbound.webhook.SendAgitPort; import clap.server.common.annotation.architecture.ApplicationService; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/clap/server/application/service/webhook/SendEmailService.java b/src/main/java/clap/server/application/service/webhook/SendEmailService.java index 725989b7..72f93712 100644 --- a/src/main/java/clap/server/application/service/webhook/SendEmailService.java +++ b/src/main/java/clap/server/application/service/webhook/SendEmailService.java @@ -1,6 +1,6 @@ package clap.server.application.service.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendWebhookRequest; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; import clap.server.application.port.outbound.webhook.SendEmailPort; import clap.server.common.annotation.architecture.ApplicationService; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java index d218c26b..35ea3f22 100644 --- a/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java +++ b/src/main/java/clap/server/application/service/webhook/SendKaKaoWorkService.java @@ -1,6 +1,6 @@ package clap.server.application.service.webhook; -import clap.server.adapter.inbound.web.dto.webhook.SendKakaoWorkRequest; +import clap.server.adapter.outbound.api.dto.SendKakaoWorkRequest; import clap.server.application.port.outbound.webhook.SendKaKaoWorkPort; import clap.server.common.annotation.architecture.ApplicationService; import lombok.RequiredArgsConstructor; From 202c6bb622b86d7749f0bd7ff290d4a54c3cb0c2 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 16:09:00 +0900 Subject: [PATCH 22/24] =?UTF-8?q?CLAP-150=20Fix:=20build=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91=20test=20=EC=8B=A4=ED=8C=A8=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EA=B4=80=EB=A0=A8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 75c4575f..ac7daec4 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -58,4 +58,11 @@ password: length: 12 characters: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+ +kakaowork: + url: "https://api.kakaowork.com/v1/messages.send_by_email"; + auth: "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d" + +agit: + url: "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1" + From 7cee8a6a0ec180c429cef86367ac8537b89d6ef8 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 27 Jan 2025 16:17:38 +0900 Subject: [PATCH 23/24] =?UTF-8?q?CLAP-150=20Fix:=20build=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91=20test=20=EC=8B=A4=ED=8C=A8=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EA=B4=80=EB=A0=A8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 6 +++--- src/test/resources/application.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index dee57fe5..e30d620b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -47,11 +47,11 @@ web: # 카카오워크 및 agit url, accessKey값 환경 변수로 설정 kakaowork: - url: "https://api.kakaowork.com/v1/messages.send_by_email"; - auth: "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d" + url: https://api.kakaowork.com/v1/messages.send_by_email + auth: Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d agit: - url: "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1" + url: https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1 #logging: diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index ac7daec4..b10d6c61 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -59,10 +59,10 @@ password: characters: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+ kakaowork: - url: "https://api.kakaowork.com/v1/messages.send_by_email"; - auth: "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d" + url: https://api.kakaowork.com/v1/messages.send_by_email + auth: Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d agit: - url: "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1" + url: https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1 From db001f4d16bf4e8c4d954f2c8eb5c60a0715b06e Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 28 Jan 2025 02:49:11 +0900 Subject: [PATCH 24/24] CLAP-150 Fix: merge confliction --- .../adapter/inbound/web/dto/common/SliceResponse.java | 3 +-- .../clap/server/application/mapper/NotificationMapper.java | 3 +-- src/main/resources/application.yml | 6 ++++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/common/SliceResponse.java b/src/main/java/clap/server/adapter/inbound/web/dto/common/SliceResponse.java index 3cffcec0..d8c08509 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/common/SliceResponse.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/common/SliceResponse.java @@ -5,8 +5,7 @@ public record SliceResponse ( List content, - int currentPage, - int size, + boolean hasNext, boolean isFirst, boolean isLast ) { diff --git a/src/main/java/clap/server/application/mapper/NotificationMapper.java b/src/main/java/clap/server/application/mapper/NotificationMapper.java index d53d71b4..e2c83e52 100644 --- a/src/main/java/clap/server/application/mapper/NotificationMapper.java +++ b/src/main/java/clap/server/application/mapper/NotificationMapper.java @@ -23,8 +23,7 @@ public static FindNotificationListResponse toFindNoticeListResponse(Notification public static SliceResponse toSliceOfFindNoticeListResponse(Slice slice) { return new SliceResponse<>( slice.getContent(), - slice.getNumber(), - slice.getSize(), + slice.hasNext(), slice.isFirst(), slice.isLast() ); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e30d620b..a689f8f0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,8 +41,8 @@ server: web: domain: - local: ${TASKFLOW_LOCAL_WEB:127.0.0.1:3O00} - service: ${TASKFLOW_SERVICE_WEB:127.0.0.1:3000} + local: ${TASKFLOW_LOCAL_WEB:127.0.0.1:5173} + service: ${TASKFLOW_SERVICE_WEB:127.0.0.1:5173} # 카카오워크 및 agit url, accessKey값 환경 변수로 설정 @@ -53,6 +53,8 @@ kakaowork: agit: url: https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1 +local: ${TASKFLOW_LOCAL_WEB:127.0.0.1:5173} +service: ${TASKFLOW_SERVICE_WEB:127.0.0.1:5173} #logging: # level: