From 6e230f760581e6c2862ae677651a3f04c391c610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Sat, 17 Feb 2024 20:59:49 +0100 Subject: [PATCH] Create a dedicated EmailAddress class --- .../admin/console/http/MailMessageResource.kt | 4 +- .../io/mailit/test/TestCreateMethods.kt | 5 +- .../connector/http/web/CreateMailDto.kt | 11 +++ .../connector/http/web/HttpConnector.kt | 17 ++++- core/connector-api/build.gradle.kts | 1 + .../core/external/api/MailMessageService.kt | 11 +-- core/model/build.gradle.kts | 1 + .../io/mailit/core/model/MailMessage.kt | 5 +- .../mailit/core/service/StringExtensions.kt | 5 -- .../service/mail/MailMessageServiceImpl.kt | 25 ++----- .../service/quarkus/mailing/MailFactory.kt | 8 +-- .../mail/MailMessageServiceImplTest.kt | 69 ++++--------------- .../quarkus/mailing/MailFactoryTest.kt | 16 ++--- .../admin/client/MailMessageResourceTest.kt | 8 +-- .../http/HttpConnectorSecurityTest.kt | 12 ++-- .../connector/http/HttpConnectorTest.kt | 16 ++--- language-extensions/README.md | 5 ++ .../persistence/h2/H2MailMessageRepository.kt | 4 +- .../persistence/h2/ResultSetExtensions.kt | 5 +- .../mysql/MysqlMailMessageRepository.kt | 4 +- .../mailit/persistence/mysql/RowExtensions.kt | 5 +- .../PostgresqlMailMessageRepository.kt | 4 +- .../persistence/postgresql/RowExtensions.kt | 5 +- .../test/MailMessageRepositoryTest.kt | 29 ++++---- settings.gradle.kts | 1 + value-classes/README.md | 5 ++ value-classes/build.gradle.kts | 3 + .../kotlin/io/mailit/value/EmailAddress.kt | 16 +++++ .../io/mailit/value/EmailAddressTest.kt | 21 +++--- 29 files changed, 163 insertions(+), 158 deletions(-) create mode 100644 connector/http/src/main/kotlin/io/mailit/connector/http/web/CreateMailDto.kt delete mode 100644 core/service/src/main/kotlin/io/mailit/core/service/StringExtensions.kt create mode 100644 language-extensions/README.md create mode 100644 value-classes/README.md create mode 100644 value-classes/build.gradle.kts create mode 100644 value-classes/src/main/kotlin/io/mailit/value/EmailAddress.kt rename core/service/src/test/kotlin/io/mailit/core/service/StringExtensionsTest.kt => value-classes/src/test/kotlin/io/mailit/value/EmailAddressTest.kt (53%) diff --git a/admin-console/src/main/kotlin/io/mailit/admin/console/http/MailMessageResource.kt b/admin-console/src/main/kotlin/io/mailit/admin/console/http/MailMessageResource.kt index b408e1a0..76fa1fdf 100644 --- a/admin-console/src/main/kotlin/io/mailit/admin/console/http/MailMessageResource.kt +++ b/admin-console/src/main/kotlin/io/mailit/admin/console/http/MailMessageResource.kt @@ -28,8 +28,8 @@ class MailMessageResource( private fun MailMessage.toDto() = AdminSlicedMailDto( id = id.toString(), - emailFrom = emailFrom, - emailTo = emailTo, + emailFrom = emailFrom?.email, + emailTo = emailTo.email, type = IdNameDto(type.id.toString(), type.name), createdAt = createdAt, sendingStartedAt = sendingStartedAt, diff --git a/common-test/src/main/kotlin/io/mailit/test/TestCreateMethods.kt b/common-test/src/main/kotlin/io/mailit/test/TestCreateMethods.kt index 004ea03a..4f71b68b 100644 --- a/common-test/src/main/kotlin/io/mailit/test/TestCreateMethods.kt +++ b/common-test/src/main/kotlin/io/mailit/test/TestCreateMethods.kt @@ -7,6 +7,7 @@ import io.mailit.core.model.MailMessageTemplate import io.mailit.core.model.MailMessageType import io.mailit.core.model.PlainTextMailMessageType import io.mailit.template.api.TemplateEngine +import io.mailit.value.EmailAddress.Companion.toEmailAddress import java.util.concurrent.atomic.AtomicLong private val counter = AtomicLong() @@ -47,8 +48,8 @@ fun createMailMessage(messageType: MailMessageType): MailMessage { text = "text", data = emptyMap(), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = messageType, createdAt = nowWithoutNanos(), status = MailMessageStatus.PENDING, diff --git a/connector/http/src/main/kotlin/io/mailit/connector/http/web/CreateMailDto.kt b/connector/http/src/main/kotlin/io/mailit/connector/http/web/CreateMailDto.kt new file mode 100644 index 00000000..7352a794 --- /dev/null +++ b/connector/http/src/main/kotlin/io/mailit/connector/http/web/CreateMailDto.kt @@ -0,0 +1,11 @@ +package io.mailit.connector.http.web + +data class CreateMailDto( + val text: String?, + val data: Map?, + val subject: String?, + val emailFrom: String?, + val emailTo: String, + val mailType: String, + val deduplicationId: String?, +) diff --git a/connector/http/src/main/kotlin/io/mailit/connector/http/web/HttpConnector.kt b/connector/http/src/main/kotlin/io/mailit/connector/http/web/HttpConnector.kt index 6aa20c37..ec321737 100644 --- a/connector/http/src/main/kotlin/io/mailit/connector/http/web/HttpConnector.kt +++ b/connector/http/src/main/kotlin/io/mailit/connector/http/web/HttpConnector.kt @@ -1,8 +1,9 @@ package io.mailit.connector.http.web import io.mailit.connector.http.security.Roles.APPLICATION -import io.mailit.core.external.api.CreateMailCommand +import io.mailit.core.external.api.CreateMailRequest import io.mailit.core.external.api.MailMessageService +import io.mailit.value.EmailAddress.Companion.toEmailAddress import jakarta.annotation.security.RolesAllowed import jakarta.ws.rs.POST import jakarta.ws.rs.Path @@ -17,8 +18,18 @@ class HttpConnector( @ResponseStatus(ACCEPTED) @POST - suspend fun sendMail(command: CreateMailCommand): IdDto { - val savedMail = mailMessageService.createNewMail(command) + suspend fun sendMail(dto: CreateMailDto): IdDto { + val savedMail = mailMessageService.createNewMail(dto.toRequest()) return IdDto(savedMail.id.toString()) } + + private fun CreateMailDto.toRequest() = CreateMailRequest( + text = text, + data = data, + subject = subject, + emailFrom = emailFrom?.toEmailAddress(), + emailTo = emailTo.toEmailAddress(), + mailTypeName = mailType, + deduplicationId = deduplicationId, + ) } diff --git a/core/connector-api/build.gradle.kts b/core/connector-api/build.gradle.kts index b68579a5..74e5bbba 100644 --- a/core/connector-api/build.gradle.kts +++ b/core/connector-api/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { api(project(":core:exception")) api(project(":core:model")) + api(project(":value-classes")) } diff --git a/core/connector-api/src/main/kotlin/io/mailit/core/external/api/MailMessageService.kt b/core/connector-api/src/main/kotlin/io/mailit/core/external/api/MailMessageService.kt index 58188808..4b38fa83 100644 --- a/core/connector-api/src/main/kotlin/io/mailit/core/external/api/MailMessageService.kt +++ b/core/connector-api/src/main/kotlin/io/mailit/core/external/api/MailMessageService.kt @@ -1,18 +1,19 @@ package io.mailit.core.external.api import io.mailit.core.model.MailMessage +import io.mailit.value.EmailAddress interface MailMessageService { - suspend fun createNewMail(command: CreateMailCommand): MailMessage + suspend fun createNewMail(command: CreateMailRequest): MailMessage } -data class CreateMailCommand( +data class CreateMailRequest( val text: String?, val data: Map?, val subject: String?, - val emailFrom: String?, - val emailTo: String, - val mailType: String, + val emailFrom: EmailAddress?, + val emailTo: EmailAddress, + val mailTypeName: String, val deduplicationId: String?, ) diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 4d5fe342..4f99cd40 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -1,4 +1,5 @@ dependencies { + api(project(":value-classes")) implementation(project(":template:template-api")) testImplementation(project(":common-test")) diff --git a/core/model/src/main/kotlin/io/mailit/core/model/MailMessage.kt b/core/model/src/main/kotlin/io/mailit/core/model/MailMessage.kt index 9489a14f..f64c3a59 100644 --- a/core/model/src/main/kotlin/io/mailit/core/model/MailMessage.kt +++ b/core/model/src/main/kotlin/io/mailit/core/model/MailMessage.kt @@ -1,5 +1,6 @@ package io.mailit.core.model +import io.mailit.value.EmailAddress import java.time.Instant data class MailMessage( @@ -21,9 +22,9 @@ data class MailMessage( /** * Overrides default sender email address */ - val emailFrom: String?, + val emailFrom: EmailAddress?, - val emailTo: String, + val emailTo: EmailAddress, val type: MailMessageType, diff --git a/core/service/src/main/kotlin/io/mailit/core/service/StringExtensions.kt b/core/service/src/main/kotlin/io/mailit/core/service/StringExtensions.kt deleted file mode 100644 index 4f6dd854..00000000 --- a/core/service/src/main/kotlin/io/mailit/core/service/StringExtensions.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.mailit.core.service - -private val EMAIL_ADDRESS_REGEX = Regex("[a-zA-Z0-9+._%\\-]{1,256}@[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25})+") - -fun String.isEmail() = matches(EMAIL_ADDRESS_REGEX) diff --git a/core/service/src/main/kotlin/io/mailit/core/service/mail/MailMessageServiceImpl.kt b/core/service/src/main/kotlin/io/mailit/core/service/mail/MailMessageServiceImpl.kt index 0b62678f..31e8ba7e 100644 --- a/core/service/src/main/kotlin/io/mailit/core/service/mail/MailMessageServiceImpl.kt +++ b/core/service/src/main/kotlin/io/mailit/core/service/mail/MailMessageServiceImpl.kt @@ -3,12 +3,11 @@ package io.mailit.core.service.mail import io.mailit.core.admin.api.mail.MailMessageService import io.mailit.core.exception.DuplicateUniqueKeyException import io.mailit.core.exception.ValidationException -import io.mailit.core.external.api.CreateMailCommand +import io.mailit.core.external.api.CreateMailRequest import io.mailit.core.external.api.MailMessageService as ConnectorMailMessageService import io.mailit.core.model.MailMessage import io.mailit.core.model.MailMessageStatus import io.mailit.core.model.Slice -import io.mailit.core.service.isEmail import io.mailit.core.spi.MailMessageRepository import io.mailit.core.spi.MailMessageTypeRepository import io.mailit.idgenerator.api.IdGenerator @@ -24,11 +23,9 @@ class MailMessageServiceImpl( override suspend fun getAllSliced(page: Int, size: Int): Slice = mailMessageRepository.findAllSlicedDescendingIdSorted(page, size) - override suspend fun createNewMail(command: CreateMailCommand): MailMessage { - val messageType = mailMessageTypeRepository.findByName(command.mailType) - ?: throw ValidationException("Invalid type: ${command.mailType} is passed") - - validateBeforeCreate(command.emailFrom, command.emailTo) + override suspend fun createNewMail(command: CreateMailRequest): MailMessage { + val messageType = mailMessageTypeRepository.findByName(command.mailTypeName) + ?: throw ValidationException("Invalid type: ${command.mailTypeName} is passed") val message = MailMessage( id = idGenerator.generateId(), @@ -54,19 +51,5 @@ class MailMessageServiceImpl( return message } - private fun validateBeforeCreate(emailFrom: String?, emailTo: String) { - if (emailFrom?.isEmail() == false) { - throw ValidationException("emailFrom is incorrect") - } - - if (emailTo.isBlank()) { - throw ValidationException("emailTo shouldn't be blank") - } - - if (!emailTo.isEmail()) { - throw ValidationException("emailTo is incorrect") - } - } - companion object : KLogging() } diff --git a/core/service/src/main/kotlin/io/mailit/core/service/quarkus/mailing/MailFactory.kt b/core/service/src/main/kotlin/io/mailit/core/service/quarkus/mailing/MailFactory.kt index e3fd25d7..64476009 100644 --- a/core/service/src/main/kotlin/io/mailit/core/service/quarkus/mailing/MailFactory.kt +++ b/core/service/src/main/kotlin/io/mailit/core/service/quarkus/mailing/MailFactory.kt @@ -19,8 +19,8 @@ class MailFactory( is HtmlMailMessageType -> htmlMessage(mailMessage, type) } - if (!mailMessage.emailFrom.isNullOrBlank()) { - mail.from = mailMessage.emailFrom + mailMessage.emailFrom?.let { + mail.from = it.email } // todo allow to configure domain @@ -30,7 +30,7 @@ class MailFactory( } private fun plainTextMessage(mailMessage: MailMessage) = - Mail.withText(mailMessage.emailTo, mailMessage.subject, mailMessage.text) + Mail.withText(mailMessage.emailTo.email, mailMessage.subject, mailMessage.text) private suspend fun htmlMessage(mailMessage: MailMessage, mailMessageType: HtmlMailMessageType): Mail { val htmlMessage = templateProcessor.process( @@ -39,6 +39,6 @@ class MailFactory( data = mailMessage.data.orEmpty(), ) - return Mail.withHtml(mailMessage.emailTo, mailMessage.subject, htmlMessage.getOrThrow()) + return Mail.withHtml(mailMessage.emailTo.email, mailMessage.subject, htmlMessage.getOrThrow()) } } diff --git a/core/service/src/test/kotlin/io/mailit/core/service/mail/MailMessageServiceImplTest.kt b/core/service/src/test/kotlin/io/mailit/core/service/mail/MailMessageServiceImplTest.kt index 9ff7ab9a..54b7428f 100644 --- a/core/service/src/test/kotlin/io/mailit/core/service/mail/MailMessageServiceImplTest.kt +++ b/core/service/src/test/kotlin/io/mailit/core/service/mail/MailMessageServiceImplTest.kt @@ -2,7 +2,7 @@ package io.mailit.core.service.mail import io.mailit.core.exception.DuplicateUniqueKeyException import io.mailit.core.exception.ValidationException -import io.mailit.core.external.api.CreateMailCommand +import io.mailit.core.external.api.CreateMailRequest import io.mailit.core.model.MailMessage import io.mailit.core.model.MailMessageStatus.PENDING import io.mailit.core.model.MailMessageType @@ -11,6 +11,7 @@ import io.mailit.core.spi.MailMessageTypeRepository import io.mailit.idgenerator.test.ConstantIdGenerator import io.mailit.test.createMailMessage import io.mailit.test.createPlainMailMessageType +import io.mailit.value.EmailAddress.Companion.toEmailAddress import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.InjectMockKs @@ -25,10 +26,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource @ExtendWith(MockKExtension::class) class MailMessageServiceImplTest { @@ -59,13 +56,13 @@ class MailMessageServiceImplTest { @Test fun `createNewMail - when everything is correct - creates`() = runTest { // given - val command = CreateMailCommand( + val command = CreateMailRequest( text = "Some message", data = mapOf("name" to "john"), subject = "subject", - emailFrom = "from@gmail.com", - emailTo = "to@mail.com", - mailType = mailType.name, + emailFrom = "from@gmail.com".toEmailAddress(), + emailTo = "to@mail.com".toEmailAddress(), + mailTypeName = mailType.name, deduplicationId = "deduplication", ) @@ -92,13 +89,13 @@ class MailMessageServiceImplTest { @Test fun `createNewMail - when mail is duplicate - does nothing`() = runTest { // given - val command = CreateMailCommand( + val command = CreateMailRequest( text = "Some message", data = mapOf("name" to "john"), subject = "subject", - emailFrom = "from@gmail.com", - emailTo = "to@mail.com", - mailType = mailType.name, + emailFrom = "from@gmail.com".toEmailAddress(), + emailTo = "to@mail.com".toEmailAddress(), + mailTypeName = mailType.name, deduplicationId = "deduplication", ) @@ -114,56 +111,20 @@ class MailMessageServiceImplTest { @Test fun `createNewMail - when message type is invalid - throws exception`() = runTest { - val command = CreateMailCommand( + val command = CreateMailRequest( text = "Some message", data = mapOf("name" to "john"), subject = "subject", - emailFrom = "from@gmail.com", - emailTo = "to@mail.com", - mailType = "invalid", + emailFrom = "from@gmail.com".toEmailAddress(), + emailTo = "to@mail.com".toEmailAddress(), + mailTypeName = "invalid", deduplicationId = "deduplication", ) - coEvery { mailMessageTypeRepository.findByName(command.mailType) } returns null + coEvery { mailMessageTypeRepository.findByName(command.mailTypeName) } returns null assertThrows { mailMessageService.createNewMail(command) } coVerify(exactly = 0) { mailMessageRepository.create(any()) } } - - @ParameterizedTest - @MethodSource("invalidDataForCreation") - fun `createNewMail - with invalid data - throws exception`( - subject: String?, - emailFrom: String?, - emailTo: String, - expectedMessage: String, - ) = runTest { - val command = CreateMailCommand( - text = "123", - data = emptyMap(), - subject = subject, - emailFrom = emailFrom, - emailTo = emailTo, - mailType = "123", - deduplicationId = "deduplication", - ) - - val exception = assertThrows { - mailMessageService.createNewMail(command) - } - - assertEquals(expectedMessage, exception.message) - - coVerify(exactly = 0) { mailMessageRepository.create(any()) } - } - - companion object { - @JvmStatic - private fun invalidDataForCreation(): List = listOf( - arguments("subject", "email.email.com", "email@gmail.com", "emailFrom is incorrect"), - arguments("subject", "email@email.com", "", "emailTo shouldn't be blank"), - arguments("subject", null, "email.email.com", "emailTo is incorrect"), - ) - } } diff --git a/core/service/src/test/kotlin/io/mailit/core/service/quarkus/mailing/MailFactoryTest.kt b/core/service/src/test/kotlin/io/mailit/core/service/quarkus/mailing/MailFactoryTest.kt index 3b7e1546..b2452e95 100644 --- a/core/service/src/test/kotlin/io/mailit/core/service/quarkus/mailing/MailFactoryTest.kt +++ b/core/service/src/test/kotlin/io/mailit/core/service/quarkus/mailing/MailFactoryTest.kt @@ -10,10 +10,6 @@ import org.junit.jupiter.api.Test class MailFactoryTest { - companion object { - private const val MESSAGE_ID_HEADER = "Message-ID" - } - private val templateProcessor = StubTemplateProcessor("some html") private val mailFactory = MailFactory(templateProcessor) @@ -29,8 +25,8 @@ class MailFactoryTest { // then assertEquals(message.text, actual.text) assertEquals(message.subject, actual.subject) - assertEquals(listOf(message.emailTo), actual.to) - assertEquals(message.emailFrom, actual.from) + assertEquals(listOf(message.emailTo.email), actual.to) + assertEquals(message.emailFrom?.email, actual.from) assertEquals(listOf("${message.id}@mail-it.io"), actual.headers[MESSAGE_ID_HEADER]) } @@ -46,8 +42,12 @@ class MailFactoryTest { // then assertEquals(templateProcessor.html, actual.html) assertEquals(message.subject, actual.subject) - assertEquals(listOf(message.emailTo), actual.to) - assertEquals(message.emailFrom, actual.from) + assertEquals(listOf(message.emailTo.email), actual.to) + assertEquals(message.emailFrom?.email, actual.from) assertEquals(listOf("${message.id}@mail-it.io"), actual.headers[MESSAGE_ID_HEADER]) } + + companion object { + private const val MESSAGE_ID_HEADER = "Message-ID" + } } diff --git a/distribution/src/test/kotlin/io/mailit/distribution/admin/client/MailMessageResourceTest.kt b/distribution/src/test/kotlin/io/mailit/distribution/admin/client/MailMessageResourceTest.kt index 6da03a8e..bd48c7e7 100644 --- a/distribution/src/test/kotlin/io/mailit/distribution/admin/client/MailMessageResourceTest.kt +++ b/distribution/src/test/kotlin/io/mailit/distribution/admin/client/MailMessageResourceTest.kt @@ -63,16 +63,16 @@ class MailMessageResourceTest { "content.size()" equalTo 2, "content[0].id" equalTo mail2.id.toString(), - "content[0].emailFrom" equalTo mail2.emailFrom, - "content[0].emailTo" equalTo mail2.emailTo, + "content[0].emailFrom" equalTo mail2.emailFrom?.email, + "content[0].emailTo" equalTo mail2.emailTo.email, "content[0].type.id" equalTo mail2.type.id.toString(), "content[0].type.name" equalTo mail2.type.name, "content[0].status" equalTo mail2.status.name, "content[0].failedCount" equalTo mail2.failedCount, "content[1].id" equalTo mail1.id.toString(), - "content[1].emailFrom" equalTo mail1.emailFrom, - "content[1].emailTo" equalTo mail1.emailTo, + "content[1].emailFrom" equalTo mail1.emailFrom?.email, + "content[1].emailTo" equalTo mail1.emailTo.email, "content[1].type.id" equalTo mail1.type.id.toString(), "content[1].type.name" equalTo mail1.type.name, "content[1].status" equalTo mail1.status.name, diff --git a/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorSecurityTest.kt b/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorSecurityTest.kt index 5b001492..5bfacef4 100644 --- a/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorSecurityTest.kt +++ b/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorSecurityTest.kt @@ -3,7 +3,7 @@ package io.mailit.distribution.connector.http import io.mailit.admin.console.security.UserCredentials import io.mailit.apikey.api.ApiKeyCrud import io.mailit.apikey.api.CreateApiKeyCommand -import io.mailit.core.external.api.CreateMailCommand +import io.mailit.connector.http.web.CreateMailDto import io.mailit.core.model.MailMessageType import io.mailit.core.spi.MailMessageTypeRepository import io.mailit.test.createPlainMailMessageType @@ -50,7 +50,7 @@ class HttpConnectorSecurityTest { fun `sendMail with valid api key`() = runTest { Given { contentType(JSON) - body(createCommand()) + body(createRequestDto()) header(API_KEY_HEADER, apiKeyToken) } When { post(SEND_URL) @@ -63,7 +63,7 @@ class HttpConnectorSecurityTest { fun `sendMail with invalid api key`() = runTest { Given { contentType(JSON) - body(createCommand()) + body(createRequestDto()) header(API_KEY_HEADER, "invalid") } When { post(SEND_URL) @@ -82,7 +82,7 @@ class HttpConnectorSecurityTest { Given { contentType(JSON) - body(createCommand()) + body(createRequestDto()) header(API_KEY_HEADER, expiredToken) } When { post(SEND_URL) @@ -95,7 +95,7 @@ class HttpConnectorSecurityTest { fun `sendMail as user`() = runTest { Given { contentType(JSON) - body(createCommand()) + body(createRequestDto()) auth().form(userCredentials.username, String(userCredentials.password), FormAuthConfig("/api/admin/login", "username", "password")) } When { post(SEND_URL) @@ -104,7 +104,7 @@ class HttpConnectorSecurityTest { } } - private fun createCommand() = CreateMailCommand( + private fun createRequestDto() = CreateMailDto( text = "Hello. How are you?", data = null, subject = "Greeting", diff --git a/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorTest.kt b/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorTest.kt index fdcfd35c..d5158f54 100644 --- a/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorTest.kt +++ b/distribution/src/test/kotlin/io/mailit/distribution/connector/http/HttpConnectorTest.kt @@ -1,6 +1,6 @@ package io.mailit.distribution.connector.http -import io.mailit.core.external.api.CreateMailCommand +import io.mailit.connector.http.web.CreateMailDto import io.mailit.core.model.MailMessageType import io.mailit.core.spi.MailMessageRepository import io.mailit.core.spi.MailMessageTypeRepository @@ -46,7 +46,7 @@ class HttpConnectorTest { @Test fun `sendMail with valid message - saves mail to db`() = runTest { - val createMailDto = CreateMailCommand( + val createMailDto = CreateMailDto( text = "Hello. How are you?", data = null, subject = "Greeting", @@ -71,14 +71,14 @@ class HttpConnectorTest { assertEquals(createMailDto.text, savedMail.text) assertNull(savedMail.data) assertEquals(createMailDto.subject, savedMail.subject) - assertEquals(createMailDto.emailFrom, savedMail.emailFrom) - assertEquals(createMailDto.emailTo, savedMail.emailTo) + assertEquals(createMailDto.emailFrom, savedMail.emailFrom?.email) + assertEquals(createMailDto.emailTo, savedMail.emailTo.email) assertEquals(mailType, savedMail.type) } @Test fun `sendMail with template data in message - saves mail to db`() = runTest { - val createMailDto = CreateMailCommand( + val createMailDto = CreateMailDto( text = null, data = mapOf("oranges" to 2.39, "apples" to 0.99), subject = "Purchase receipt", @@ -103,8 +103,8 @@ class HttpConnectorTest { assertNull(savedMail.text) assertEquals(createMailDto.data, savedMail.data) assertEquals(createMailDto.subject, savedMail.subject) - assertEquals(createMailDto.emailFrom, savedMail.emailFrom) - assertEquals(createMailDto.emailTo, savedMail.emailTo) + assertEquals(createMailDto.emailFrom, savedMail.emailFrom?.email) + assertEquals(createMailDto.emailTo, savedMail.emailTo.email) assertEquals(mailType, savedMail.type) } @@ -128,7 +128,7 @@ class HttpConnectorTest { @Test fun `sendMail with invalid message type - returns 400`() { - val createMailDto = CreateMailCommand( + val createMailDto = CreateMailDto( text = "Hello. How are you?", data = null, subject = "Greeting", diff --git a/language-extensions/README.md b/language-extensions/README.md new file mode 100644 index 00000000..e0b166a7 --- /dev/null +++ b/language-extensions/README.md @@ -0,0 +1,5 @@ +# language-extensions + +The module contains extensions to the existing language classes. + +Add domain-specific value classes into [value-classes](../value-classes) module instead. diff --git a/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/H2MailMessageRepository.kt b/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/H2MailMessageRepository.kt index 21c3623e..c50cd4ed 100644 --- a/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/H2MailMessageRepository.kt +++ b/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/H2MailMessageRepository.kt @@ -230,8 +230,8 @@ class H2MailMessageRepository( mailMessage.text, dataBlob, mailMessage.subject, - mailMessage.emailFrom, - mailMessage.emailTo, + mailMessage.emailFrom?.email, + mailMessage.emailTo.email, mailMessage.type.id, mailMessage.createdAt, mailMessage.sendingStartedAt, diff --git a/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/ResultSetExtensions.kt b/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/ResultSetExtensions.kt index 3a7767af..d5b5c4a4 100644 --- a/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/ResultSetExtensions.kt +++ b/persistence/h2/src/main/kotlin/io/mailit/persistence/h2/ResultSetExtensions.kt @@ -15,6 +15,7 @@ import io.mailit.persistence.h2.Columns.MailMessageType as MailMessageTypeCol import io.mailit.persistence.h2.MailMessageContent.HTML import io.mailit.template.api.TemplateEngine import io.mailit.template.spi.persistence.PersistenceTemplate +import io.mailit.value.EmailAddress.Companion.toEmailAddress import java.sql.ResultSet import java.time.Instant @@ -99,8 +100,8 @@ internal fun ResultSet.getMailMessageWithTypeFromRow(dataSerializer: MailMessage text = text, data = data, subject = subject, - emailFrom = emailFrom, - emailTo = emailTo, + emailFrom = emailFrom?.toEmailAddress(), + emailTo = emailTo.toEmailAddress(), type = getMailMessageTypeFromRow(), createdAt = createdAt, sendingStartedAt = sendingStartedAt, diff --git a/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/MysqlMailMessageRepository.kt b/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/MysqlMailMessageRepository.kt index 469d7bbd..b89270fc 100644 --- a/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/MysqlMailMessageRepository.kt +++ b/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/MysqlMailMessageRepository.kt @@ -223,8 +223,8 @@ class MysqlMailMessageRepository( mailMessage.text, data?.toBuffer(), mailMessage.subject, - mailMessage.emailFrom, - mailMessage.emailTo, + mailMessage.emailFrom?.email, + mailMessage.emailTo.email, mailMessage.type.id, mailMessage.createdAt.toLocalDateTime(), mailMessage.sendingStartedAt?.toLocalDateTime(), diff --git a/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/RowExtensions.kt b/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/RowExtensions.kt index a5d40978..675452a7 100644 --- a/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/RowExtensions.kt +++ b/persistence/mysql/src/main/kotlin/io/mailit/persistence/mysql/RowExtensions.kt @@ -15,6 +15,7 @@ import io.mailit.persistence.mysql.Columns.MailMessageType as MailMessageTypeCol import io.mailit.persistence.mysql.MailMessageContent.HTML import io.mailit.template.api.TemplateEngine import io.mailit.template.spi.persistence.PersistenceTemplate +import io.mailit.value.EmailAddress.Companion.toEmailAddress import io.vertx.mutiny.sqlclient.Row import java.time.Instant import java.time.ZoneOffset.UTC @@ -95,8 +96,8 @@ internal fun Row.getMailMessageWithTypeFromRow(dataSerializer: MailMessageDataSe text = text, data = data, subject = subject, - emailFrom = emailFrom, - emailTo = emailTo, + emailFrom = emailFrom?.toEmailAddress(), + emailTo = emailTo.toEmailAddress(), type = getMailMessageTypeFromRow(), createdAt = createdAt, sendingStartedAt = sendingStartedAt, diff --git a/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/PostgresqlMailMessageRepository.kt b/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/PostgresqlMailMessageRepository.kt index 8194792d..8f4eed39 100644 --- a/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/PostgresqlMailMessageRepository.kt +++ b/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/PostgresqlMailMessageRepository.kt @@ -207,8 +207,8 @@ class PostgresqlMailMessageRepository( mailMessage.text, data, mailMessage.subject, - mailMessage.emailFrom, - mailMessage.emailTo, + mailMessage.emailFrom?.email, + mailMessage.emailTo.email, mailMessage.type.id, mailMessage.createdAt.toLocalDateTime(), mailMessage.sendingStartedAt?.toLocalDateTime(), diff --git a/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/RowExtensions.kt b/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/RowExtensions.kt index 2b5f1612..d05b7395 100644 --- a/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/RowExtensions.kt +++ b/persistence/postgresql/src/main/kotlin/io/mailit/persistence/postgresql/RowExtensions.kt @@ -15,6 +15,7 @@ import io.mailit.persistence.postgresql.Columns.MailMessageType as MailMessageTy import io.mailit.persistence.postgresql.MailMessageContent.HTML import io.mailit.template.api.TemplateEngine import io.mailit.template.spi.persistence.PersistenceTemplate +import io.mailit.value.EmailAddress.Companion.toEmailAddress import io.vertx.mutiny.sqlclient.Row import java.time.Instant import java.time.ZoneOffset.UTC @@ -95,8 +96,8 @@ internal fun Row.getMailMessageWithTypeFromRow(dataSerializer: MailMessageDataSe text = text, data = data, subject = subject, - emailFrom = emailFrom, - emailTo = emailTo, + emailFrom = emailFrom?.toEmailAddress(), + emailTo = emailTo.toEmailAddress(), type = getMailMessageTypeFromRow(), createdAt = createdAt, sendingStartedAt = sendingStartedAt, diff --git a/persistence/tests/src/main/kotlin/io/mailit/persistence/test/MailMessageRepositoryTest.kt b/persistence/tests/src/main/kotlin/io/mailit/persistence/test/MailMessageRepositoryTest.kt index 0f8112ad..302cd2c4 100644 --- a/persistence/tests/src/main/kotlin/io/mailit/persistence/test/MailMessageRepositoryTest.kt +++ b/persistence/tests/src/main/kotlin/io/mailit/persistence/test/MailMessageRepositoryTest.kt @@ -10,6 +10,7 @@ import io.mailit.core.spi.MailMessageRepository import io.mailit.core.spi.MailMessageTypeRepository import io.mailit.test.createPlainMailMessageType import io.mailit.test.nowWithoutNanos +import io.mailit.value.EmailAddress.Companion.toEmailAddress import jakarta.inject.Inject import java.time.Instant import kotlinx.coroutines.runBlocking @@ -44,8 +45,8 @@ abstract class MailMessageRepositoryTest { text = "text", data = emptyMap(), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = nowWithoutNanos(), status = PENDING, @@ -86,8 +87,8 @@ abstract class MailMessageRepositoryTest { text = "text2", data = emptyMap(), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = Instant.now().minusSeconds(100), sendingStartedAt = messageSendingStartedAt, @@ -112,8 +113,8 @@ abstract class MailMessageRepositoryTest { text = "text2", data = emptyMap(), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = Instant.now(), status = PENDING, @@ -137,8 +138,8 @@ abstract class MailMessageRepositoryTest { text = "text", data = emptyMap(), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = Instant.now(), status = PENDING, @@ -162,8 +163,8 @@ abstract class MailMessageRepositoryTest { text = null, data = mapOf("name" to "Name", "age" to 20), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = nowWithoutNanos(), status = PENDING, @@ -187,8 +188,8 @@ abstract class MailMessageRepositoryTest { text = null, data = mapOf("name" to "Name", "age" to 20), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = nowWithoutNanos(), status = PENDING, @@ -207,8 +208,8 @@ abstract class MailMessageRepositoryTest { text = null, data = mapOf("name" to "Name", "age" to 20), subject = null, - emailFrom = "email@from.com", - emailTo = "email@to.com", + emailFrom = "email@from.com".toEmailAddress(), + emailTo = "email@to.com".toEmailAddress(), type = mailMessageType, createdAt = nowWithoutNanos(), status = PENDING, diff --git a/settings.gradle.kts b/settings.gradle.kts index d1dbb18d..e94db600 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -60,6 +60,7 @@ include( "template:template-quarkus", "template:template-spi-persistence", "template:template-test", + "value-classes", "worker:worker-api", "worker:worker-core", "worker:worker-quarkus", diff --git a/value-classes/README.md b/value-classes/README.md new file mode 100644 index 00000000..1c63dcdf --- /dev/null +++ b/value-classes/README.md @@ -0,0 +1,5 @@ +# value-classes + +The module contains domain-specific value classes. + +Extensions to the existing language classes should go into [language-extensions](../language-extensions) module. diff --git a/value-classes/build.gradle.kts b/value-classes/build.gradle.kts new file mode 100644 index 00000000..fc014aa0 --- /dev/null +++ b/value-classes/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + testImplementation(project(":common-test")) +} diff --git a/value-classes/src/main/kotlin/io/mailit/value/EmailAddress.kt b/value-classes/src/main/kotlin/io/mailit/value/EmailAddress.kt new file mode 100644 index 00000000..6f36c31c --- /dev/null +++ b/value-classes/src/main/kotlin/io/mailit/value/EmailAddress.kt @@ -0,0 +1,16 @@ +package io.mailit.value + +@JvmInline +value class EmailAddress private constructor(val email: String) { + + companion object { + private val emailAddressRegex = Regex("[a-zA-Z0-9+._%\\-]{1,256}@[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25})+") + + fun String.toEmailAddress() = + if (matches(emailAddressRegex)) { + EmailAddress(this) + } else { + throw IllegalArgumentException("Invalid email format") + } + } +} diff --git a/core/service/src/test/kotlin/io/mailit/core/service/StringExtensionsTest.kt b/value-classes/src/test/kotlin/io/mailit/value/EmailAddressTest.kt similarity index 53% rename from core/service/src/test/kotlin/io/mailit/core/service/StringExtensionsTest.kt rename to value-classes/src/test/kotlin/io/mailit/value/EmailAddressTest.kt index b67b6c05..a8aee0d9 100644 --- a/core/service/src/test/kotlin/io/mailit/core/service/StringExtensionsTest.kt +++ b/value-classes/src/test/kotlin/io/mailit/value/EmailAddressTest.kt @@ -1,11 +1,12 @@ -package io.mailit.core.service +package io.mailit.value -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue +import io.mailit.value.EmailAddress.Companion.toEmailAddress +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -class StringExtensionsTest { +class EmailAddressTest { @ParameterizedTest @ValueSource( @@ -17,8 +18,10 @@ class StringExtensionsTest { "email@123.123.123.123", ], ) - fun `isEmail - when valid emails are passed - return true`(string: String) { - assertTrue(string.isEmail()) + fun `toEmail - when valid emails are passed - creates`(string: String) { + val email = string.toEmailAddress() + + assertEquals(string, email.email) } @ParameterizedTest @@ -32,7 +35,9 @@ class StringExtensionsTest { "Joe Smith ", ], ) - fun `isEmail - when invalid emails are passed - return false`(string: String) { - assertFalse(string.isEmail()) + fun `toEmail - when invalid emails are passed - throwsException`(string: String) { + val ex = assertThrows { string.toEmailAddress() } + + assertEquals("Invalid email format", ex.message) } }