From 59fb90c206dee58875f921a3eb0ab03488b5e69f Mon Sep 17 00:00:00 2001 From: toino Date: Thu, 23 Feb 2023 16:58:43 +0000 Subject: [PATCH 01/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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 +} From d9435bfc43c2ab745fe35a74206db31c05ec22b1 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Fri, 6 Sep 2024 13:07:58 +0100 Subject: [PATCH 11/17] Fix tests --- .../backend/email/SimpleEmailBuilder.kt | 1 + .../backend/email/BaseEmailBuilderTest.kt | 21 +++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) 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 a9424a70..f9c0f307 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() 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 index 9bfaaae9..e2e1de9b 100644 --- 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 @@ -1,16 +1,18 @@ -import jakarta.validation.ConstraintViolationException +package pt.up.fe.ni.website.backend.email + +import org.junit.jupiter.api.Assertions 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.boot.test.context.SpringBootTest 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 +@SpringBootTest @ExtendWith(MockitoExtension::class) class BaseEmailBuilderTest { private lateinit var emailConfigProperties: EmailConfigProperties @@ -20,8 +22,8 @@ class BaseEmailBuilderTest { @BeforeEach fun setup() { emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { - Mockito.`when`(from).thenReturn("test@email.com") - Mockito.`when`(fromPersonal).thenReturn("Test") + Mockito.lenient().`when`(from).thenReturn("test@email.com") + Mockito.lenient().`when`(fromPersonal).thenReturn("Test") } val javaMailSender = JavaMailSenderImpl() @@ -39,16 +41,9 @@ class BaseEmailBuilderTest { 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 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) From 024dae60f59afa69d1f3ed8e57fc88c51d8318a4 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Thu, 10 Oct 2024 10:46:31 +0100 Subject: [PATCH 12/17] Create new tests --- .../backend/service/EmailServiceTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt new file mode 100644 index 00000000..ee014d34 --- /dev/null +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt @@ -0,0 +1,47 @@ +package pt.up.fe.ni.website.backend.service + +import jakarta.mail.Session +import jakarta.mail.internet.MimeMessage +import java.io.File +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.email.EmailBuilder +import pt.up.fe.ni.website.backend.email.SimpleEmailBuilder + +@SpringBootTest +@ExtendWith(MockitoExtension::class) +class EmailServiceTest { + private lateinit var service: EmailService + private lateinit var emailBuilder: EmailBuilder + + @Test + fun `email service calls JavaMail functions with the right arguments`() { + val session = Session.getInstance(System.getProperties()) + val mailSender = Mockito.mock(JavaMailSender::class.java).apply { + Mockito.`when`(createMimeMessage()).thenReturn(MimeMessage(session)) + } + service = EmailService(mailSender) + emailBuilder = SimpleEmailBuilder() + .subject("subject") + .to("email@email.com") + + service.send(emailBuilder) + + verify(mailSender, times(1)).createMimeMessage() + + val argumentCaptor= ArgumentCaptor.forClass(MimeMessage::class.java) + verify(mailSender,times(1)).send(argumentCaptor.capture()) + Assertions.assertEquals("subject",argumentCaptor.value.subject) + } +} From efd4b4e5f5e9e368ef61f80d7af6f9e43dccca20 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Thu, 28 Nov 2024 16:59:14 +0000 Subject: [PATCH 13/17] fix: linter issues --- .../fe/ni/website/backend/service/EmailServiceTest.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt index ee014d34..3911679d 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/service/EmailServiceTest.kt @@ -2,20 +2,16 @@ package pt.up.fe.ni.website.backend.service import jakarta.mail.Session import jakarta.mail.internet.MimeMessage -import java.io.File import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any import org.mockito.Mockito import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.jupiter.MockitoExtension import org.springframework.boot.test.context.SpringBootTest import org.springframework.mail.javamail.JavaMailSender -import org.springframework.mail.javamail.MimeMessageHelper import pt.up.fe.ni.website.backend.email.EmailBuilder import pt.up.fe.ni.website.backend.email.SimpleEmailBuilder @@ -40,8 +36,8 @@ class EmailServiceTest { verify(mailSender, times(1)).createMimeMessage() - val argumentCaptor= ArgumentCaptor.forClass(MimeMessage::class.java) - verify(mailSender,times(1)).send(argumentCaptor.capture()) - Assertions.assertEquals("subject",argumentCaptor.value.subject) + val argumentCaptor = ArgumentCaptor.forClass(MimeMessage::class.java) + verify(mailSender, times(1)).send(argumentCaptor.capture()) + Assertions.assertEquals("subject", argumentCaptor.value.subject) } } From b70fd520f9c123decc761edee405a7f96508b204 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Sun, 26 Jan 2025 16:51:14 +0000 Subject: [PATCH 14/17] feat:TemplateEmailBuilder tests --- .../backend/email/ExampleEmailBuilder.kt | 8 +++ .../backend/email/TemplateEmailBuilder.kt | 2 +- .../backend/email/TemplateEmailBuilderTest.kt | 56 +++++++++++++++++++ .../templates/email/testTemplate.mustache | 7 +++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt create mode 100644 src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt create mode 100644 src/test/resources/templates/email/testTemplate.mustache diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt new file mode 100644 index 00000000..8f58f1f4 --- /dev/null +++ b/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt @@ -0,0 +1,8 @@ +package pt.up.fe.ni.website.backend.email + +data class ExampleContext( + val title: String, + val todos: List +) + +class ExampleEmailBuilder : TemplateEmailBuilder("example") 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 c7a9cc09..06e50de7 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 @@ -19,7 +19,7 @@ abstract class TemplateEmailBuilder( private val commonmarkTextRenderer = ApplicationContextUtils.getBean(TextContentRenderer::class.java) private val mustache = ApplicationContextUtils.getBean(Mustache.Compiler::class.java) - private var data: T? = null + public var data: T? = null fun data(data: T) = apply { this.data = data diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt new file mode 100644 index 00000000..908880ec --- /dev/null +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt @@ -0,0 +1,56 @@ +package pt.up.fe.ni.website.backend.email + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mockito +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.mail.javamail.JavaMailSenderImpl +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties + +@SpringBootTest +@ExtendWith(MockitoExtension::class) +class TemplateEmailBuilderTest { + private lateinit var emailConfigProperties: EmailConfigProperties + private lateinit var mimeMessageHelper: MimeMessageHelper + private lateinit var templateEmailBuilder : TemplateEmailBuilderImpl + + @BeforeEach + fun setup() { + emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + Mockito.lenient().`when`(from).thenReturn("test@email.com") + Mockito.lenient().`when`(fromPersonal).thenReturn("Test") + Mockito.lenient().`when`(templatePrefix).thenReturn("classpath:templates/email/") + Mockito.lenient().`when`(templateSuffix).thenReturn(".mustache") + Mockito.lenient().`when`(defaultHtmlLayout).thenReturn("layout.html") + Mockito.lenient().`when`(defaultStyle).thenReturn("classpath:email/style.css") + } + + val javaMailSender = JavaMailSenderImpl() + val mimeMessage = javaMailSender.createMimeMessage() + mimeMessageHelper = MimeMessageHelper(mimeMessage, true) + + templateEmailBuilder = TemplateEmailBuilderImpl(emailConfigProperties) + } + + @Test + fun `data is correctly set in 'data' field`() { + templateEmailBuilder.data(ExampleContext("title")) + templateEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals(ExampleContext("title"),templateEmailBuilder.data) + } +} + +data class ExampleContext( + val title: String +) + +class TemplateEmailBuilderImpl ( + override val emailConfigProperties: EmailConfigProperties +) : TemplateEmailBuilder("testTemplate") { + fun getData() : ExampleContext? = data +} diff --git a/src/test/resources/templates/email/testTemplate.mustache b/src/test/resources/templates/email/testTemplate.mustache new file mode 100644 index 00000000..f4eae930 --- /dev/null +++ b/src/test/resources/templates/email/testTemplate.mustache @@ -0,0 +1,7 @@ +--- +subject: Example: {{title}} +attachments: +- {{title}}.txt :: classpath:application.properties +--- + +# {{title}} From 4b0505bd41e207b52b07a5b2c96efa92b06eb3f2 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Sun, 26 Jan 2025 18:48:15 +0000 Subject: [PATCH 15/17] feat:SimpleEmailBuilder tests --- .../backend/email/SimpleEmailBuilder.kt | 14 +-- .../backend/email/TemplateEmailBuilder.kt | 2 +- .../backend/email/SimpleEmailBuilderTest.kt | 93 +++++++++++++++++++ .../backend/email/TemplateEmailBuilderTest.kt | 10 +- 4 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt 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 f9c0f307..c437daaa 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 @@ -7,14 +7,14 @@ import java.io.File import java.net.URL import org.springframework.mail.javamail.MimeMessageHelper -class SimpleEmailBuilder : BaseEmailBuilder() { - private var text: String? = null - private var html: String? = null - private var subject: String? = null - private var attachments: MutableList = mutableListOf() +open class SimpleEmailBuilder : BaseEmailBuilder() { + var text: String? = null + var html: String? = null + var subject: String? = null + 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() + var inlines: MutableList = mutableListOf() fun text(text: String) = apply { this.text = text @@ -67,7 +67,7 @@ class SimpleEmailBuilder : BaseEmailBuilder() { inlines.forEach { helper.addInline(it.name, it.content) } } - private data class EmailFile( + data class EmailFile( 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 06e50de7..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 @@ -19,7 +19,7 @@ abstract class TemplateEmailBuilder( private val commonmarkTextRenderer = ApplicationContextUtils.getBean(TextContentRenderer::class.java) private val mustache = ApplicationContextUtils.getBean(Mustache.Compiler::class.java) - public var data: T? = null + private var data: T? = null fun data(data: T) = apply { this.data = data diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt new file mode 100644 index 00000000..38c99272 --- /dev/null +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt @@ -0,0 +1,93 @@ +package pt.up.fe.ni.website.backend.email + +import jakarta.activation.DataSource +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mockito +import org.mockito.junit.jupiter.MockitoExtension +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.mail.javamail.JavaMailSenderImpl +import org.springframework.mail.javamail.MimeMessageHelper +import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties + +@SpringBootTest +@ExtendWith(MockitoExtension::class) +class SimpleEmailBuilderTest { + val dataSource: DataSource = Mockito.mock(DataSource::class.java) + + private lateinit var emailConfigProperties: EmailConfigProperties + private lateinit var mimeMessageHelper: MimeMessageHelper + private lateinit var simpleEmailBuilder: SimpleEmailBuilderImpl + + @BeforeEach + fun setup() { + emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + Mockito.lenient().`when`(from).thenReturn("test@email.com") + Mockito.lenient().`when`(fromPersonal).thenReturn("Test") + } + + val javaMailSender = JavaMailSenderImpl() + val mimeMessage = javaMailSender.createMimeMessage() + mimeMessageHelper = MimeMessageHelper(mimeMessage, true) + + simpleEmailBuilder = SimpleEmailBuilderImpl(emailConfigProperties) + } + + @Test + fun `text is correctly set when calling 'text' function`() { + simpleEmailBuilder.text("Test String") + simpleEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals("Test String", simpleEmailBuilder.getEmailText()) + } + + @Test + fun `html is correctly set when calling 'html' function`() { + simpleEmailBuilder.html("
") + simpleEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals("
", simpleEmailBuilder.getEmailHTML()) + } + + @Test + fun `subject is correctly set when calling 'subject' function`() { + simpleEmailBuilder.subject("Test Subject") + simpleEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals("Test Subject", simpleEmailBuilder.getEmailSubject()) + } + + @Test + fun `attachments are correctly set when calling 'attach' function with DataSource`() { + simpleEmailBuilder.attach("test_attachment", dataSource) + simpleEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals( + listOf(SimpleEmailBuilder.EmailFile("test_attachment", dataSource)), + simpleEmailBuilder.getEmailAttachments(), + ) + } + + @Test + fun `inlines are correctly set when calling 'inline' function`() { + simpleEmailBuilder.inline("test_attachment", dataSource) + simpleEmailBuilder.build(mimeMessageHelper) + + Assertions.assertEquals( + listOf(SimpleEmailBuilder.EmailFile("test_attachment", dataSource)), + simpleEmailBuilder.getEmailInlines() + ) + } +} + +class SimpleEmailBuilderImpl( + override val emailConfigProperties: EmailConfigProperties +) : SimpleEmailBuilder() { + fun getEmailText(): String? = text + fun getEmailHTML(): String? = html + fun getEmailSubject(): String? = subject + fun getEmailAttachments(): List = attachments + fun getEmailInlines(): List = inlines +} diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt index 908880ec..9076801e 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt @@ -16,7 +16,7 @@ import pt.up.fe.ni.website.backend.config.email.EmailConfigProperties class TemplateEmailBuilderTest { private lateinit var emailConfigProperties: EmailConfigProperties private lateinit var mimeMessageHelper: MimeMessageHelper - private lateinit var templateEmailBuilder : TemplateEmailBuilderImpl + private lateinit var templateEmailBuilder: TemplateEmailBuilderImpl @BeforeEach fun setup() { @@ -41,7 +41,7 @@ class TemplateEmailBuilderTest { templateEmailBuilder.data(ExampleContext("title")) templateEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals(ExampleContext("title"),templateEmailBuilder.data) + Assertions.assertEquals(ExampleContext("title"), templateEmailBuilder.data) } } @@ -49,8 +49,6 @@ data class ExampleContext( val title: String ) -class TemplateEmailBuilderImpl ( +class TemplateEmailBuilderImpl( override val emailConfigProperties: EmailConfigProperties -) : TemplateEmailBuilder("testTemplate") { - fun getData() : ExampleContext? = data -} +) : TemplateEmailBuilder("testTemplate") From 78fa260a24cc131774ab8d769279a3ce5612f610 Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Thu, 27 Feb 2025 18:49:20 +0000 Subject: [PATCH 16/17] fix:change data attribute and removed Impl --- .../backend/email/TemplateEmailBuilder.kt | 2 +- .../backend/email/BaseEmailBuilderTest.kt | 20 +++++++------------ .../backend/email/SimpleEmailBuilderTest.kt | 18 ++++++----------- 3 files changed, 14 insertions(+), 26 deletions(-) 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 c7a9cc09..d196d362 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 @@ -19,7 +19,7 @@ abstract class TemplateEmailBuilder( private val commonmarkTextRenderer = ApplicationContextUtils.getBean(TextContentRenderer::class.java) private val mustache = ApplicationContextUtils.getBean(Mustache.Compiler::class.java) - private var data: T? = null + var data: T? = null fun data(data: T) = apply { this.data = data 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 index e2e1de9b..83ffedb9 100644 --- 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 @@ -38,7 +38,7 @@ class BaseEmailBuilderTest { baseEmailBuilder.to("to1@email.com", "to2@email.com") baseEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals(setOf("to1@email.com", "to2@email.com"), baseEmailBuilder.getToEmails()) + Assertions.assertEquals(setOf("to1@email.com", "to2@email.com"), baseEmailBuilder.to) } @Test @@ -49,7 +49,7 @@ class BaseEmailBuilderTest { baseEmailBuilder.to(account1, account2) baseEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals(setOf("account1@email.com", "account2@email.com"), baseEmailBuilder.getToEmails()) + Assertions.assertEquals(setOf("account1@email.com", "account2@email.com"), baseEmailBuilder.to) } @Test @@ -57,7 +57,7 @@ class BaseEmailBuilderTest { baseEmailBuilder.cc("cc1@email.com", "cc2@email.com") baseEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals(setOf("cc1@email.com", "cc2@email.com"), baseEmailBuilder.getCcEmails()) + Assertions.assertEquals(setOf("cc1@email.com", "cc2@email.com"), baseEmailBuilder.cc) } @Test @@ -65,7 +65,7 @@ class BaseEmailBuilderTest { baseEmailBuilder.bcc("bcc1@email.com", "bcc2@email.com") baseEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals(setOf("bcc1@email.com", "bcc2@email.com"), baseEmailBuilder.getBccEmails()) + Assertions.assertEquals(setOf("bcc1@email.com", "bcc2@email.com"), baseEmailBuilder.bcc) } @Test @@ -73,17 +73,11 @@ class BaseEmailBuilderTest { baseEmailBuilder.from("from@email.com", "From") baseEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals("from@email.com", baseEmailBuilder.getFromEmail()) - Assertions.assertEquals("From", baseEmailBuilder.getName()) + Assertions.assertEquals("from@email.com", baseEmailBuilder.from) + Assertions.assertEquals("From", baseEmailBuilder.fromPersonal) } } 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 -} +) : BaseEmailBuilder() diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt index 38c99272..df660c97 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt @@ -40,7 +40,7 @@ class SimpleEmailBuilderTest { simpleEmailBuilder.text("Test String") simpleEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals("Test String", simpleEmailBuilder.getEmailText()) + Assertions.assertEquals("Test String", simpleEmailBuilder.text) } @Test @@ -48,7 +48,7 @@ class SimpleEmailBuilderTest { simpleEmailBuilder.html("
") simpleEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals("
", simpleEmailBuilder.getEmailHTML()) + Assertions.assertEquals("
", simpleEmailBuilder.html) } @Test @@ -56,7 +56,7 @@ class SimpleEmailBuilderTest { simpleEmailBuilder.subject("Test Subject") simpleEmailBuilder.build(mimeMessageHelper) - Assertions.assertEquals("Test Subject", simpleEmailBuilder.getEmailSubject()) + Assertions.assertEquals("Test Subject", simpleEmailBuilder.subject) } @Test @@ -66,7 +66,7 @@ class SimpleEmailBuilderTest { Assertions.assertEquals( listOf(SimpleEmailBuilder.EmailFile("test_attachment", dataSource)), - simpleEmailBuilder.getEmailAttachments(), + simpleEmailBuilder.attachments, ) } @@ -77,17 +77,11 @@ class SimpleEmailBuilderTest { Assertions.assertEquals( listOf(SimpleEmailBuilder.EmailFile("test_attachment", dataSource)), - simpleEmailBuilder.getEmailInlines() + simpleEmailBuilder.inlines ) } } class SimpleEmailBuilderImpl( override val emailConfigProperties: EmailConfigProperties -) : SimpleEmailBuilder() { - fun getEmailText(): String? = text - fun getEmailHTML(): String? = html - fun getEmailSubject(): String? = subject - fun getEmailAttachments(): List = attachments - fun getEmailInlines(): List = inlines -} +) : SimpleEmailBuilder() From a043d38a6114ae84d83365a6b0a593deec15cc8c Mon Sep 17 00:00:00 2001 From: Rubem Neto Date: Thu, 27 Feb 2025 18:55:37 +0000 Subject: [PATCH 17/17] fix:requested changes --- .../up/fe/ni/website/backend/email/ExampleEmailBuilder.kt | 8 -------- .../fe/ni/website/backend/email/BaseEmailBuilderTest.kt | 2 ++ .../fe/ni/website/backend/email/SimpleEmailBuilderTest.kt | 4 +++- .../ni/website/backend/email/TemplateEmailBuilderTest.kt | 2 ++ 4 files changed, 7 insertions(+), 9 deletions(-) delete mode 100644 src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt diff --git a/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt b/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt deleted file mode 100644 index 8f58f1f4..00000000 --- a/src/main/kotlin/pt/up/fe/ni/website/backend/email/ExampleEmailBuilder.kt +++ /dev/null @@ -1,8 +0,0 @@ -package pt.up.fe.ni.website.backend.email - -data class ExampleContext( - val title: String, - val todos: List -) - -class ExampleEmailBuilder : TemplateEmailBuilder("example") 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 index 83ffedb9..de7bfff2 100644 --- 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 @@ -22,6 +22,8 @@ class BaseEmailBuilderTest { @BeforeEach fun setup() { emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + // lenient is used because variables are not used in all tests + // https://stackoverflow.com/questions/60420191/mockito-lenient-when-to-use Mockito.lenient().`when`(from).thenReturn("test@email.com") Mockito.lenient().`when`(fromPersonal).thenReturn("Test") } diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt index df660c97..f8ae10f8 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/SimpleEmailBuilderTest.kt @@ -24,6 +24,8 @@ class SimpleEmailBuilderTest { @BeforeEach fun setup() { emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + // lenient is used because variables are not used in all tests + // https://stackoverflow.com/questions/60420191/mockito-lenient-when-to-use Mockito.lenient().`when`(from).thenReturn("test@email.com") Mockito.lenient().`when`(fromPersonal).thenReturn("Test") } @@ -66,7 +68,7 @@ class SimpleEmailBuilderTest { Assertions.assertEquals( listOf(SimpleEmailBuilder.EmailFile("test_attachment", dataSource)), - simpleEmailBuilder.attachments, + simpleEmailBuilder.attachments ) } diff --git a/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt index 9076801e..9a2a57f0 100644 --- a/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt +++ b/src/test/kotlin/pt/up/fe/ni/website/backend/email/TemplateEmailBuilderTest.kt @@ -21,6 +21,8 @@ class TemplateEmailBuilderTest { @BeforeEach fun setup() { emailConfigProperties = Mockito.mock(EmailConfigProperties::class.java).apply { + // lenient is used because variables are not used in all tests + // https://stackoverflow.com/questions/60420191/mockito-lenient-when-to-use Mockito.lenient().`when`(from).thenReturn("test@email.com") Mockito.lenient().`when`(fromPersonal).thenReturn("Test") Mockito.lenient().`when`(templatePrefix).thenReturn("classpath:templates/email/")