diff --git a/src/main/java/clap/server/adapter/inbound/web/example/ErrorExampleController.java b/src/main/java/clap/server/adapter/inbound/web/example/ErrorExampleController.java index 8658dd86..ce90801e 100644 --- a/src/main/java/clap/server/adapter/inbound/web/example/ErrorExampleController.java +++ b/src/main/java/clap/server/adapter/inbound/web/example/ErrorExampleController.java @@ -29,7 +29,7 @@ public void getMemberErrorCode() {} @GetMapping("/auth") @DevelopOnlyApi @Operation(summary = "인증 및 인가 관련 에러 코드 나열") - @ApiErrorCodes(MemberErrorCode.class) + @ApiErrorCodes(AuthErrorCode.class) public void getAuthErrorCode() {} @GetMapping("/task") 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 36d4f7cb..2ceea26e 100644 --- a/src/main/java/clap/server/adapter/outbound/api/EmailClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/EmailClient.java @@ -1,110 +1,54 @@ package clap.server.adapter.outbound.api; +import clap.server.adapter.outbound.api.dto.EmailTemplate; 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.ExternalApiAdapter; -import clap.server.exception.ApplicationException; +import clap.server.exception.AdapterException; 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; @ExternalApiAdapter @RequiredArgsConstructor public class EmailClient implements SendEmailPort { - private final SpringTemplateEngine templateEngine; + private final EmailTemplateBuilder emailTemplateBuilder; private final JavaMailSender mailSender; @Override - public void sendEmail(SendWebhookRequest request) { + public void sendWebhookEmail(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 알림] 신규 작업이 요청되었습니다."); + EmailTemplate template = emailTemplateBuilder.createWebhookTemplate(request); + helper.setTo(template.email()); + helper.setSubject(template.subject()); + helper.setText(template.body(), true); - 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 if (request.notificationType() == NotificationType.INVITATION) { - helper.setTo(request.email()); - helper.setSubject("[TaskFlow 초대] 회원가입을 환영합니다."); - - context.setVariable("invitationLink", "https://example.com/reset-password"); //TODO:비밀번호 설정 링크로 변경 예정 - context.setVariable("initialPassword", request.message()); - context.setVariable("receiverName", request.senderName()); - - body = templateEngine.process("invitation", 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); + throw new AdapterException(NotificationErrorCode.EMAIL_SEND_FAILED); } } + @Override public void sendInvitationEmail(String memberEmail, String receiverName, String initialPassword) { try { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); - helper.setTo(memberEmail); - helper.setSubject("[TaskFlow 초대] 회원가입을 환영합니다."); - - Context context = new Context(); - context.setVariable("invitationLink", "https://example.com/reset-password"); // TODO: 비밀번호 재설정 링크로 변경 - context.setVariable("initialPassword", initialPassword); - context.setVariable("receiverName", receiverName); - - String body = templateEngine.process("invitation", context); - helper.setText(body, true); + EmailTemplate template = emailTemplateBuilder.createInvitationTemplate(memberEmail, receiverName, initialPassword); + helper.setTo(template.email()); + helper.setSubject(template.subject()); + helper.setText(template.body(), true); mailSender.send(mimeMessage); } catch (Exception e) { - throw new ApplicationException(NotificationErrorCode.EMAIL_SEND_FAILED); + throw new AdapterException(NotificationErrorCode.EMAIL_SEND_FAILED); } } } diff --git a/src/main/java/clap/server/adapter/outbound/api/EmailTemplateBuilder.java b/src/main/java/clap/server/adapter/outbound/api/EmailTemplateBuilder.java new file mode 100644 index 00000000..be8c8744 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/api/EmailTemplateBuilder.java @@ -0,0 +1,65 @@ +package clap.server.adapter.outbound.api; + +import clap.server.adapter.outbound.api.dto.EmailTemplate; +import clap.server.adapter.outbound.api.dto.SendWebhookRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +@Component +@RequiredArgsConstructor +public class EmailTemplateBuilder { + private final SpringTemplateEngine templateEngine; + + public EmailTemplate createWebhookTemplate(SendWebhookRequest request) { + Context context = new Context(); + String templateName = ""; + String subject = ""; + switch (request.notificationType()) { + case TASK_REQUESTED: + templateName = "task-request"; + subject = "[TaskFlow 알림] 신규 작업이 요청되었습니다."; + context.setVariable("receiverName", request.senderName()); + context.setVariable("title", request.taskName()); + break; + case STATUS_SWITCHED: + templateName = "status-switched"; + subject = "[TaskFlow 알림] 작업 상태가 변경되었습니다."; + context.setVariable("receiverName", request.senderName()); + context.setVariable("title", request.taskName()); + break; + case PROCESSOR_CHANGED: + templateName = "processor-changed"; + subject = "[TaskFlow 알림] 작업 담당자가 변경되었습니다."; + context.setVariable("processorName", request.message()); + context.setVariable("title", request.taskName()); + break; + case PROCESSOR_ASSIGNED: + templateName = "processor-assigned"; + subject = "[TaskFlow 알림] 작업 담당자가 지정되었습니다."; + context.setVariable("processorName", request.message()); + context.setVariable("title", request.taskName()); + break; + case COMMENT: + subject = "[TaskFlow 알림] 댓글이 작성되었습니다."; + context.setVariable("comment", request.message()); + context.setVariable("title", request.taskName()); + break; + + } + String body = templateEngine.process(templateName, context); + return new EmailTemplate(request.email(), subject, body); + } + + public EmailTemplate createInvitationTemplate(String receiver, String receiverName, String initialPassword) { + Context context = new Context(); + String templateName = "invitation"; + String subject = "[TaskFlow 초대] 회원가입을 환영합니다."; + context.setVariable("invitationLink", "https://example.com/reset-password"); //TODO:비밀번호 설정 링크로 변경 예정 + context.setVariable("initialPassword", initialPassword); + context.setVariable("receiverName", receiverName); + String body = templateEngine.process(templateName, context); + return new EmailTemplate(receiver, subject, body); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkBlockBuilder.java similarity index 91% rename from src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java rename to src/main/java/clap/server/adapter/outbound/api/KakaoWorkBlockBuilder.java index 9c8451f4..cb3aeeb4 100644 --- a/src/main/java/clap/server/adapter/outbound/api/ObjectBlockService.java +++ b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkBlockBuilder.java @@ -1,24 +1,41 @@ package clap.server.adapter.outbound.api; import clap.server.adapter.outbound.api.dto.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 clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; import java.util.Map; -@PersistenceAdapter +@Component @RequiredArgsConstructor -public class ObjectBlockService implements MakeObjectBlockPort { +public class KakaoWorkBlockBuilder { private final ObjectMapper objectMapper; - private final NotificationRepository notificationRepository; - @Override - public String makeTaskRequestBlock(SendWebhookRequest request) { + public String makeObjectBlock(SendWebhookRequest request){ + String payload; + if (request.notificationType() == NotificationType.TASK_REQUESTED) { + payload = makeTaskRequestBlock(request); + } + else if (request.notificationType() == NotificationType.PROCESSOR_ASSIGNED) { + payload = makeNewProcessorBlock(request); + } + else if (request.notificationType() == NotificationType.PROCESSOR_CHANGED) { + payload = makeProcessorChangeBlock(request); + } + else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { + payload = makeTaskStatusBlock(request); + } + else { + payload = makeCommentBlock(request); + } + return payload; + } + + private String makeTaskRequestBlock(SendWebhookRequest request) { // Blocks 데이터 생성 Object[] blocks = new Object[]{ // Header 블록 @@ -90,8 +107,7 @@ public String makeTaskRequestBlock(SendWebhookRequest request) { return payload; } - @Override - public String makeNewProcessorBlock(SendWebhookRequest request) { + private String makeNewProcessorBlock(SendWebhookRequest request) { Object[] blocks = new Object[]{ Map.of( "type", "header", @@ -157,8 +173,7 @@ public String makeNewProcessorBlock(SendWebhookRequest request) { return payload; } - @Override - public String makeProcessorChangeBlock(SendWebhookRequest request) { + private String makeProcessorChangeBlock(SendWebhookRequest request) { Object[] blocks = new Object[]{ Map.of( "type", "header", @@ -225,8 +240,7 @@ public String makeProcessorChangeBlock(SendWebhookRequest request) { return payload; } - @Override - public String makeCommentBlock(SendWebhookRequest request) { + private String makeCommentBlock(SendWebhookRequest request) { Object[] blocks = new Object[]{ Map.of( "type", "header", @@ -303,8 +317,7 @@ public String makeCommentBlock(SendWebhookRequest request) { return payload; } - @Override - public String makeTaskStatusBlock(SendWebhookRequest request) { + private String makeTaskStatusBlock(SendWebhookRequest request) { Object[] blocks = new Object[]{ Map.of( "type", "header", 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 27a05cfe..415b3be8 100644 --- a/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java +++ b/src/main/java/clap/server/adapter/outbound/api/KakaoWorkClient.java @@ -1,10 +1,9 @@ package clap.server.adapter.outbound.api; 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.SendKaKaoWorkPort; import clap.server.common.annotation.architecture.ExternalApiAdapter; -import clap.server.exception.ApplicationException; +import clap.server.exception.AdapterException; import clap.server.exception.code.NotificationErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -23,29 +22,14 @@ public class KakaoWorkClient implements SendKaKaoWorkPort { @Value("${webhook.kakaowork.auth}") private String kakaworkAuth; - private final ObjectBlockService makeObjectBlock; + private final KakaoWorkBlockBuilder kakaoWorkBlockBuilder; @Override public void sendKakaoWork(SendWebhookRequest 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); - } + String payload = kakaoWorkBlockBuilder.makeObjectBlock(request); // HTTP 요청 헤더 설정 HttpHeaders headers = new HttpHeaders(); @@ -62,7 +46,7 @@ else if (request.notificationType() == NotificationType.STATUS_SWITCHED) { ); } catch (Exception e) { - throw new ApplicationException(NotificationErrorCode.KAKAO_SEND_FAILED); + throw new AdapterException(NotificationErrorCode.KAKAO_SEND_FAILED); } } } diff --git a/src/main/java/clap/server/adapter/outbound/api/dto/EmailTemplate.java b/src/main/java/clap/server/adapter/outbound/api/dto/EmailTemplate.java new file mode 100644 index 00000000..d5215de2 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/api/dto/EmailTemplate.java @@ -0,0 +1,8 @@ +package clap.server.adapter.outbound.api.dto; + +public record EmailTemplate( + String email, + String subject, + String body +) { +} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SseAdapter.java similarity index 83% rename from src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java rename to src/main/java/clap/server/adapter/outbound/infrastructure/sse/SseAdapter.java index 18e04210..3be28ab2 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SsePersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/sse/SseAdapter.java @@ -3,13 +3,13 @@ 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; +import clap.server.common.annotation.architecture.InfrastructureAdapter; import lombok.RequiredArgsConstructor; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -@PersistenceAdapter +@InfrastructureAdapter @RequiredArgsConstructor -public class SsePersistenceAdapter implements LoadSsePort, CommandSsePort { +public class SseAdapter implements LoadSsePort, CommandSsePort { private final EmitterRepository emitterRepository; 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 deleted file mode 100644 index dfcb3f61..00000000 --- a/src/main/java/clap/server/application/port/outbound/webhook/MakeObjectBlockPort.java +++ /dev/null @@ -1,16 +0,0 @@ -package clap.server.application.port.outbound.webhook; - -import clap.server.adapter.outbound.api.dto.SendWebhookRequest; - -public interface MakeObjectBlockPort { - - String makeTaskRequestBlock(SendWebhookRequest request); - - String makeNewProcessorBlock(SendWebhookRequest request); - - String makeProcessorChangeBlock(SendWebhookRequest request); - - String makeCommentBlock(SendWebhookRequest request); - - String makeTaskStatusBlock(SendWebhookRequest 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 0a3b7b6c..e9f9ce26 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 @@ -4,7 +4,7 @@ public interface SendEmailPort { - void sendEmail(SendWebhookRequest request); + void sendWebhookEmail(SendWebhookRequest request); void sendInvitationEmail(String memberEmail, String receiverName, String initialPassword); 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 72f93712..4f4b1035 100644 --- a/src/main/java/clap/server/application/service/webhook/SendEmailService.java +++ b/src/main/java/clap/server/application/service/webhook/SendEmailService.java @@ -12,6 +12,6 @@ public class SendEmailService { private final SendEmailPort port; public void sendEmail(SendWebhookRequest request) { - port.sendEmail(request); + port.sendWebhookEmail(request); } } diff --git a/src/main/java/clap/server/exception/AdapterException.java b/src/main/java/clap/server/exception/AdapterException.java new file mode 100644 index 00000000..be7b3973 --- /dev/null +++ b/src/main/java/clap/server/exception/AdapterException.java @@ -0,0 +1,15 @@ +package clap.server.exception; + +import clap.server.exception.code.BaseErrorCode; + +public class AdapterException extends BaseException { + + public AdapterException(BaseErrorCode code) { + super(code); + } + + public static AdapterException from(BaseErrorCode code) { + return new AdapterException(code); + } +} +