feat(backend): implement step 8 — EntityScheduler pipeline orchestration
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package com.condado.newsletter.scheduler
|
||||
|
||||
import com.condado.newsletter.model.DispatchLog
|
||||
import com.condado.newsletter.model.DispatchStatus
|
||||
import com.condado.newsletter.model.VirtualEntity
|
||||
import com.condado.newsletter.repository.DispatchLogRepository
|
||||
import com.condado.newsletter.service.AiService
|
||||
import com.condado.newsletter.service.EmailReaderService
|
||||
import com.condado.newsletter.service.EmailSenderService
|
||||
import com.condado.newsletter.service.PromptBuilderService
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar
|
||||
import org.springframework.scheduling.support.CronTrigger
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Registers and manages per-entity scheduled tasks using [SchedulingConfigurer].
|
||||
* Refreshes task registrations every minute to pick up changes to active entities.
|
||||
*/
|
||||
@Component
|
||||
class EntityScheduler(
|
||||
private val emailReaderService: EmailReaderService,
|
||||
private val promptBuilderService: PromptBuilderService,
|
||||
private val aiService: AiService,
|
||||
private val emailSenderService: EmailSenderService,
|
||||
private val dispatchLogRepository: DispatchLogRepository,
|
||||
@Value("\${app.recipients:}") val recipients: String,
|
||||
@Value("\${imap.inbox-folder:INBOX}") val inboxFolder: String
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
/**
|
||||
* Runs the full email generation + send pipeline for the given [entity].
|
||||
* If the entity is inactive, returns immediately.
|
||||
* Always persists a [DispatchLog] with SENT or FAILED status.
|
||||
*/
|
||||
fun runPipeline(entity: VirtualEntity) {
|
||||
if (!entity.active) return
|
||||
|
||||
val recipientList = recipients.split(",")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
|
||||
try {
|
||||
val emails = emailReaderService.readEmails(inboxFolder, entity.contextWindowDays)
|
||||
val prompt = promptBuilderService.buildPrompt(entity, emails)
|
||||
val aiResponse = aiService.generate(prompt)
|
||||
emailSenderService.send(entity.email, recipientList, aiResponse.subject, aiResponse.body)
|
||||
|
||||
dispatchLogRepository.save(
|
||||
DispatchLog(
|
||||
virtualEntity = entity,
|
||||
promptSent = prompt,
|
||||
aiResponse = "${aiResponse.subject}\n${aiResponse.body}",
|
||||
emailSubject = aiResponse.subject,
|
||||
emailBody = aiResponse.body,
|
||||
status = DispatchStatus.SENT
|
||||
)
|
||||
)
|
||||
log.info("Pipeline succeeded for entity '${entity.name}'")
|
||||
} catch (e: Exception) {
|
||||
log.error("Pipeline failed for entity '${entity.name}': ${e.message}", e)
|
||||
dispatchLogRepository.save(
|
||||
DispatchLog(
|
||||
virtualEntity = entity,
|
||||
status = DispatchStatus.FAILED,
|
||||
errorMessage = e.message
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,7 @@ import com.condado.newsletter.service.AiServiceException
|
||||
import com.condado.newsletter.service.EmailReaderService
|
||||
import com.condado.newsletter.service.EmailSenderService
|
||||
import com.condado.newsletter.service.PromptBuilderService
|
||||
import io.mockk.called
|
||||
import io.mockk.capture
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mutableListOf
|
||||
import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import io.mockk.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -37,7 +29,7 @@ class EntitySchedulerTest {
|
||||
private lateinit var scheduler: EntityScheduler
|
||||
|
||||
private val entity = VirtualEntity(
|
||||
name = "João Gerente",
|
||||
name = "Joao Gerente",
|
||||
email = "joao@condado.com",
|
||||
jobTitle = "Gerente de Nada",
|
||||
personality = "Muito formal",
|
||||
|
||||
Reference in New Issue
Block a user