From de7f7cb9635645e09847691bb15993714773f84a Mon Sep 17 00:00:00 2001 From: belljun3395 <195850@jnu.ac.kr> Date: Mon, 6 Jan 2025 21:26:34 +0900 Subject: [PATCH] =?UTF-8?q?[Refactor/#479]=20CRM=20=EC=9C=A0=EC=8A=A4=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EB=A6=AC=ED=8E=99=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20(#483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Jsoup 의존성 추가 * refactor: BrowseTemplateUseCase 리펙토링 * refactor: SendNotificationEmailUseCase 리펙토링 * feat: HtmlValidator 구현 * feat: PostEmailTemplateEvent 구현 * refactor: PostTemplateUseCase 리펙토링 * refactor: EnrollUserUseCase 리펙토링 * refactor: 뷰에 경고창 생성 추가 --- domain/crm/build.gradle.kts | 3 + .../template/EmailTemplateTransactionEvent.kt | 38 ++++++++ .../EventTemplateTransactionListener.kt | 23 +++++ .../handler/PostEmailTemplateEventHandler.kt | 33 +++++++ .../few/crm/email/service/HtmlValidator.kt | 43 +++++++++ .../email/usecase/BrowseTemplateUseCase.kt | 70 +++++++------- .../crm/email/usecase/PostTemplateUseCase.kt | 96 ++++++++++++------- .../usecase/SendNotificationEmailUseCase.kt | 82 +++++++++------- .../usecase/dto/PostTemplateUseCaseDto.kt | 2 +- .../few/crm/user/usecase/EnrollUserUseCase.kt | 43 ++++++--- .../few/crm/view/email/CrmEmailSendView.kt | 20 ++-- .../crm/view/email/CrmEmailTemplateView.kt | 47 +++++---- 12 files changed, 352 insertions(+), 148 deletions(-) create mode 100644 domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt create mode 100644 domain/crm/src/main/kotlin/com/few/crm/email/event/template/EventTemplateTransactionListener.kt create mode 100644 domain/crm/src/main/kotlin/com/few/crm/email/event/template/handler/PostEmailTemplateEventHandler.kt create mode 100644 domain/crm/src/main/kotlin/com/few/crm/email/service/HtmlValidator.kt diff --git a/domain/crm/build.gradle.kts b/domain/crm/build.gradle.kts index 7785ec8a5..f8c32b908 100644 --- a/domain/crm/build.gradle.kts +++ b/domain/crm/build.gradle.kts @@ -20,6 +20,9 @@ dependencies { implementation(project(":library:email")) implementation(project(":library:event")) + /** jsoup - html parser */ + implementation("org.jsoup:jsoup:1.15.3") + /** jpa */ implementation("org.springframework.boot:spring-boot-starter-data-jpa") diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt new file mode 100644 index 000000000..6eb1f5b97 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt @@ -0,0 +1,38 @@ +package com.few.crm.email.event.template + +import event.Event +import event.EventUtils + +abstract class EmailTemplateTransactionEvent( + eventId: String = EventUtils.generateEventId(), + eventType: String, + eventTime: Long = System.currentTimeMillis(), +) : Event( + eventId = eventId, + eventType = eventType, + eventTime = eventTime, + ) + +abstract class EmailTemplateTransactionAfterCompletionEvent( + eventId: String = EventUtils.generateEventId(), + eventType: String, + eventTime: Long = System.currentTimeMillis(), +) : EmailTemplateTransactionEvent( + eventId = eventId, + eventType = eventType, + eventTime = eventTime, + ) + +class PostEmailTemplateEvent( + val templateId: Long, + eventId: String = EventUtils.generateEventId(), + eventType: String, + eventTime: Long = System.currentTimeMillis(), +) : EmailTemplateTransactionAfterCompletionEvent( + eventType = "PostEmailTemplateEvent", + ) { + override fun getData(): Map = + mapOf( + "templateId" to templateId, + ) +} \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EventTemplateTransactionListener.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EventTemplateTransactionListener.kt new file mode 100644 index 000000000..b4804caf0 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EventTemplateTransactionListener.kt @@ -0,0 +1,23 @@ +package com.few.crm.email.event.template + +import com.few.crm.email.event.template.handler.PostEmailTemplateEventHandler +import com.few.crm.support.jpa.CrmTransactional +import org.springframework.scheduling.annotation.Async +import org.springframework.stereotype.Component +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.event.TransactionPhase +import org.springframework.transaction.event.TransactionalEventListener + +@Component +class EventTemplateTransactionListener( + private val postEmailTemplateEventHandler: PostEmailTemplateEventHandler, +) { + @Async + @CrmTransactional(propagation = Propagation.REQUIRES_NEW) + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) + fun handleAfterCompletionEvent(event: EmailTemplateTransactionAfterCompletionEvent) { + when (event) { + is PostEmailTemplateEvent -> postEmailTemplateEventHandler.handle(event) + } + } +} \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/template/handler/PostEmailTemplateEventHandler.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/handler/PostEmailTemplateEventHandler.kt new file mode 100644 index 000000000..e924948b4 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/handler/PostEmailTemplateEventHandler.kt @@ -0,0 +1,33 @@ +package com.few.crm.email.event.template.handler + +import com.few.crm.email.domain.EmailTemplateHistory +import com.few.crm.email.event.template.PostEmailTemplateEvent +import com.few.crm.email.repository.EmailTemplateHistoryRepository +import com.few.crm.email.repository.EmailTemplateRepository +import com.few.crm.support.jpa.CrmTransactional +import event.EventHandler +import org.springframework.stereotype.Component + +@Component +class PostEmailTemplateEventHandler( + private val emailTemplateRepository: EmailTemplateRepository, + private val emailTemplateHistoryRepository: EmailTemplateHistoryRepository, +) : EventHandler { + @CrmTransactional + override fun handle(event: PostEmailTemplateEvent) { + val templateId = event.templateId + val template = + emailTemplateRepository + .findById(templateId) + .orElseThrow { IllegalArgumentException("EmailTemplate not found for id: $templateId") } + emailTemplateHistoryRepository.save( + EmailTemplateHistory( + templateId = template.id!!, + subject = template.subject, + body = template.body, + variables = template.variables, + version = template.version, + ), + ) + } +} \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/service/HtmlValidator.kt b/domain/crm/src/main/kotlin/com/few/crm/email/service/HtmlValidator.kt new file mode 100644 index 000000000..43f296a42 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/email/service/HtmlValidator.kt @@ -0,0 +1,43 @@ +package com.few.crm.email.service + +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.parser.Parser +import org.springframework.stereotype.Service + +@Service +class HtmlValidator { + fun isValidHtml(input: String): Boolean = + try { + Jsoup.parse(input, "", Parser.htmlParser()) + true + } catch (e: Exception) { + false + } + + fun prettyPrintHtml(input: String): String? = + try { + val document: Document = Jsoup.parse(input, "", Parser.htmlParser()) + document.normalise() + document.outerHtml() + } catch (e: Exception) { + null + } + + fun extractVariables(input: String): List { + val document: Document = Jsoup.parse(input, "", Parser.htmlParser()) + val variables = mutableSetOf() // 중복 제거를 위해 Set 사용 + + document.allElements.forEach { element -> + element.attributes().forEach { attr -> + val value = attr.value + val regex = """\$\{([a-zA-Z0-9_.]+)}""".toRegex() + regex.findAll(value).forEach { matchResult -> + variables.add(matchResult.groupValues[1]) + } + } + } + + return variables.toList() + } +} \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/BrowseTemplateUseCase.kt b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/BrowseTemplateUseCase.kt index e61b178c7..46afc02c2 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/BrowseTemplateUseCase.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/BrowseTemplateUseCase.kt @@ -24,39 +24,41 @@ class BrowseTemplateUseCase( null } - templates - .map { template -> - val history = histories?.get(template.id) - template to history - }.map { - val template = it.first - val templateHistories = it.second - TemplateResult( - template = - TemplateCurrent( - id = template.id!!, - templateName = template.templateName, - subject = template.subject, - body = template.body, - variables = template.variables, - version = template.version, - createdAt = template.createdAt.toString(), - ), - histories = - templateHistories?.map { history -> - TemplateHistory( - id = history.id!!, - templateId = history.templateId, - subject = history.subject, - body = history.body, - variables = history.variables, - version = history.version, - createdAt = history.createdAt.toString(), - ) - } ?: emptyList(), - ) - }.let { - return BrowseTemplateUseCaseOut(it) - } + return run { + templates + .map { template -> + val history = histories?.get(template.id) + template to history + }.map { + val template = it.first + val templateHistories = it.second + TemplateResult( + template = + TemplateCurrent( + id = template.id!!, + templateName = template.templateName, + subject = template.subject, + body = template.body, + variables = template.variables, + version = template.version, + createdAt = template.createdAt.toString(), + ), + histories = + templateHistories?.map { history -> + TemplateHistory( + id = history.id!!, + templateId = history.templateId, + subject = history.subject, + body = history.body, + variables = history.variables, + version = history.version, + createdAt = history.createdAt.toString(), + ) + } ?: emptyList(), + ) + }.let { + BrowseTemplateUseCaseOut(it) + } + } } } \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/PostTemplateUseCase.kt b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/PostTemplateUseCase.kt index 70c1f38b7..65c72a3cb 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/PostTemplateUseCase.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/PostTemplateUseCase.kt @@ -1,39 +1,49 @@ package com.few.crm.email.usecase import com.few.crm.email.domain.EmailTemplate -import com.few.crm.email.domain.EmailTemplateHistory -import com.few.crm.email.repository.EmailTemplateHistoryRepository +import com.few.crm.email.event.template.PostEmailTemplateEvent import com.few.crm.email.repository.EmailTemplateRepository +import com.few.crm.email.service.HtmlValidator import com.few.crm.email.usecase.dto.PostTemplateUseCaseIn import com.few.crm.email.usecase.dto.PostTemplateUseCaseOut +import com.few.crm.support.jpa.CrmTransactional +import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Service @Service class PostTemplateUseCase( private val emailTemplateRepository: EmailTemplateRepository, - private val emailTemplateHistoryRepository: EmailTemplateHistoryRepository, + private val applicationEventPublisher: ApplicationEventPublisher, + private val htmlValidator: HtmlValidator, ) { + @CrmTransactional fun execute(useCaseIn: PostTemplateUseCaseIn): PostTemplateUseCaseOut { val id: Long? = useCaseIn.id val templateName = useCaseIn.templateName val subject: String? = useCaseIn.subject val version: Float? = useCaseIn.version - // TODO: body & variables 유효성 검사 - val body = useCaseIn.body - val variables = useCaseIn.variables + var body = useCaseIn.body + if (!htmlValidator.isValidHtml(body)) { + throw IllegalArgumentException("Invalid HTML") + } + body = htmlValidator.prettyPrintHtml(body)!! + val bodyVariables: List = htmlValidator.extractVariables(body).sorted() - val persistedTemplate: EmailTemplate? = - id?.let { - emailTemplateRepository - .findById(it) - .orElseThrow { IllegalArgumentException("Template not found") } - } ?: emailTemplateRepository - .findByTemplateName(templateName) - ?.let { - throw IllegalArgumentException("Duplicate template name: $templateName") - } + val variables = + useCaseIn.variables + .filterNot { + it.isBlank() + }.filterNot { + it.isEmpty() + }.sorted() + + if (bodyVariables != variables) { + throw IllegalArgumentException("Variables do not match") + } - return ( + val persistedTemplate: EmailTemplate? = getEmailTemplate(id, templateName) + + val modifiedOrNewTemplate = persistedTemplate ?.modifySubject(subject) ?.modifyBody(body, variables) @@ -45,31 +55,45 @@ class PostTemplateUseCase( variables = variables, ) } - ).let { template -> + + modifiedOrNewTemplate.let { template -> if (template.isNewTemplate()) { emailTemplateRepository.save(template) } else { - template.updateVersion(version).let { - emailTemplateRepository.save(it) - } + template.updateVersion(version) } - // TODO: Refactor to event - }.let { savedTemplate -> - emailTemplateHistoryRepository.save( - EmailTemplateHistory( - templateId = savedTemplate.id!!, - subject = savedTemplate.subject, - body = savedTemplate.body, - variables = savedTemplate.variables, - version = savedTemplate.version, - ), - ) - }.let { template -> + } + + applicationEventPublisher.publishEvent( + PostEmailTemplateEvent( + templateId = modifiedOrNewTemplate.id!!, + eventType = "POST", + ), + ) + + return run { PostTemplateUseCaseOut( - id = template.templateId, // TODO Fix to template.id - templateName = templateName, // TODO Fix to template.templateName - version = template.version, + id = modifiedOrNewTemplate.id!!, + templateName = modifiedOrNewTemplate.templateName, + version = modifiedOrNewTemplate.version, ) } } + + private fun getEmailTemplate( + id: Long?, + templateName: String, + ): EmailTemplate? { + if (id != null) { + return emailTemplateRepository + .findById(id) + .orElseThrow { IllegalArgumentException("Template not found") } + } + + return emailTemplateRepository + .findByTemplateName(templateName) + ?.let { + throw IllegalArgumentException("Duplicate template name: $templateName") + } + } } \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/SendNotificationEmailUseCase.kt b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/SendNotificationEmailUseCase.kt index 91d7d5158..307710cd5 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/SendNotificationEmailUseCase.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/SendNotificationEmailUseCase.kt @@ -34,42 +34,11 @@ class SendNotificationEmailUseCase( val userIds = useCaseIn.userIds val sendType = "email" - val properties = - templateVersion?.let { it -> - emailTemplateHistoryRepository - .findByTemplateIdAndVersion(templateId, it) - ?.let { - NotificationEmailTemplateProperties( - subject = it.subject, - body = it.body, - ) - } - ?: throw IllegalArgumentException("Template not found") - } ?: emailTemplateRepository - .findById(templateId) - .orElseThrow { IllegalArgumentException("Template not found") } - .let { - NotificationEmailTemplateProperties( - subject = it.subject, - body = it.body, - ) - } + val properties = getEmailNotificationProperties(templateVersion, templateId) val targetUsers = - if (userIds.isEmpty()) { - userRepository - .findAllExistByUserAttributesKey() - .groupBy { - objectMapper.readValue(it.userAttributes, Map::class.java)[sendType] as String - } - } else { - userRepository - .findAllByIdIn(userIds) - .filter { - objectMapper.readValue(it.userAttributes, Map::class.java)[sendType] != null - }.groupBy { - objectMapper.readValue(it.userAttributes, Map::class.java)[sendType] as String - } + getTargetUsers(userIds, sendType).groupBy { + objectMapper.readValue(it.userAttributes, Map::class.java)[sendType] as String } targetUsers.keys.forEach { email -> @@ -94,8 +63,47 @@ class SendNotificationEmailUseCase( ) } - return SendNotificationEmailUseCaseOut( - isSuccess = true, - ) + return run { + SendNotificationEmailUseCaseOut( + isSuccess = true, + ) + } + } + + private fun getTargetUsers( + userIds: List, + sendType: String, + ) = if (userIds.isEmpty()) { + userRepository + .findAllExistByUserAttributesKey() + } else { + userRepository + .findAllByIdIn(userIds) + .filter { + objectMapper.readValue(it.userAttributes, Map::class.java)[sendType] != null + } } + + private fun getEmailNotificationProperties( + templateVersion: Float?, + templateId: Long, + ) = templateVersion?.let { it -> + emailTemplateHistoryRepository + .findByTemplateIdAndVersion(templateId, it) + ?.let { + NotificationEmailTemplateProperties( + subject = it.subject, + body = it.body, + ) + } + ?: throw IllegalArgumentException("Template not found") + } ?: emailTemplateRepository + .findById(templateId) + .orElseThrow { IllegalArgumentException("Template not found") } + .let { + NotificationEmailTemplateProperties( + subject = it.subject, + body = it.body, + ) + } } \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/dto/PostTemplateUseCaseDto.kt b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/dto/PostTemplateUseCaseDto.kt index f80630176..4a6a43d43 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/usecase/dto/PostTemplateUseCaseDto.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/usecase/dto/PostTemplateUseCaseDto.kt @@ -12,7 +12,7 @@ data class PostTemplateUseCaseIn( ) data class PostTemplateUseCaseOut( - val id: Long?, + val id: Long, val templateName: String, val version: Float?, ) \ No newline at end of file diff --git a/domain/crm/src/main/kotlin/com/few/crm/user/usecase/EnrollUserUseCase.kt b/domain/crm/src/main/kotlin/com/few/crm/user/usecase/EnrollUserUseCase.kt index 1b67ba3ff..95d938a5c 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/user/usecase/EnrollUserUseCase.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/user/usecase/EnrollUserUseCase.kt @@ -1,5 +1,6 @@ package com.few.crm.user.usecase +import com.fasterxml.jackson.databind.ObjectMapper import com.few.crm.support.jpa.CrmTransactional import com.few.crm.user.domain.User import com.few.crm.user.repository.UserRepository @@ -11,15 +12,26 @@ import java.lang.IllegalArgumentException @Service class EnrollUserUseCase( private val userRepository: UserRepository, + private val objectMapper: ObjectMapper, ) { @CrmTransactional fun execute(useCaseIn: EnrollUserUseCaseIn): EnrollUserUseCaseOut { val id: Long? = useCaseIn.id val externalId: String = useCaseIn.externalId val userAttributes: String = useCaseIn.userAttributes + run { + try { + val json = objectMapper.readTree(userAttributes) + if (json["email"] == null) { + throw IllegalArgumentException("Email is required") + } + } catch (e: Exception) { + throw IllegalArgumentException("Invalid JSON") + } + } - if (id != null) { - val modifiedUser = + val updateOrSaveUser = + if (id != null) { userRepository .findById(id) .orElseThrow { @@ -27,19 +39,20 @@ class EnrollUserUseCase( }.apply { updateAttributes(userAttributes) } - userRepository.save(modifiedUser) - } else { - userRepository.save( - User( - externalId = externalId, - userAttributes = userAttributes, - ), - ) - }.let { - return EnrollUserUseCaseOut( - id = it.id!!, - externalId = it.externalId!!, - userAttributes = it.userAttributes, + } else { + userRepository.save( + User( + externalId = externalId, + userAttributes = userAttributes, + ), + ) + } + + return run { + EnrollUserUseCaseOut( + id = updateOrSaveUser.id!!, + externalId = updateOrSaveUser.externalId!!, + userAttributes = updateOrSaveUser.userAttributes, ) } } diff --git a/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailSendView.kt b/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailSendView.kt index 1ace05ec7..fcd0a14e9 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailSendView.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailSendView.kt @@ -11,6 +11,7 @@ import com.few.crm.view.CommonVerticalLayout import com.vaadin.flow.component.button.Button import com.vaadin.flow.component.dialog.Dialog import com.vaadin.flow.component.grid.Grid +import com.vaadin.flow.component.notification.Notification import com.vaadin.flow.component.orderedlayout.HorizontalLayout import com.vaadin.flow.component.orderedlayout.VerticalLayout import com.vaadin.flow.component.textfield.TextArea @@ -176,13 +177,18 @@ class CrmEmailSendView( val sendButton = Button("Send").apply { addClickListener { - sendNotificationEmailUseCase.execute( - SendNotificationEmailUseCaseIn( - templateId = emailTemplate!!.id!!, - templateVersion = null, - userIds = selectedUsers.map { it.id!! }, - ), - ) + try { + sendNotificationEmailUseCase.execute( + SendNotificationEmailUseCaseIn( + templateId = emailTemplate!!.id!!, + templateVersion = null, + userIds = selectedUsers.map { it.id!! }, + ), + ) + } catch (e: Exception) { + val alter = Notification.show(e.message, 3000, Notification.Position.MIDDLE) + alter.open() + } dialog.close() } } diff --git a/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailTemplateView.kt b/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailTemplateView.kt index bd2780384..e305668b6 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailTemplateView.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/view/email/CrmEmailTemplateView.kt @@ -11,6 +11,7 @@ import com.vaadin.flow.component.button.Button import com.vaadin.flow.component.dialog.Dialog import com.vaadin.flow.component.formlayout.FormLayout import com.vaadin.flow.component.grid.Grid +import com.vaadin.flow.component.notification.Notification import com.vaadin.flow.component.orderedlayout.VerticalLayout import com.vaadin.flow.component.textfield.TextArea import com.vaadin.flow.component.textfield.TextField @@ -82,15 +83,20 @@ class CrmEmailTemplateView( val saveButton = Button("Save") { - PostTemplateUseCaseIn( - id = null, - templateName = templateNameField.value, - subject = subjectField.value, - version = 1.0f, - body = bodyField.value, - variables = variablesField.value.split(",").map { it.trim() }, - ).let { - postTemplateUseCase.execute(it) + try { + PostTemplateUseCaseIn( + id = null, + templateName = templateNameField.value, + subject = subjectField.value, + version = 1.0f, + body = bodyField.value, + variables = variablesField.value.split(",").map { it.trim() }, + ).let { + postTemplateUseCase.execute(it) + } + } catch (e: Exception) { + val alter = Notification.show(e.message, 3000, Notification.Position.MIDDLE) + alter.open() } grid.setItems(emailTemplateRepository.findAll()) @@ -130,15 +136,20 @@ class CrmEmailTemplateView( val saveButton = Button("Save") { - PostTemplateUseCaseIn( - id = template.id, - templateName = templateNameField.value, - subject = subjectField.value, - version = template.version + 1, - body = bodyField.value, - variables = variablesField.value.split(",").map { it.trim() }, - ).let { - postTemplateUseCase.execute(it) + try { + PostTemplateUseCaseIn( + id = template.id, + templateName = templateNameField.value, + subject = subjectField.value, + version = template.version + 1, + body = bodyField.value, + variables = variablesField.value.split(",").map { it.trim() }, + ).let { + postTemplateUseCase.execute(it) + } + } catch (e: Exception) { + val alter = Notification.show(e.message, 3000, Notification.Position.MIDDLE) + alter.open() } grid.setItems(emailTemplateRepository.findAll()) dialog.close()