Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void getMemberErrorCode() {}
@GetMapping("/auth")
@DevelopOnlyApi
@Operation(summary = "인증 및 인가 관련 에러 코드 나열")
@ApiErrorCodes(MemberErrorCode.class)
@ApiErrorCodes(AuthErrorCode.class)
public void getAuthErrorCode() {}

@GetMapping("/task")
Expand Down
86 changes: 15 additions & 71 deletions src/main/java/clap/server/adapter/outbound/api/EmailClient.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 블록
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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();
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package clap.server.adapter.outbound.api.dto;

public record EmailTemplate(
String email,
String subject,
String body
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public interface SendEmailPort {

void sendEmail(SendWebhookRequest request);
void sendWebhookEmail(SendWebhookRequest request);

void sendInvitationEmail(String memberEmail, String receiverName, String initialPassword);

Expand Down
Loading