From 59fb90c206dee58875f921a3eb0ab03488b5e69f Mon Sep 17 00:00:00 2001 From: toino Date: Thu, 23 Feb 2023 16:58:43 +0000 Subject: [PATCH 01/10] Add email service --- build.gradle.kts | 2 + .../ni/website/backend/BackendApplication.kt | 3 +- .../config/email/EmailConfigProperties.kt | 9 +++ .../website/backend/email/BaseEmailBuilder.kt | 61 +++++++++++++++++++ .../ni/website/backend/email/EmailBuilder.kt | 8 +++ .../backend/email/SimpleEmailBuilder.kt | 38 ++++++++++++ .../website/backend/service/EmailService.kt | 22 +++++++ src/main/resources/application.properties | 10 +++ 8 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6f2a76ae..32094fd2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,6 +38,8 @@ dependencies { implementation("com.cloudinary:cloudinary:1.0.14") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") implementation("org.springframework.boot:spring-boot-starter-validation:3.1.1") + implementation("org.springframework.boot:spring-boot-starter-mail:3.0.2") + implementation("org.springframework.boot:spring-boot-starter-mustache:3.0.2") developmentOnly("org.springframework.boot:spring-boot-devtools") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/BackendApplication.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/BackendApplication.kt index 5c1bbe1c..26cb571b 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/BackendApplication.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/BackendApplication.kt @@ -5,10 +5,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication import org.springframework.data.jpa.repository.config.EnableJpaAuditing import pt.up.fe.ni.website.backend.config.auth.AuthConfigProperties +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.config.upload.UploadConfigProperties @SpringBootApplication -@EnableConfigurationProperties(AuthConfigProperties::class, UploadConfigProperties::class) +@EnableConfigurationProperties(AuthConfigProperties::class, UploadConfigProperties::class, EmailConfigProperties::class) @EnableJpaAuditing class BackendApplication diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt new file mode 100644 index 00000000..751c9be0 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt @@ -0,0 +1,9 @@ +package pt.up.fe.ni.website.backend.config.email + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties(prefix = "email") +data class EmailConfigProperties( + val from: String, + val fromPersonal: String? +) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt new file mode 100644 index 00000000..f63edd0e --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -0,0 +1,61 @@ +package pt.up.fe.ni.website.backend.email + +import jakarta.validation.Valid +import jakarta.validation.constraints.Email +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties +import pt.up.fe.ni.website.backend.model.Account + +abstract class BaseEmailBuilder : EmailBuilder { + private var from: String? = null + private var fromPersonal: String? = null + private var to: MutableSet = mutableSetOf() + private var cc: MutableSet = mutableSetOf() + private var bcc: MutableSet = mutableSetOf() + + fun from(@Email email: String) = apply { + from = email + } + + fun fromPersonal(name: String) = apply { + fromPersonal = name + } + + fun to(@Email vararg emails: String) = apply { + to.addAll(emails) + } + + fun to(@Valid vararg users: Account) = apply { + to.addAll(users.map { it.email }) + } + + fun cc(@Email vararg emails: String) = apply { + cc.addAll(emails) + } + + fun cc(@Valid vararg users: Account) = apply { + cc.addAll(users.map { it.email }) + } + + fun bcc(@Email vararg emails: String) = apply { + bcc.addAll(emails) + } + + fun bcc(@Valid vararg users: Account) = apply { + bcc.addAll(users.map { it.email }) + } + + override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { + if (from == null) { + helper.setFrom(emailConfigProperties.from, emailConfigProperties.fromPersonal ?: emailConfigProperties.from) + } else if (fromPersonal == null) { + helper.setFrom(from!!) + } else { + helper.setFrom(from!!, fromPersonal!!) + } + + to.forEach(helper::setTo) + cc.forEach(helper::setCc) + bcc.forEach(helper::setBcc) + } +} diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt new file mode 100644 index 00000000..69a259d8 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt @@ -0,0 +1,8 @@ +package pt.up.fe.ni.website.backend.email + +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties + +interface EmailBuilder { + fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) +} diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt new file mode 100644 index 00000000..eb34b334 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt @@ -0,0 +1,38 @@ +package pt.up.fe.ni.website.backend.email + +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties + +class SimpleEmailBuilder : BaseEmailBuilder() { + private var text: String? = null + private var html: String? = null + private var subject: String? = null + + fun text(text: String) = apply { + this.text = text + } + + fun html(html: String) = apply { + this.html = html + } + + fun subject(subject: String) = apply { + this.subject = subject + } + + override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { + super.build(helper, emailConfigProperties) + + if (text != null && html != null) { + helper.setText(text!!, html!!) + } else if (text != null) { + helper.setText(text!!) + } else if (html != null) { + helper.setText(html!!, true) + } + + if (subject != null) { + helper.setSubject(subject!!) + } + } +} diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt new file mode 100644 index 00000000..79ac8db2 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt @@ -0,0 +1,22 @@ +package pt.up.fe.ni.website.backend.service + +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.mail.javamail.MimeMessageHelper +import org.springframework.stereotype.Service +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties +import pt.up.fe.ni.website.backend.email.EmailBuilder + +@Service +class EmailService( + private val mailSender: JavaMailSender, + val emailConfigProperties: EmailConfigProperties +) { + fun send(email: EmailBuilder) { + val message = mailSender.createMimeMessage() + + val helper = MimeMessageHelper(message, true) + email.build(helper, emailConfigProperties) + + mailSender.send(message) + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 449647da..912180c7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -38,3 +38,13 @@ upload.static-serve=http://localhost:3000/static # Cors Origin cors.allow-origin = http://localhost:3000 + +# Email config +spring.mail.properties[mail.smtp.auth]=true +spring.mail.properties[mail.smtp.starttls.enable]=true +spring.mail.properties[mail.smtp.connectiontimeout]=5000 +spring.mail.properties[mail.smtp.timeout]=3000 +spring.mail.properties[mail.smtp.writetimeout]=5000 + +email.from=ni@aefeup.pt +email.from-personal=NIAEFEUP From ec5e79a7c33f6ca12a2f671759a713ab2fdc8bef Mon Sep 17 00:00:00 2001 From: toino Date: Thu, 23 Feb 2023 22:39:55 +0000 Subject: [PATCH 02/10] Add template email builder --- .../backend/email/TemplateEmailBuilder.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt new file mode 100644 index 00000000..3af5dd15 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -0,0 +1,49 @@ +package pt.up.fe.ni.website.backend.email + +import com.samskivert.mustache.Mustache +import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties + +abstract class TemplateEmailBuilder : BaseEmailBuilder() { + private var data: T? = null + + open val htmlTemplatePath: String? = null + open val textTemplatePath: String? = null + + protected open fun subject(data: T?): String = "" + + fun data(data: T) = apply { + this.data = data + } + + override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { + super.build(helper, emailConfigProperties) + + helper.setSubject(subject(data)) + + val mustache = Mustache.compiler().withLoader( + MustacheResourceTemplateLoader("classpath:/templates/email/", ".mustache") + ) + var text: String? = null + var html: String? = null + + if (textTemplatePath != null) { + val textTemplate = mustache.loadTemplate(textTemplatePath) + text = textTemplate.execute(data) + } + + if (htmlTemplatePath != null) { + val htmlTemplate = mustache.loadTemplate(htmlTemplatePath) + html = htmlTemplate.execute(data) + } + + if (text != null && html != null) { + helper.setText(text, html) + } else if (text != null) { + helper.setText(text) + } else if (html != null) { + helper.setText(html, true) + } + } +} From 213ef50479de912212a99ffde7d4505b4f34c946 Mon Sep 17 00:00:00 2001 From: toino Date: Sat, 25 Feb 2023 05:16:19 +0000 Subject: [PATCH 03/10] Add more properties to email config --- .../backend/config/email/EmailConfigProperties.kt | 4 +++- .../ni/website/backend/email/BaseEmailBuilder.kt | 15 +++------------ .../website/backend/email/TemplateEmailBuilder.kt | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt index 751c9be0..cba5c5bd 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt @@ -5,5 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties(prefix = "email") data class EmailConfigProperties( val from: String, - val fromPersonal: String? + val fromPersonal: String = from, + val templatePrefix: String = "classpath:/templates/email/", + val templateSuffix: String = ".mustache" ) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt index f63edd0e..7762bdea 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -13,12 +13,9 @@ abstract class BaseEmailBuilder : EmailBuilder { private var cc: MutableSet = mutableSetOf() private var bcc: MutableSet = mutableSetOf() - fun from(@Email email: String) = apply { + fun from(@Email email: String, personal: String = email) = apply { from = email - } - - fun fromPersonal(name: String) = apply { - fromPersonal = name + fromPersonal = personal } fun to(@Email vararg emails: String) = apply { @@ -46,13 +43,7 @@ abstract class BaseEmailBuilder : EmailBuilder { } override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { - if (from == null) { - helper.setFrom(emailConfigProperties.from, emailConfigProperties.fromPersonal ?: emailConfigProperties.from) - } else if (fromPersonal == null) { - helper.setFrom(from!!) - } else { - helper.setFrom(from!!, fromPersonal!!) - } + helper.setFrom(from ?: emailConfigProperties.from, fromPersonal ?: emailConfigProperties.fromPersonal) to.forEach(helper::setTo) cc.forEach(helper::setCc) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt index 3af5dd15..136a11df 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -23,7 +23,7 @@ abstract class TemplateEmailBuilder : BaseEmailBuilder() { helper.setSubject(subject(data)) val mustache = Mustache.compiler().withLoader( - MustacheResourceTemplateLoader("classpath:/templates/email/", ".mustache") + MustacheResourceTemplateLoader(emailConfigProperties.templatePrefix, emailConfigProperties.templateSuffix) ) var text: String? = null var html: String? = null From 445a092790562f3d0e8f4bb2904945d58d3cd364 Mon Sep 17 00:00:00 2001 From: toino Date: Sat, 25 Feb 2023 11:01:12 +0000 Subject: [PATCH 04/10] Implement email attachments --- .../website/backend/email/BaseEmailBuilder.kt | 25 +++++++++++++++ .../backend/email/TemplateEmailBuilder.kt | 31 ++++++++++++++----- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt index 7762bdea..3afe90d1 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -1,7 +1,12 @@ package pt.up.fe.ni.website.backend.email +import jakarta.activation.DataSource +import jakarta.activation.FileDataSource +import jakarta.activation.URLDataSource import jakarta.validation.Valid import jakarta.validation.constraints.Email +import java.io.File +import java.net.URL import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.model.Account @@ -12,6 +17,7 @@ abstract class BaseEmailBuilder : EmailBuilder { private var to: MutableSet = mutableSetOf() private var cc: MutableSet = mutableSetOf() private var bcc: MutableSet = mutableSetOf() + private var attachments: MutableList = mutableListOf() fun from(@Email email: String, personal: String = email) = apply { from = email @@ -42,11 +48,30 @@ abstract class BaseEmailBuilder : EmailBuilder { bcc.addAll(users.map { it.email }) } + fun attach(name: String, content: DataSource) = apply { + attachments.add(Attachment(name, content)) + } + + fun attach(name: String, content: File) = apply { + attachments.add(Attachment(name, FileDataSource(content))) + } + + fun attach(name: String, path: String) = apply { + attachments.add(Attachment(name, URLDataSource(URL(path)))) + } + override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { helper.setFrom(from ?: emailConfigProperties.from, fromPersonal ?: emailConfigProperties.fromPersonal) to.forEach(helper::setTo) cc.forEach(helper::setCc) bcc.forEach(helper::setBcc) + + attachments.forEach { helper.addAttachment(it.name, it.content) } } + + protected data class Attachment( + val name: String, + val content: DataSource + ) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt index 136a11df..3d399742 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -1,6 +1,7 @@ package pt.up.fe.ni.website.backend.email import com.samskivert.mustache.Mustache +import java.io.ByteArrayInputStream import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties @@ -8,10 +9,10 @@ import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties abstract class TemplateEmailBuilder : BaseEmailBuilder() { private var data: T? = null - open val htmlTemplatePath: String? = null - open val textTemplatePath: String? = null - - protected open fun subject(data: T?): String = "" + protected open fun subject(data: T?): String? = null + protected open fun htmlTemplatePath(data: T?): String? = null + protected open fun textTemplatePath(data: T?): String? = null + protected open fun attachments(data: T?): List = emptyList() fun data(data: T) = apply { this.data = data @@ -20,19 +21,24 @@ abstract class TemplateEmailBuilder : BaseEmailBuilder() { override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { super.build(helper, emailConfigProperties) - helper.setSubject(subject(data)) + val subject = subject(data) + if (subject != null) { + helper.setSubject(subject) + } val mustache = Mustache.compiler().withLoader( MustacheResourceTemplateLoader(emailConfigProperties.templatePrefix, emailConfigProperties.templateSuffix) ) - var text: String? = null - var html: String? = null + var text: String? = null + val textTemplatePath = textTemplatePath(data) if (textTemplatePath != null) { val textTemplate = mustache.loadTemplate(textTemplatePath) text = textTemplate.execute(data) } + var html: String? = null + val htmlTemplatePath = htmlTemplatePath(data) if (htmlTemplatePath != null) { val htmlTemplate = mustache.loadTemplate(htmlTemplatePath) html = htmlTemplate.execute(data) @@ -45,5 +51,16 @@ abstract class TemplateEmailBuilder : BaseEmailBuilder() { } else if (html != null) { helper.setText(html, true) } + + for (attachment in attachments(data)) { + val template = mustache.loadTemplate(attachment.path(data)) + val content = template.execute(data) + helper.addAttachment(attachment.name(data)) { ByteArrayInputStream(content.encodeToByteArray()) } + } + } + + protected abstract inner class TemplateAttachment { + abstract fun path(data: T?): String + abstract fun name(data: T?): String } } From a1a75b210cd2da030241107d310e0c12df3bfa48 Mon Sep 17 00:00:00 2001 From: toino Date: Sat, 4 Mar 2023 01:30:20 +0000 Subject: [PATCH 05/10] Add default empty email credentials --- src/main/resources/application.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 912180c7..80a4c760 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -40,6 +40,10 @@ upload.static-serve=http://localhost:3000/static cors.allow-origin = http://localhost:3000 # Email config +spring.mail.host= +spring.mail.port= +spring.mail.username= +spring.mail.password= spring.mail.properties[mail.smtp.auth]=true spring.mail.properties[mail.smtp.starttls.enable]=true spring.mail.properties[mail.smtp.connectiontimeout]=5000 From 0604abc300fe82a04278504eeac48e4d366f2db5 Mon Sep 17 00:00:00 2001 From: toino Date: Tue, 7 Mar 2023 18:54:30 +0000 Subject: [PATCH 06/10] Use markdown for email templates and add support for inline files --- build.gradle.kts | 2 + .../backend/email/TemplateEmailBuilder.kt | 84 ++++++++++--------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 32094fd2..0154c12a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,6 +40,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-validation:3.1.1") implementation("org.springframework.boot:spring-boot-starter-mail:3.0.2") implementation("org.springframework.boot:spring-boot-starter-mustache:3.0.2") + implementation("org.commonmark:commonmark:0.21.0") + implementation("org.commonmark:commonmark-ext-yaml-front-matter:0.21.0") developmentOnly("org.springframework.boot:spring-boot-devtools") runtimeOnly("com.h2database:h2") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt index 3d399742..551eecd6 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -1,19 +1,22 @@ package pt.up.fe.ni.website.backend.email import com.samskivert.mustache.Mustache -import java.io.ByteArrayInputStream +import org.commonmark.ext.front.matter.YamlFrontMatterExtension +import org.commonmark.ext.front.matter.YamlFrontMatterVisitor +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer +import org.commonmark.renderer.text.TextContentRenderer import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader +import org.springframework.core.io.Resource +import org.springframework.core.io.UrlResource import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties -abstract class TemplateEmailBuilder : BaseEmailBuilder() { +abstract class TemplateEmailBuilder( + private val template: String +) : BaseEmailBuilder() { private var data: T? = null - protected open fun subject(data: T?): String? = null - protected open fun htmlTemplatePath(data: T?): String? = null - protected open fun textTemplatePath(data: T?): String? = null - protected open fun attachments(data: T?): List = emptyList() - fun data(data: T) = apply { this.data = data } @@ -21,46 +24,51 @@ abstract class TemplateEmailBuilder : BaseEmailBuilder() { override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { super.build(helper, emailConfigProperties) - val subject = subject(data) - if (subject != null) { - helper.setSubject(subject) - } - val mustache = Mustache.compiler().withLoader( MustacheResourceTemplateLoader(emailConfigProperties.templatePrefix, emailConfigProperties.templateSuffix) ) - var text: String? = null - val textTemplatePath = textTemplatePath(data) - if (textTemplatePath != null) { - val textTemplate = mustache.loadTemplate(textTemplatePath) - text = textTemplate.execute(data) - } + val markdown = mustache.loadTemplate(template).execute(data) - var html: String? = null - val htmlTemplatePath = htmlTemplatePath(data) - if (htmlTemplatePath != null) { - val htmlTemplate = mustache.loadTemplate(htmlTemplatePath) - html = htmlTemplate.execute(data) - } + val commonmarkParser = Parser.builder().extensions( + listOf( + YamlFrontMatterExtension.create() + ) + ).build() + val commonmarkHtmlRenderer = HtmlRenderer.builder().build() + val commonmarkTextRenderer = TextContentRenderer.builder().build() - if (text != null && html != null) { - helper.setText(text, html) - } else if (text != null) { - helper.setText(text) - } else if (html != null) { - helper.setText(html, true) - } + val doc = commonmarkParser.parse(markdown) + + val html = commonmarkHtmlRenderer.render(doc) + val text = commonmarkTextRenderer.render(doc) + + helper.setText(text, html) - for (attachment in attachments(data)) { - val template = mustache.loadTemplate(attachment.path(data)) - val content = template.execute(data) - helper.addAttachment(attachment.name(data)) { ByteArrayInputStream(content.encodeToByteArray()) } + val yamlVisitor = YamlFrontMatterVisitor() + doc.accept(yamlVisitor) + + val subject = yamlVisitor.data["subject"]?.getOrNull(0) + if (subject != null) { + helper.setSubject(subject) } + + print(yamlVisitor.data) + + yamlVisitor.data.getOrDefault("attachments", emptyList()).forEach { addFile(helper::addAttachment, it) } + yamlVisitor.data.getOrDefault("inline", emptyList()).forEach { addFile(helper::addInline, it) } } - protected abstract inner class TemplateAttachment { - abstract fun path(data: T?): String - abstract fun name(data: T?): String + private fun addFile(fn: (String, Resource) -> Any, file: String) { + val split = file.split("\\s*::\\s*".toRegex()) + + if (split.size != 2) { + return + } + + val name = split[0] + val path = split[1] + + fn(name, UrlResource(path)) } } From 65d97f7273e0502725826bac387785816ead823d Mon Sep 17 00:00:00 2001 From: toino Date: Fri, 17 Mar 2023 15:45:18 +0000 Subject: [PATCH 07/10] Add html layout and style support for emails --- .../config/email/EmailConfigProperties.kt | 6 +- .../backend/email/TemplateEmailBuilder.kt | 59 +++++++++++++------ src/main/resources/email/style.css | 3 + .../templates/email/layout.html.mustache | 20 +++++++ 4 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 src/main/resources/email/style.css create mode 100644 src/main/resources/templates/email/layout.html.mustache diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt index cba5c5bd..778d435d 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfigProperties.kt @@ -6,6 +6,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties data class EmailConfigProperties( val from: String, val fromPersonal: String = from, - val templatePrefix: String = "classpath:/templates/email/", - val templateSuffix: String = ".mustache" + val templatePrefix: String = "classpath:templates/email/", + val templateSuffix: String = ".mustache", + val defaultHtmlLayout: String = "layout.html", + val defaultStyle: String = "classpath:email/style.css" ) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt index 551eecd6..54e3ef19 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -10,11 +10,22 @@ import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateL import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.mail.javamail.MimeMessageHelper +import org.springframework.util.ResourceUtils import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties abstract class TemplateEmailBuilder( private val template: String ) : BaseEmailBuilder() { + private companion object { + val commonmarkParser: Parser = Parser.builder().extensions( + listOf( + YamlFrontMatterExtension.create() + ) + ).build() + val commonmarkHtmlRenderer: HtmlRenderer = HtmlRenderer.builder().build() + val commonmarkTextRenderer: TextContentRenderer = TextContentRenderer.builder().build() + } + private var data: T? = null fun data(data: T) = apply { @@ -30,45 +41,55 @@ abstract class TemplateEmailBuilder( val markdown = mustache.loadTemplate(template).execute(data) - val commonmarkParser = Parser.builder().extensions( - listOf( - YamlFrontMatterExtension.create() - ) - ).build() - val commonmarkHtmlRenderer = HtmlRenderer.builder().build() - val commonmarkTextRenderer = TextContentRenderer.builder().build() - val doc = commonmarkParser.parse(markdown) - - val html = commonmarkHtmlRenderer.render(doc) + val htmlContent = commonmarkHtmlRenderer.render(doc) val text = commonmarkTextRenderer.render(doc) - helper.setText(text, html) - val yamlVisitor = YamlFrontMatterVisitor() doc.accept(yamlVisitor) - val subject = yamlVisitor.data["subject"]?.getOrNull(0) + val subject = yamlVisitor.data["subject"]?.firstOrNull() if (subject != null) { helper.setSubject(subject) } - print(yamlVisitor.data) - yamlVisitor.data.getOrDefault("attachments", emptyList()).forEach { addFile(helper::addAttachment, it) } yamlVisitor.data.getOrDefault("inline", emptyList()).forEach { addFile(helper::addInline, it) } + + val styles = yamlVisitor.data.getOrDefault("styles", mutableListOf()).apply { + if (yamlVisitor.data["no_default_style"].isNullOrEmpty()) { + this.add(emailConfigProperties.defaultStyle) + } + }.map { + ResourceUtils.getFile(it).readText() + } + + val htmlTemplate = yamlVisitor.data["layout"]?.firstOrNull() ?: emailConfigProperties.defaultHtmlLayout + val html = mustache.loadTemplate(htmlTemplate).execute( + mapOf( + "subject" to subject, + "content" to htmlContent, + "styles" to styles + ) + ) + + helper.setText(text, html) } - private fun addFile(fn: (String, Resource) -> Any, file: String) { - val split = file.split("\\s*::\\s*".toRegex()) + private fun addFile(fn: (String, Resource) -> Any, file: String): Pair? { + var split = file.split("\\s*::\\s*".toRegex(), 2) - if (split.size != 2) { - return + if (split.isEmpty()) { + return null + } else if (split.size == 1) { + split = listOf(split[0], split[0]) } val name = split[0] val path = split[1] fn(name, UrlResource(path)) + + return Pair(name, path) } } diff --git a/src/main/resources/email/style.css b/src/main/resources/email/style.css new file mode 100644 index 00000000..d34cd93f --- /dev/null +++ b/src/main/resources/email/style.css @@ -0,0 +1,3 @@ +/* +TODO +*/ diff --git a/src/main/resources/templates/email/layout.html.mustache b/src/main/resources/templates/email/layout.html.mustache new file mode 100644 index 00000000..3b497c42 --- /dev/null +++ b/src/main/resources/templates/email/layout.html.mustache @@ -0,0 +1,20 @@ + + + + + + + {{{subject}}} + + {{#styles}} + + {{/styles}} + + + + {{{content}}} + + From 8c9a3136f285b5ab203b78ad4966927e37f4147d Mon Sep 17 00:00:00 2001 From: toino Date: Sat, 18 Mar 2023 19:41:18 +0000 Subject: [PATCH 08/10] Move attachment support to SimpleEmailBuilder, add inline support to it --- .../website/backend/email/BaseEmailBuilder.kt | 25 ------------ .../backend/email/SimpleEmailBuilder.kt | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt index 3afe90d1..7762bdea 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -1,12 +1,7 @@ package pt.up.fe.ni.website.backend.email -import jakarta.activation.DataSource -import jakarta.activation.FileDataSource -import jakarta.activation.URLDataSource import jakarta.validation.Valid import jakarta.validation.constraints.Email -import java.io.File -import java.net.URL import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.model.Account @@ -17,7 +12,6 @@ abstract class BaseEmailBuilder : EmailBuilder { private var to: MutableSet = mutableSetOf() private var cc: MutableSet = mutableSetOf() private var bcc: MutableSet = mutableSetOf() - private var attachments: MutableList = mutableListOf() fun from(@Email email: String, personal: String = email) = apply { from = email @@ -48,30 +42,11 @@ abstract class BaseEmailBuilder : EmailBuilder { bcc.addAll(users.map { it.email }) } - fun attach(name: String, content: DataSource) = apply { - attachments.add(Attachment(name, content)) - } - - fun attach(name: String, content: File) = apply { - attachments.add(Attachment(name, FileDataSource(content))) - } - - fun attach(name: String, path: String) = apply { - attachments.add(Attachment(name, URLDataSource(URL(path)))) - } - override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { helper.setFrom(from ?: emailConfigProperties.from, fromPersonal ?: emailConfigProperties.fromPersonal) to.forEach(helper::setTo) cc.forEach(helper::setCc) bcc.forEach(helper::setBcc) - - attachments.forEach { helper.addAttachment(it.name, it.content) } } - - protected data class Attachment( - val name: String, - val content: DataSource - ) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt index eb34b334..9a978b41 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt @@ -1,5 +1,10 @@ package pt.up.fe.ni.website.backend.email +import jakarta.activation.DataSource +import jakarta.activation.FileDataSource +import jakarta.activation.URLDataSource +import java.io.File +import java.net.URL import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties @@ -7,6 +12,8 @@ class SimpleEmailBuilder : BaseEmailBuilder() { private var text: String? = null private var html: String? = null private var subject: String? = null + private var attachments: MutableList = mutableListOf() + private var inlines: MutableList = mutableListOf() fun text(text: String) = apply { this.text = text @@ -20,6 +27,30 @@ class SimpleEmailBuilder : BaseEmailBuilder() { this.subject = subject } + fun attach(name: String, content: DataSource) = apply { + attachments.add(EmailFile(name, content)) + } + + fun attach(name: String, content: File) = apply { + attachments.add(EmailFile(name, FileDataSource(content))) + } + + fun attach(name: String, path: String) = apply { + attachments.add(EmailFile(name, URLDataSource(URL(path)))) + } + + fun inline(name: String, content: DataSource) = apply { + inlines.add(EmailFile(name, content)) + } + + fun inline(name: String, content: File) = apply { + inlines.add(EmailFile(name, FileDataSource(content))) + } + + fun inline(name: String, path: String) = apply { + inlines.add(EmailFile(name, URLDataSource(URL(path)))) + } + override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { super.build(helper, emailConfigProperties) @@ -34,5 +65,13 @@ class SimpleEmailBuilder : BaseEmailBuilder() { if (subject != null) { helper.setSubject(subject!!) } + + attachments.forEach { helper.addAttachment(it.name, it.content) } + inlines.forEach { helper.addInline(it.name, it.content) } } + + private data class EmailFile( + val name: String, + val content: DataSource + ) } From 33dc7311fc8b453ba33d11e723c43a08e93970e4 Mon Sep 17 00:00:00 2001 From: toino Date: Thu, 23 Mar 2023 16:24:06 +0000 Subject: [PATCH 09/10] Use beans in email builders --- .../backend/config/email/EmailConfig.kt | 33 +++++++++++++ .../website/backend/email/BaseEmailBuilder.kt | 5 +- .../ni/website/backend/email/EmailBuilder.kt | 3 +- .../backend/email/SimpleEmailBuilder.kt | 19 +++---- .../backend/email/TemplateEmailBuilder.kt | 49 ++++++------------- .../website/backend/service/EmailService.kt | 6 +-- 6 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfig.kt diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfig.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfig.kt new file mode 100644 index 00000000..ea325a07 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/config/email/EmailConfig.kt @@ -0,0 +1,33 @@ +package pt.up.fe.ni.website.backend.config.email + +import com.samskivert.mustache.Mustache +import org.commonmark.ext.front.matter.YamlFrontMatterExtension +import org.commonmark.parser.Parser +import org.commonmark.renderer.html.HtmlRenderer +import org.commonmark.renderer.text.TextContentRenderer +import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class EmailConfig( + private val emailConfigProperties: EmailConfigProperties +) { + @Bean + fun mustacheCompiler() = Mustache.compiler().withLoader( + MustacheResourceTemplateLoader(emailConfigProperties.templatePrefix, emailConfigProperties.templateSuffix) + ) + + @Bean + fun commonmarkParser() = Parser.builder().extensions( + listOf( + YamlFrontMatterExtension.create() + ) + ).build() + + @Bean + fun commonmarkHtmlRenderer() = HtmlRenderer.builder().build() + + @Bean + fun commonmarkTextRenderer() = TextContentRenderer.builder().build() +} diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt index 7762bdea..3487fb1e 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -3,10 +3,13 @@ package pt.up.fe.ni.website.backend.email import jakarta.validation.Valid import jakarta.validation.constraints.Email import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.ApplicationContextUtils import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.model.Account abstract class BaseEmailBuilder : EmailBuilder { + protected val emailConfigProperties = ApplicationContextUtils.getBean(EmailConfigProperties::class.java) + private var from: String? = null private var fromPersonal: String? = null private var to: MutableSet = mutableSetOf() @@ -42,7 +45,7 @@ abstract class BaseEmailBuilder : EmailBuilder { bcc.addAll(users.map { it.email }) } - override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { + override fun build(helper: MimeMessageHelper) { helper.setFrom(from ?: emailConfigProperties.from, fromPersonal ?: emailConfigProperties.fromPersonal) to.forEach(helper::setTo) diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt index 69a259d8..30a80db5 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/EmailBuilder.kt @@ -1,8 +1,7 @@ package pt.up.fe.ni.website.backend.email import org.springframework.mail.javamail.MimeMessageHelper -import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties interface EmailBuilder { - fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) + fun build(helper: MimeMessageHelper) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt index 9a978b41..ec625b00 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt @@ -6,7 +6,6 @@ import jakarta.activation.URLDataSource import java.io.File import java.net.URL import org.springframework.mail.javamail.MimeMessageHelper -import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties class SimpleEmailBuilder : BaseEmailBuilder() { private var text: String? = null @@ -51,20 +50,16 @@ class SimpleEmailBuilder : BaseEmailBuilder() { inlines.add(EmailFile(name, URLDataSource(URL(path)))) } - override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { - super.build(helper, emailConfigProperties) + override fun build(helper: MimeMessageHelper) { + super.build(helper) - if (text != null && html != null) { - helper.setText(text!!, html!!) - } else if (text != null) { - helper.setText(text!!) - } else if (html != null) { - helper.setText(html!!, true) + when { + text != null && html != null -> helper.setText(text!!, html!!) + html != null -> helper.setText(html!!, true) + text != null -> helper.setText(text!!) } - if (subject != null) { - helper.setSubject(subject!!) - } + subject?.let { helper.setSubject(it) } attachments.forEach { helper.addAttachment(it.name, it.content) } inlines.forEach { helper.addInline(it.name, it.content) } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt index 54e3ef19..c7a9cc09 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilder.kt @@ -1,30 +1,23 @@ package pt.up.fe.ni.website.backend.email import com.samskivert.mustache.Mustache -import org.commonmark.ext.front.matter.YamlFrontMatterExtension import org.commonmark.ext.front.matter.YamlFrontMatterVisitor import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.text.TextContentRenderer -import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader import org.springframework.core.io.Resource import org.springframework.core.io.UrlResource import org.springframework.mail.javamail.MimeMessageHelper import org.springframework.util.ResourceUtils -import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties +import pt.up.fe.ni.website.backend.config.ApplicationContextUtils abstract class TemplateEmailBuilder( private val template: String ) : BaseEmailBuilder() { - private companion object { - val commonmarkParser: Parser = Parser.builder().extensions( - listOf( - YamlFrontMatterExtension.create() - ) - ).build() - val commonmarkHtmlRenderer: HtmlRenderer = HtmlRenderer.builder().build() - val commonmarkTextRenderer: TextContentRenderer = TextContentRenderer.builder().build() - } + private val commonmarkParser = ApplicationContextUtils.getBean(Parser::class.java) + private val commonmarkHtmlRenderer = ApplicationContextUtils.getBean(HtmlRenderer::class.java) + private val commonmarkTextRenderer = ApplicationContextUtils.getBean(TextContentRenderer::class.java) + private val mustache = ApplicationContextUtils.getBean(Mustache.Compiler::class.java) private var data: T? = null @@ -32,12 +25,10 @@ abstract class TemplateEmailBuilder( this.data = data } - override fun build(helper: MimeMessageHelper, emailConfigProperties: EmailConfigProperties) { - super.build(helper, emailConfigProperties) + override fun build(helper: MimeMessageHelper) { + super.build(helper) - val mustache = Mustache.compiler().withLoader( - MustacheResourceTemplateLoader(emailConfigProperties.templatePrefix, emailConfigProperties.templateSuffix) - ) + if (data == null) return val markdown = mustache.loadTemplate(template).execute(data) @@ -49,12 +40,7 @@ abstract class TemplateEmailBuilder( doc.accept(yamlVisitor) val subject = yamlVisitor.data["subject"]?.firstOrNull() - if (subject != null) { - helper.setSubject(subject) - } - - yamlVisitor.data.getOrDefault("attachments", emptyList()).forEach { addFile(helper::addAttachment, it) } - yamlVisitor.data.getOrDefault("inline", emptyList()).forEach { addFile(helper::addInline, it) } + subject?.let { helper.setSubject(it) } val styles = yamlVisitor.data.getOrDefault("styles", mutableListOf()).apply { if (yamlVisitor.data["no_default_style"].isNullOrEmpty()) { @@ -74,22 +60,19 @@ abstract class TemplateEmailBuilder( ) helper.setText(text, html) + + yamlVisitor.data.getOrDefault("attachments", emptyList()).forEach { addFile(helper::addAttachment, it) } + yamlVisitor.data.getOrDefault("inline", emptyList()).forEach { addFile(helper::addInline, it) } } - private fun addFile(fn: (String, Resource) -> Any, file: String): Pair? { - var split = file.split("\\s*::\\s*".toRegex(), 2) + private fun addFile(fn: (String, Resource) -> Any, file: String) { + val split = file.split("\\s*::\\s*".toRegex(), 2) - if (split.isEmpty()) { - return null - } else if (split.size == 1) { - split = listOf(split[0], split[0]) - } + if (split.isEmpty()) return val name = split[0] - val path = split[1] + val path = split.getOrElse(1) { split[0] } fn(name, UrlResource(path)) - - return Pair(name, path) } } diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt index 79ac8db2..61335751 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/service/EmailService.kt @@ -3,19 +3,17 @@ package pt.up.fe.ni.website.backend.service import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.MimeMessageHelper import org.springframework.stereotype.Service -import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.email.EmailBuilder @Service class EmailService( - private val mailSender: JavaMailSender, - val emailConfigProperties: EmailConfigProperties + private val mailSender: JavaMailSender ) { fun send(email: EmailBuilder) { val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) - email.build(helper, emailConfigProperties) + email.build(helper) mailSender.send(message) } From 9737590259a7aa57a3e55ac8102a1afdfa819118 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Sun, 18 Aug 2024 19:20:09 +0100 Subject: [PATCH 10/10] progress in tests (temporary changes) --- .../website/backend/email/BaseEmailBuilder.kt | 12 +-- .../backend/email/SimpleEmailBuilder.kt | 1 + .../backend/email/BaseEmailBuilderTest.kt | 94 +++++++++++++++++++ 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/test/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilderTest.kt diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt index 3487fb1e..9ebd78b3 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilder.kt @@ -8,13 +8,13 @@ import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties import pt.up.fe.ni.website.backend.model.Account abstract class BaseEmailBuilder : EmailBuilder { - protected val emailConfigProperties = ApplicationContextUtils.getBean(EmailConfigProperties::class.java) + protected open val emailConfigProperties = ApplicationContextUtils.getBean(EmailConfigProperties::class.java) - private var from: String? = null - private var fromPersonal: String? = null - private var to: MutableSet = mutableSetOf() - private var cc: MutableSet = mutableSetOf() - private var bcc: MutableSet = mutableSetOf() + var from: String? = null + var fromPersonal: String? = null + var to: MutableSet = mutableSetOf() + var cc: MutableSet = mutableSetOf() + var bcc: MutableSet = mutableSetOf() fun from(@Email email: String, personal: String = email) = apply { from = email diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt index ec625b00..a9424a70 100644 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilder.kt @@ -12,6 +12,7 @@ class SimpleEmailBuilder : BaseEmailBuilder() { private var html: String? = null private var subject: String? = null private var attachments: MutableList = mutableListOf() + // Inlines - similar to attachments, not shown as downloadable but can be inserted in an email. For example, inline images. private var inlines: MutableList = mutableListOf() fun text(text: String) = apply { diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilderTest.kt new file mode 100644 index 00000000..9bfaaae9 --- /dev/null +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/BaseEmailBuilderTest.kt @@ -0,0 +1,94 @@ +import jakarta.validation.ConstraintViolationException +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mockito +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.mail.javamail.JavaMailSenderImpl +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties +import pt.up.fe.ni.website.backend.email.BaseEmailBuilder +import pt.up.fe.ni.website.backend.model.Account + +@ExtendWith(MockitoExtension::class) +class BaseEmailBuilderTest { + private lateinit var emailConfigProperties: EmailConfigProperties + private lateinit var mimeMessageHelper: MimeMessageHelper + private lateinit var baseEmailBuilder: BaseEmailBuilderImpl + + @BeforeEach + fun setup() { + emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + Mockito.`when`(from).thenReturn("test@email.com") + Mockito.`when`(fromPersonal).thenReturn("Test") + } + + val javaMailSender = JavaMailSenderImpl() + val mimeMessage = javaMailSender.createMimeMessage() + mimeMessageHelper = MimeMessageHelper(mimeMessage, true) + + baseEmailBuilder = BaseEmailBuilderImpl(emailConfigProperties) + } + + @Test + fun `valid emails are correctly set in 'to' field`() { + baseEmailBuilder.to("to1@email.com", "to2@email.com") + baseEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals(setOf("to1@email.com", "to2@email.com"), baseEmailBuilder.getToEmails()) + } + + @Test + fun `invalid emails throw exception`() { + Assertions.assertThrows(ConstraintViolationException::class.java) { + baseEmailBuilder.to("invalid") + } + } + + @Test + fun `valid account emails are correctly set in 'to' field`() { + val account1 = Account("Account 1", "account1@email.com","account1password", null, null, null, null, null) + val account2 = Account("Account 2", "account2@email.com", "account2password", null, null, null, null, null) + + baseEmailBuilder.to(account1, account2) + baseEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals(setOf("account1@email.com", "account2@email.com"), baseEmailBuilder.getToEmails()) + } + + @Test + fun `emails are correctly set in 'cc' field`() { + baseEmailBuilder.cc("cc1@email.com", "cc2@email.com") + baseEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals(setOf("cc1@email.com", "cc2@email.com"), baseEmailBuilder.getCcEmails()) + } + + @Test + fun `emails are correctly set in 'bcc' field`() { + baseEmailBuilder.bcc("bcc1@email.com", "bcc2@email.com") + baseEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals(setOf("bcc1@email.com", "bcc2@email.com"), baseEmailBuilder.getBccEmails()) + } + + @Test + fun `'from' email and personal name are set correctly`() { + baseEmailBuilder.from("from@email.com", "From") + baseEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals("from@email.com", baseEmailBuilder.getFromEmail()) + Assertions.assertEquals("From", baseEmailBuilder.getName()) + } +} + +class BaseEmailBuilderImpl( + override val emailConfigProperties: EmailConfigProperties +) : BaseEmailBuilder() { + fun getToEmails(): Set = to + fun getCcEmails(): Set = cc + fun getBccEmails(): Set = bcc + fun getFromEmail(): String? = from + fun getName(): String? = fromPersonal +}