Skip to content

Commit

Permalink
[Refactor/#479] CRM 유스 케이스 리펙토링 (#483)
Browse files Browse the repository at this point in the history
* feat: Jsoup 의존성 추가

* refactor: BrowseTemplateUseCase 리펙토링

* refactor: SendNotificationEmailUseCase 리펙토링

* feat: HtmlValidator 구현

* feat: PostEmailTemplateEvent 구현

* refactor: PostTemplateUseCase 리펙토링

* refactor: EnrollUserUseCase 리펙토링

* refactor: 뷰에 경고창 생성 추가
  • Loading branch information
belljun3395 authored Jan 6, 2025
1 parent af88ccc commit de7f7cb
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 148 deletions.
3 changes: 3 additions & 0 deletions domain/crm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Any> =
mapOf(
"templateId" to templateId,
)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<PostEmailTemplateEvent> {
@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,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -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<String> {
val document: Document = Jsoup.parse(input, "", Parser.htmlParser())
val variables = mutableSetOf<String>() // 중복 제거를 위해 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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String> = 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)
Expand All @@ -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")
}
}
}
Loading

0 comments on commit de7f7cb

Please sign in to comment.