-
Notifications
You must be signed in to change notification settings - Fork 5
CLAP-150 시스템과 외부 푸시 알림 기능 구현 #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 22 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
7065d7c
CLAP-149 HotFix: 작업 생성 및 수정 시 secured 오탈자 수정
parkjaehak d6d66b7
CLAP-149 HotFix: 작업생성에 대한 알림 생성 시 null 설정
parkjaehak 2a96a08
CLAP-149 HotFix: 작업 생성 및 수정 시 mediatype json 추가
parkjaehak 4ee201e
CLAP-150 Feature: 회원 최초 접속 시 Sse 등록 logic 구현
starboxxxx 774442a
CLAP-150 Feature: 알림 생성 시 푸시 알림으로 receiver에게 실시간 전송하는 logic 구현
starboxxxx 30916e2
CLAP-150 Fix: 알림 ErrorCode 추가
starboxxxx e19b5cc
CLAP-150 Feature: 알림 생성 시 sseRepository에서 실시간 알림을 전송하는 회원 조회하는 method
starboxxxx 80607c6
CLAP-150 Feature: 이메일 webhook 푸시 알림 자동 전송 기능 구현
starboxxxx e47531f
CLAP-150 Feature: 이메일 webhook 푸시 알림 작성자 설정을 위한 초기 세팅(논의 필요)
starboxxxx 84279a2
CLAP-150 Feature: KakaoWork 푸시 알림 자동 전송 기능 구현
starboxxxx 3c0dbc7
CLAP-150 Feature: KakaoWork 푸시 알림 자동 전송 기능 구현에 포함
starboxxxx 8137098
CLAP-150 Feature: KakaoWork 푸시 알림 자동 전송 기능 구현에 포함
starboxxxx a519d06
CLAP-150 Feature: Agit 실시간 Webhook 알림 전송 기능 구현
starboxxxx 47d4584
CLAP-150 Feature: 이메일 전송에 필요한 html 파일
starboxxxx 342c9d8
CLAP-150 Fix: Error Code 추가 설정
starboxxxx 71af703
CLAP-150 Fix: Git Conflict
starboxxxx 7821ec8
Merge remote-tracking branch 'origin/CLAP-150' into CLAP-150
starboxxxx d0a9ce9
CLAP-150 Fix: 이메일 알림 전송 기능에 필요한 thymeleaf 의존성 추가
starboxxxx 00dcadd
CLAP-150 Fix: test yml에 spring mailsender 초기 설정 추가
starboxxxx 261a21c
CLAP-150 Fix: 파일 구조 정리 및 불필요한 usecase 제거
starboxxxx 6fb5e95
CLAP-150 Fix: Agit, KakaoWork 환경 변수 설정
starboxxxx eb4ec38
CLAP-150 Fix: SSE files infrastructure로 이동
starboxxxx 202c6bb
CLAP-150 Fix: build 과정 중 test 실패 오류 관련 처리
starboxxxx 7cee8a6
CLAP-150 Fix: build 과정 중 test 실패 오류 관련 처리
starboxxxx db001f4
CLAP-150 Fix: merge confliction
starboxxxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/clap/server/adapter/inbound/web/dto/common/SliceResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package clap.server.adapter.inbound.web.dto.common; | ||
|
|
||
|
|
||
| import java.util.List; | ||
|
|
||
| public record SliceResponse<T> ( | ||
| List<T> content, | ||
| int currentPage, | ||
| int size, | ||
| boolean isFirst, | ||
| boolean isLast | ||
| ) { | ||
| } |
11 changes: 11 additions & 0 deletions
11
src/main/java/clap/server/adapter/inbound/web/dto/notification/SseRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) { | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendAgitRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) { | ||
| } |
14 changes: 14 additions & 0 deletions
14
src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendKakaoWorkRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) { | ||
| } |
14 changes: 14 additions & 0 deletions
14
src/main/java/clap/server/adapter/inbound/web/dto/webhook/SendWebhookRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ) { | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/main/java/clap/server/adapter/inbound/web/notification/SubscribeEmitterController.java
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 따로 api가 있는 이유가 무엇인지 궁금합니다! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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()); | ||
| } | ||
| } |
51 changes: 51 additions & 0 deletions
51
src/main/java/clap/server/adapter/outbound/api/AgitClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package clap.server.adapter.outbound.api; | ||
|
|
||
| import clap.server.adapter.inbound.web.dto.webhook.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; | ||
| 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 AgitClient implements SendAgitPort { | ||
|
|
||
| private static final String AGITWEBHOOK_URL = "https://agit.io/webhook/a342181d-fb18-4eb0-a99a-30f4fb5b14b1"; | ||
|
joowojr marked this conversation as resolved.
Outdated
|
||
|
|
||
| @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<String> entity = new HttpEntity<>(payload, headers); | ||
|
|
||
| // Post 요청 | ||
| restTemplate.exchange(AGITWEBHOOK_URL, HttpMethod.POST, entity, String.class); | ||
| } | ||
| } | ||
86 changes: 86 additions & 0 deletions
86
src/main/java/clap/server/adapter/outbound/api/EmailClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| 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; | ||
| 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 EmailClient 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); | ||
| } | ||
| } | ||
| } |
64 changes: 64 additions & 0 deletions
64
src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| 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; | ||
| 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 KakaoWorkClient implements SendKaKaoWorkPort { | ||
|
|
||
| private static final String KAKAOWORK_URL = "https://api.kakaowork.com/v1/messages.send_by_email"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중요한 정보기 때문에 환경변수화 부탁드립니다! |
||
| private static final String KAKAOWORK_AUTH = "Bearer 1b01becc.a7f10da76d2e4038948771107cfe5c1d"; | ||
|
|
||
| private final ObjectBlockService 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<String> 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); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
request dto도 oubound/api/dto로 이동 해야할거같아요!