diff --git a/api/src/test/kotlin/com/backgu/amaker/api/mail/infra/FakeEmailSender.kt b/api/src/test/kotlin/com/backgu/amaker/api/mail/infra/FakeEmailSender.kt deleted file mode 100644 index df103a2c..00000000 --- a/api/src/test/kotlin/com/backgu/amaker/api/mail/infra/FakeEmailSender.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.backgu.amaker.api.mail.infra - -import com.backgu.amaker.api.common.annotation.IntegrationTestComponent -import com.backgu.amaker.application.notification.mail.service.EmailSender -import io.github.oshai.kotlinlogging.KotlinLogging - -private val logger = KotlinLogging.logger {} - -@IntegrationTestComponent -class FakeEmailSender : EmailSender { - override fun sendEmail( - emailAddress: String, - title: String, - body: String, - ) { - logger.info { "Test Sending email to $emailAddress with title $title and body $body" } - } -} diff --git a/infra/build.gradle.kts b/infra/build.gradle.kts index ce805da3..d63ddd3d 100644 --- a/infra/build.gradle.kts +++ b/infra/build.gradle.kts @@ -22,14 +22,11 @@ dependencies { implementation(project(":domain")) implementation(project(":common")) implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2") - implementation("org.springframework.boot:spring-boot-starter-mail") - implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.mysql:mysql-connector-j:8.4.0") implementation("org.springframework.kafka:spring-kafka:3.1.4") implementation("org.springframework.boot:spring-boot-starter-data-redis") implementation("org.springframework.boot:spring-boot-starter-web") -// implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") implementation("org.reflections:reflections:0.10.2") implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta") diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/constants/EmailConstants.kt b/infra/src/main/kotlin/com/backgu/amaker/infra/mail/constants/EmailConstants.kt deleted file mode 100644 index 2961eaf8..00000000 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/constants/EmailConstants.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.backgu.amaker.infra.mail.constants - -enum class EmailConstants( - val templateName: String, - val title: String, - val content: String, -) { - WORKSPACE_INVITED("workspace-invite", "워크스페이스에 초대되었습니다.", "%s님은 %s 워크스페이스에 초대되었습니다."), - WORKSPACE_JOINED("workspace-join", "워크스페이스에 가입되었습니다.", "%s님은 %s 워크스페이스에 초대되었습니다."), - NOT_COMPLETED_NOTIFICATION("event-notification", "미완료된 이벤트", "[%s]가 마감 %s 전입니다. 맡은 업무를 완료해주세요."), - OVERDUE_NOTIFICATION("event-notification", "마감이 지난 이벤트", "[%s]의 마감 기한이 지났습니다. 맡은 업무를 완료해주세요."), -} diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/service/EmailEventService.kt b/infra/src/main/kotlin/com/backgu/amaker/infra/mail/service/EmailEventService.kt deleted file mode 100644 index 0620343d..00000000 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/service/EmailEventService.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.backgu.amaker.infra.mail.service - -import com.backgu.amaker.domain.notifiacation.Notification -import org.springframework.context.ApplicationEventPublisher -import org.springframework.stereotype.Service - -@Service -class EmailEventService( - private val eventPublisher: ApplicationEventPublisher, -) { - fun publishEmailEvent(emailEvent: Notification) { - eventPublisher.publishEvent(emailEvent) - } -} diff --git a/notification/build.gradle.kts b/notification/build.gradle.kts index b8675b7e..25e257f4 100644 --- a/notification/build.gradle.kts +++ b/notification/build.gradle.kts @@ -20,6 +20,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-redis") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("com.google.firebase:firebase-admin:6.8.1") + implementation("org.springframework.boot:spring-boot-starter-mail") + implementation("org.springframework.boot:spring-boot-starter-thymeleaf") + implementation("software.amazon.awssdk:ses:2.27.8") + implementation("org.apache.httpcomponents:httpclient:4.5.13") testImplementation(kotlin("test")) testImplementation("com.redis.testcontainers:testcontainers-redis-junit:1.6.4") diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/NotificationApplication.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/NotificationApplication.kt index ae5ce6bd..56f09f34 100644 --- a/notification/src/main/kotlin/com/backgu/amaker/notification/NotificationApplication.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/NotificationApplication.kt @@ -3,7 +3,6 @@ package com.backgu.amaker.notification import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.runApplication -import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories import org.springframework.data.redis.repository.configuration.EnableRedisRepositories import org.springframework.scheduling.annotation.EnableAsync @@ -13,7 +12,6 @@ import org.springframework.scheduling.annotation.EnableAsync @EntityScan(basePackages = ["com.backgu.amaker.infra"]) @EnableJpaRepositories(basePackages = ["com.backgu.amaker.infra.jpa.user", "com.backgu.amaker.infra.jpa.workspace"]) @EnableRedisRepositories(basePackages = ["com.backgu.amaker.infra.redis"]) -@ComponentScan("com.backgu.amaker.notification", "com.backgu.amaker.infra.mail") class NotificationApplication fun main(args: Array) { diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/email/config/EMailSenderConfig.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/config/EMailSenderConfig.kt new file mode 100644 index 00000000..d08ed003 --- /dev/null +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/config/EMailSenderConfig.kt @@ -0,0 +1,17 @@ +package com.backgu.amaker.notification.email.config + +import com.backgu.amaker.notification.email.service.EmailSender +import com.backgu.amaker.notification.email.ses.config.AWSSESConfig +import com.backgu.amaker.notification.email.ses.service.SESEmailService +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import software.amazon.awssdk.services.ses.SesClient + +@Configuration +class EMailSenderConfig { + @Bean + fun mailSender( + sesClient: SesClient, + sesConfig: AWSSESConfig, + ): EmailSender = SESEmailService(sesClient, sesConfig) +} diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailClientConfig.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailClientConfig.kt similarity index 91% rename from infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailClientConfig.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailClientConfig.kt index 2134bd3d..0f356449 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailClientConfig.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailClientConfig.kt @@ -1,4 +1,4 @@ -package com.backgu.amaker.infra.mail.config +package com.backgu.amaker.notification.email.gmail.config import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Configuration diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailConstantsConfig.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailConstantsConfig.kt similarity index 94% rename from infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailConstantsConfig.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailConstantsConfig.kt index 6ba92f98..62e7f09a 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/config/MailConstantsConfig.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/config/MailConstantsConfig.kt @@ -1,4 +1,4 @@ -package com.backgu.amaker.infra.mail.config +package com.backgu.amaker.notification.email.gmail.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/HtmlEmailSender.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/HtmlEmailSender.kt similarity index 84% rename from infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/HtmlEmailSender.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/HtmlEmailSender.kt index 983ea14c..a1cf63e9 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/HtmlEmailSender.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/HtmlEmailSender.kt @@ -1,15 +1,13 @@ -package com.backgu.amaker.infra.mail.infra +package com.backgu.amaker.notification.email.gmail.infra -import com.backgu.amaker.application.notification.mail.service.EmailSender +import com.backgu.amaker.notification.email.service.EmailSender import io.github.oshai.kotlinlogging.KotlinLogging import jakarta.mail.internet.MimeMessage import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.MimeMessageHelper -import org.springframework.stereotype.Component private val logger = KotlinLogging.logger {} -@Component class HtmlEmailSender( val mailSender: JavaMailSender, ) : EmailSender { diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/ThymeleafEmailTemplateBuilder.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/ThymeleafEmailTemplateBuilder.kt similarity index 86% rename from infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/ThymeleafEmailTemplateBuilder.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/ThymeleafEmailTemplateBuilder.kt index 78980903..0db6e9e8 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/infra/mail/infra/ThymeleafEmailTemplateBuilder.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/gmail/infra/ThymeleafEmailTemplateBuilder.kt @@ -1,6 +1,6 @@ -package com.backgu.amaker.infra.mail.infra +package com.backgu.amaker.notification.email.gmail.infra -import com.backgu.amaker.application.notification.mail.service.EmailTemplateBuilder +import com.backgu.amaker.notification.email.service.EmailTemplateBuilder import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Component import org.thymeleaf.TemplateEngine diff --git a/infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailSender.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailSender.kt similarity index 66% rename from infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailSender.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailSender.kt index 9ff2ce62..d4dc2040 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailSender.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailSender.kt @@ -1,4 +1,4 @@ -package com.backgu.amaker.application.notification.mail.service +package com.backgu.amaker.notification.email.service interface EmailSender { fun sendEmail( diff --git a/infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailTemplateBuilder.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailTemplateBuilder.kt similarity index 68% rename from infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailTemplateBuilder.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailTemplateBuilder.kt index 07a0c81d..78972990 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/application/notification/mail/service/EmailTemplateBuilder.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/EmailTemplateBuilder.kt @@ -1,4 +1,4 @@ -package com.backgu.amaker.application.notification.mail.service +package com.backgu.amaker.notification.email.service interface EmailTemplateBuilder { fun buildEmailContent( diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/email/templated/TemplateEmailHandler.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/TemplateEmailHandler.kt similarity index 78% rename from notification/src/main/kotlin/com/backgu/amaker/notification/email/templated/TemplateEmailHandler.kt rename to notification/src/main/kotlin/com/backgu/amaker/notification/email/service/TemplateEmailHandler.kt index 5bd61171..b9af27a4 100644 --- a/notification/src/main/kotlin/com/backgu/amaker/notification/email/templated/TemplateEmailHandler.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/service/TemplateEmailHandler.kt @@ -1,7 +1,5 @@ -package com.backgu.amaker.notification.email.templated +package com.backgu.amaker.notification.email.service -import com.backgu.amaker.application.notification.mail.service.EmailSender -import com.backgu.amaker.application.notification.mail.service.EmailTemplateBuilder import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod import com.backgu.amaker.domain.user.User import org.springframework.stereotype.Service diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/config/AWSSESConfig.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/config/AWSSESConfig.kt new file mode 100644 index 00000000..711b513a --- /dev/null +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/config/AWSSESConfig.kt @@ -0,0 +1,26 @@ +package com.backgu.amaker.notification.email.ses.config + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.ses.SesClient + +@Configuration +@ConfigurationProperties(prefix = "amazon.ses") +class AWSSESConfig { + lateinit var accessKey: String + lateinit var secretKey: String + lateinit var region: String + lateinit var sender: String + + @Bean + fun sesClient(): SesClient = + SesClient + .builder() + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) + .build() +} diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/service/SESEmailService.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/service/SESEmailService.kt new file mode 100644 index 00000000..f58610cb --- /dev/null +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/email/ses/service/SESEmailService.kt @@ -0,0 +1,73 @@ +package com.backgu.amaker.notification.email.ses.service + +import com.backgu.amaker.notification.email.service.EmailSender +import com.backgu.amaker.notification.email.ses.config.AWSSESConfig +import io.github.oshai.kotlinlogging.KotlinLogging +import software.amazon.awssdk.services.ses.SesClient +import software.amazon.awssdk.services.ses.model.Body +import software.amazon.awssdk.services.ses.model.Content +import software.amazon.awssdk.services.ses.model.Destination +import software.amazon.awssdk.services.ses.model.Message +import software.amazon.awssdk.services.ses.model.SendEmailRequest +import software.amazon.awssdk.services.ses.model.SesException + +private val logger = KotlinLogging.logger {} + +class SESEmailService( + private val sesClient: SesClient, + private val sesConfig: AWSSESConfig, +) : EmailSender { + override fun sendEmail( + emailAddress: String, + title: String, + body: String, + ) { + val destination: Destination = + Destination + .builder() + .toAddresses(emailAddress) + .build() + + val subjectContent: Content = + Content + .builder() + .data(title) + .charset("UTF-8") + .build() + + val bodyContent: Content = + Content + .builder() + .data(body) + .charset("UTF-8") + .build() + + val body: Body = + Body + .builder() + .html(bodyContent) + .build() + + val message: Message = + Message + .builder() + .subject(subjectContent) + .body(body) + .build() + + val request: SendEmailRequest = + SendEmailRequest + .builder() + .destination(destination) + .message(message) + .source(sesConfig.sender) + .build() + + try { + sesClient.sendEmail(request) + } catch (e: SesException) { + logger.error(e) { "Failed to send email to $emailAddress with title $title and body $body" } + throw e + } + } +} diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserFulfilledHandlerAdapter.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserFulfilledHandlerAdapter.kt index 438ff0c6..bbf5b088 100644 --- a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserFulfilledHandlerAdapter.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserFulfilledHandlerAdapter.kt @@ -4,7 +4,7 @@ import com.backgu.amaker.domain.notifiacation.Notification import com.backgu.amaker.domain.notifiacation.UserFulfilledNotification import com.backgu.amaker.domain.notifiacation.method.NotificationMethod import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod -import com.backgu.amaker.notification.email.templated.TemplateEmailHandler +import com.backgu.amaker.notification.email.service.TemplateEmailHandler import org.springframework.stereotype.Component @Component diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserHandlerAdapter.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserHandlerAdapter.kt index 62a56db0..373416a8 100644 --- a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserHandlerAdapter.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailUserHandlerAdapter.kt @@ -5,7 +5,7 @@ import com.backgu.amaker.domain.notifiacation.Notification import com.backgu.amaker.domain.notifiacation.UserNotification import com.backgu.amaker.domain.notifiacation.method.NotificationMethod import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod -import com.backgu.amaker.notification.email.templated.TemplateEmailHandler +import com.backgu.amaker.notification.email.service.TemplateEmailHandler import org.springframework.stereotype.Component @Component diff --git a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailWorkspaceHandlerAdapter.kt b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailWorkspaceHandlerAdapter.kt index ace3bbec..623e4a38 100644 --- a/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailWorkspaceHandlerAdapter.kt +++ b/notification/src/main/kotlin/com/backgu/amaker/notification/service/adapter/TemplateEmailWorkspaceHandlerAdapter.kt @@ -5,7 +5,7 @@ import com.backgu.amaker.domain.notifiacation.Notification import com.backgu.amaker.domain.notifiacation.WorkspaceNotification import com.backgu.amaker.domain.notifiacation.method.NotificationMethod import com.backgu.amaker.domain.notifiacation.method.TemplateEmailNotificationMethod -import com.backgu.amaker.notification.email.templated.TemplateEmailHandler +import com.backgu.amaker.notification.email.service.TemplateEmailHandler import org.springframework.stereotype.Component @Component diff --git a/notification/src/main/resources/application.yaml b/notification/src/main/resources/application.yaml index 43983c50..847a7478 100644 --- a/notification/src/main/resources/application.yaml +++ b/notification/src/main/resources/application.yaml @@ -49,6 +49,13 @@ management: exposure: include: health,info,metrics,prometheus +amazon: + ses: + sender: ${EMAIL_SENDER} + access-key: ${AWS_SES_ACCESS} + secret-key: ${AWS_SES_SECRET} + region: ${AWS_REGION} + fcm: file: hi.json base-url: https://fcm.googleapis.com/v1/projects/a-maker-2b48b