diff --git a/backend/src/main/kotlin/com/condado/newsletter/scheduler/EntityScheduler.kt b/backend/src/main/kotlin/com/condado/newsletter/scheduler/EntityScheduler.kt new file mode 100644 index 0000000..98abc35 --- /dev/null +++ b/backend/src/main/kotlin/com/condado/newsletter/scheduler/EntityScheduler.kt @@ -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 + ) + ) + } + } +} diff --git a/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt b/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt index f631a1f..d63a9fa 100644 --- a/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt +++ b/backend/src/test/kotlin/com/condado/newsletter/scheduler/EntitySchedulerTest.kt @@ -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",