Skip to content

Commit

Permalink
Mail Sending: moving event creation of sent emails
Browse files Browse the repository at this point in the history
  • Loading branch information
rawyler committed Mar 7, 2024
1 parent 4ceea3a commit 479177b
Show file tree
Hide file tree
Showing 32 changed files with 1,030 additions and 729 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import java.util.Calendar

val specs2V = "4.19.0" // based on spray 1.3.x built in support
val akkaV = "2.7.+"

val sprayV = "1.3.+"
val scalalikeV = "4.0.0"
val akkaHttpVersion = "10.2.10"
Expand Down
12 changes: 6 additions & 6 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,9 @@ openolitor {
slick {
profile = "slick.jdbc.MySQLProfile$"
db {
url = "jdbc:mysql://localhost:3306/csa1?cachePrepStmts=true&cacheCallableStmts=true&cacheServerConfiguration=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&enableQueryTimeouts=false&connectionAttributes=none&verifyServerCertificate=false&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC&rewriteBatchedStatements=true"
user = "user_csa1"
password = "changeThisPasswrod"
url = "jdbc:mysql://localhost:3307/try?cachePrepStmts=true&cacheCallableStmts=true&cacheServerConfiguration=true&useLocalSessionState=true&elideSetAutoCommits=true&alwaysSendSetIsolation=false&enableQueryTimeouts=false&connectionAttributes=none&verifyServerCertificate=false&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=UTC&rewriteBatchedStatements=true"
user = "super"
password = "thedefaultdbpasswordneedstobechanged"
driver = "com.mysql.cj.jdbc.Driver"
connectionPool = HikariCP
numThreads = 5
Expand All @@ -320,10 +320,10 @@ openolitor {
# Mandant specific db settings
db: {
default: {
url = "jdbc:mysql://localhost:3306/csa1"
url = "jdbc:mysql://localhost:3307/try"
driver = "com.mysql.cj.jdbc.Driver"
user = "user_csa1"
password = "changeThisPasswrod"
user = "super"
password = "thedefaultdbpasswordneedstobechanged"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import ch.openolitor.arbeitseinsatz.ArbeitseinsatzCommandHandler._
import ch.openolitor.core._
import ch.openolitor.core.db._
import ch.openolitor.core.domain._
import ch.openolitor.stammdaten.EmailHandler
import ch.openolitor.stammdaten.MailCommandForwarder
import akka.util.Timeout

import scala.concurrent.duration._
Expand All @@ -42,39 +42,22 @@ import ch.openolitor.core.repositories.EventPublishingImplicits._
import scala.concurrent.ExecutionContext.Implicits._

object ArbeitseinsatzAktionenService {
def apply(implicit sysConfig: SystemConfig, system: ActorSystem, mailService: ActorRef): ArbeitseinsatzAktionenService = new DefaultArbeitseinsatzAktionenService(sysConfig, system, mailService)
def apply(implicit sysConfig: SystemConfig, system: ActorSystem): ArbeitseinsatzAktionenService = new DefaultArbeitseinsatzAktionenService(sysConfig, system)
}

class DefaultArbeitseinsatzAktionenService(sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef)
extends ArbeitseinsatzAktionenService(sysConfig, mailService) with DefaultArbeitseinsatzWriteRepositoryComponent {
class DefaultArbeitseinsatzAktionenService(sysConfig: SystemConfig, override val system: ActorSystem)
extends ArbeitseinsatzAktionenService(sysConfig) with DefaultArbeitseinsatzWriteRepositoryComponent {
}

/**
* Actor zum Verarbeiten der Aktionen für das Arbeitseinsatz Modul
*/
class ArbeitseinsatzAktionenService(override val sysConfig: SystemConfig, override val mailService: ActorRef) extends EventService[PersistentEvent] with LazyLogging with AsyncConnectionPoolContextAware with EmailHandler
with ArbeitseinsatzDBMappings with MailServiceReference with ArbeitseinsatzEventStoreSerializer {
class ArbeitseinsatzAktionenService(override val sysConfig: SystemConfig) extends EventService[PersistentEvent] with LazyLogging with AsyncConnectionPoolContextAware
with ArbeitseinsatzDBMappings with ArbeitseinsatzEventStoreSerializer {
self: ArbeitseinsatzWriteRepositoryComponent =>

implicit val timeout = Timeout(15.seconds) //sending mails might take a little longer

val handle: Handle = {
case SendEmailToArbeitsangebotPersonenEvent(meta, subject, body, replyTo, context) =>
checkBccAndSend(meta, subject, body, replyTo, context.person, context, mailService)
case e =>
logger.warn(s"Unknown event:$e")
}

protected def checkBccAndSend(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], person: PersonEmailData, context: Product, mailService: ActorRef)(implicit originator: PersonId = meta.originator): Unit = {
DB localTxPostPublish { implicit session => implicit publisher =>
lazy val bccAddress = config.getString("smtp.bcc")
arbeitseinsatzWriteRepository.getProjekt map { projekt: Projekt =>
projekt.sendEmailToBcc match {
case true => sendEmail(meta, subject, body, replyTo, Some(bccAddress), person, None, context, mailService)
case false => sendEmail(meta, subject, body, replyTo, None, person, None, context, mailService)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,27 @@ import ch.openolitor.core.domain._
import ch.openolitor.core.models.PersonId
import ch.openolitor.stammdaten.models.{ Person, PersonContactPermissionModify, PersonEmailData }
import ch.openolitor.mailtemplates.engine.MailTemplateService
import akka.actor.ActorSystem
import akka.actor.{ ActorRef, ActorSystem }
import ch.openolitor.core.security.Subject
import ch.openolitor.stammdaten.{ DefaultMailCommandForwarderComponent, MailCommandForwarderComponent, ProjektHelper }
import scalikejdbc._

import scala.concurrent.ExecutionContext.Implicits._
import scala.util._

object ArbeitseinsatzCommandHandler {
case class ArbeitsangebotArchivedCommand(id: ArbeitsangebotId, originator: PersonId = PersonId(100)) extends UserCommand

case class SendEmailToArbeitsangebotPersonenCommand(originator: PersonId, subject: String, body: String, replyTo: Option[String], ids: Seq[ArbeitsangebotId]) extends UserCommand

case class SendEmailToArbeitsangebotPersonenEvent(meta: EventMetadata, subject: String, body: String, replyTo: Option[String], context: ArbeitsangebotMailContext) extends PersistentGeneratedEvent with JSONSerializable

case class ChangeContactPermissionForUserCommand(originator: PersonId, subject: Subject, personId: PersonId) extends UserCommand
}

trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBMappings with ConnectionPoolContextAware with MailTemplateService {
self: ArbeitseinsatzReadRepositorySyncComponent =>
trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBMappings with ConnectionPoolContextAware with MailTemplateService with ProjektHelper {
self: ArbeitseinsatzReadRepositorySyncComponent with MailCommandForwarderComponent =>

import ArbeitseinsatzCommandHandler._
import EntityStore._

Expand All @@ -56,63 +61,73 @@ trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBM
/*
* Custom update command handling
*/
case ArbeitsangebotArchivedCommand(id, personId) => idFactory => meta =>
DB readOnly { implicit session =>
arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, id) map { arbeitsangebot =>
arbeitsangebot.status match {
case (Bereit) =>
val copy = arbeitsangebot.copy(status = Archiviert)
Success(Seq(EntityUpdateEvent(id, copy)))
case _ =>
Failure(new InvalidStateException("Der Arbeitseinsatz muss 'Bereit' sein."))
}
} getOrElse Failure(new InvalidStateException(s"Keine Arbeitseinsatz zu Id $id gefunden"))
}
case ArbeitsangebotArchivedCommand(id, personId) => idFactory =>
meta =>
DB readOnly { implicit session =>
arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, id) map { arbeitsangebot =>
arbeitsangebot.status match {
case (Bereit) =>
val copy = arbeitsangebot.copy(status = Archiviert)
Success(Seq(EntityUpdateEvent(id, copy)))
case _ =>
Failure(new InvalidStateException("Der Arbeitseinsatz muss 'Bereit' sein."))
}
} getOrElse Failure(new InvalidStateException(s"Keine Arbeitseinsatz zu Id $id gefunden"))
}

case SendEmailToArbeitsangebotPersonenCommand(personId, subject, body, replyTo, ids) => idFactory =>
meta =>
DB readOnly { implicit session =>
if (checkTemplateArbeitsangebot(body, subject, ids)) {
val events = ids flatMap { arbeitsangebotId: ArbeitsangebotId =>
arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, arbeitsangebotId) map { arbeitsangebot =>
arbeitseinsatzReadRepository.getPersonenByArbeitsangebot(arbeitsangebotId) map { person =>
val personEmailData = copyTo[Person, PersonEmailData](person)
val mailContext = ArbeitsangebotMailContext(personEmailData, arbeitsangebot)

case SendEmailToArbeitsangebotPersonenCommand(personId, subject, body, replyTo, ids) => idFactory => meta =>
DB readOnly { implicit session =>
if (checkTemplateArbeitsangebot(body, subject, ids)) {
val events = ids flatMap { arbeitsangebotId: ArbeitsangebotId =>
arbeitseinsatzReadRepository.getById(arbeitsangebotMapping, arbeitsangebotId) map { arbeitsangebot =>
arbeitseinsatzReadRepository.getPersonenByArbeitsangebot(arbeitsangebotId) map { person =>
val personEmailData = copyTo[Person, PersonEmailData](person)
val mailContext = ArbeitsangebotMailContext(personEmailData, arbeitsangebot)
DefaultResultingEvent(factory => SendEmailToArbeitsangebotPersonenEvent(factory.newMetadata(), subject, body, replyTo, mailContext))
mailCommandForwarder.sendEmail(meta, subject, body, replyTo, determineBcc, personEmailData, None, mailContext)

DefaultResultingEvent(factory => SendEmailToArbeitsangebotPersonenEvent(factory.newMetadata(), subject, body, replyTo, mailContext))
}
}
}
Success(events.flatten)
} else {
Failure(new InvalidStateException("The template is not valid"))
}
Success(events.flatten)
} else {
Failure(new InvalidStateException("The template is not valid"))
}
}

case ChangeContactPermissionForUserCommand(originator, subject, personId) => idFactory => meta =>
DB readOnly { implicit session =>
val entityToSave = arbeitseinsatzReadRepository.getById(personMapping, personId) map { user =>
copyTo[Person, PersonContactPermissionModify](user, "contactPermission" -> !user.contactPermission)
}
entityToSave match {
case Some(personContactPermissionModify) => Success(Seq(EntityUpdateEvent(personId, personContactPermissionModify)))
case _ => Failure(new InvalidStateException(s"This person was not found."))
case ChangeContactPermissionForUserCommand(originator, subject, personId) => idFactory =>
meta =>
DB readOnly { implicit session =>
val entityToSave = arbeitseinsatzReadRepository.getById(personMapping, personId) map { user =>
copyTo[Person, PersonContactPermissionModify](user, "contactPermission" -> !user.contactPermission)
}
entityToSave match {
case Some(personContactPermissionModify) => Success(Seq(EntityUpdateEvent(personId, personContactPermissionModify)))
case _ => Failure(new InvalidStateException(s"This person was not found."))
}
}
}

/*
* Insert command handling
*/
case e @ InsertEntityCommand(personId, entity: ArbeitskategorieModify) => idFactory => meta =>
handleEntityInsert[ArbeitskategorieModify, ArbeitskategorieId](idFactory, meta, entity, ArbeitskategorieId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitsangebotModify) => idFactory => meta =>
handleEntityInsert[ArbeitsangebotModify, ArbeitsangebotId](idFactory, meta, entity, ArbeitsangebotId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitseinsatzModify) => idFactory => meta =>
handleEntityInsert[ArbeitseinsatzModify, ArbeitseinsatzId](idFactory, meta, entity, ArbeitseinsatzId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitsangeboteDuplicate) => idFactory => meta =>
val events = entity.daten.map { datum =>
val arbeitsangebotDuplicate = copyTo[ArbeitsangeboteDuplicate, ArbeitsangebotDuplicate](entity, "zeitVon" -> datum)
insertEntityEvent[ArbeitsangebotDuplicate, ArbeitsangebotId](idFactory, meta, arbeitsangebotDuplicate, ArbeitsangebotId.apply)
}
Success(events)
case e @ InsertEntityCommand(personId, entity: ArbeitskategorieModify) => idFactory =>
meta =>
handleEntityInsert[ArbeitskategorieModify, ArbeitskategorieId](idFactory, meta, entity, ArbeitskategorieId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitsangebotModify) => idFactory =>
meta =>
handleEntityInsert[ArbeitsangebotModify, ArbeitsangebotId](idFactory, meta, entity, ArbeitsangebotId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitseinsatzModify) => idFactory =>
meta =>
handleEntityInsert[ArbeitseinsatzModify, ArbeitseinsatzId](idFactory, meta, entity, ArbeitseinsatzId.apply)
case e @ InsertEntityCommand(personId, entity: ArbeitsangeboteDuplicate) => idFactory =>
meta =>
val events = entity.daten.map { datum =>
val arbeitsangebotDuplicate = copyTo[ArbeitsangeboteDuplicate, ArbeitsangebotDuplicate](entity, "zeitVon" -> datum)
insertEntityEvent[ArbeitsangebotDuplicate, ArbeitsangebotId](idFactory, meta, arbeitsangebotDuplicate, ArbeitsangebotId.apply)
}
Success(events)
}

private def checkTemplateArbeitsangebot(body: String, subject: String, ids: Seq[ArbeitsangebotId])(implicit session: DBSession): Boolean = {
Expand All @@ -132,7 +147,9 @@ trait ArbeitseinsatzCommandHandler extends CommandHandler with ArbeitseinsatzDBM
}
}

class DefaultArbeitseinsatzCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem) extends ArbeitseinsatzCommandHandler
with DefaultArbeitseinsatzReadRepositorySyncComponent {
class DefaultArbeitseinsatzCommandHandler(override val sysConfig: SystemConfig, override val system: ActorSystem, override val mailService: ActorRef) extends ArbeitseinsatzCommandHandler
with DefaultArbeitseinsatzReadRepositorySyncComponent
with DefaultMailCommandForwarderComponent {

override def projektReadRepository = arbeitseinsatzReadRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import ch.openolitor.core.db.ConnectionPoolContextAware
import ch.openolitor.core.domain._

object ArbeitseinsatzEntityStoreView {
def props(mailService: ActorRef, dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultArbeitseinsatzEntityStoreView], mailService, dbEvolutionActor, sysConfig, system, airbrakeNotifier)
def props(dbEvolutionActor: ActorRef, airbrakeNotifier: ActorRef)(implicit sysConfig: SystemConfig, system: ActorSystem): Props = Props(classOf[DefaultArbeitseinsatzEntityStoreView], dbEvolutionActor, sysConfig, system, airbrakeNotifier)
}

class DefaultArbeitseinsatzEntityStoreView(override val mailService: ActorRef, val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends ArbeitseinsatzEntityStoreView
class DefaultArbeitseinsatzEntityStoreView(val dbEvolutionActor: ActorRef, implicit val sysConfig: SystemConfig, implicit val system: ActorSystem, val airbrakeNotifier: ActorRef) extends ArbeitseinsatzEntityStoreView
with DefaultArbeitseinsatzWriteRepositoryComponent

/**
Expand All @@ -48,11 +48,11 @@ trait ArbeitseinsatzEntityStoreView extends EntityStoreView
/**
* Instanzieren der jeweiligen Insert, Update und Delete Child Actors
*/
trait ArbeitseinsatzEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with MailServiceReference with SystemConfigReference {
trait ArbeitseinsatzEntityStoreViewComponent extends EntityStoreViewComponent with ActorSystemReference with SystemConfigReference {

override val insertService = ArbeitseinsatzInsertService(sysConfig, system)
override val updateService = ArbeitseinsatzUpdateService(sysConfig, system)
override val deleteService = ArbeitseinsatzDeleteService(sysConfig, system)

override val aktionenService = ArbeitseinsatzAktionenService(sysConfig, system, mailService)
override val aktionenService = ArbeitseinsatzAktionenService(sysConfig, system)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ package ch.openolitor.arbeitseinsatz.repositories
import ch.openolitor.arbeitseinsatz.models._
import ch.openolitor.core.repositories._
import ch.openolitor.stammdaten.models.{ KundeId, Person, Projekt }
import ch.openolitor.stammdaten.repositories.{ ProjektReadRepositorySync, ProjektReadRepositorySyncImpl }
import ch.openolitor.util.parsing.{ GeschaeftsjahrFilter, QueryFilter }
import com.typesafe.scalalogging.LazyLogging
import scalikejdbc.DBSession

trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync {
trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync with ProjektReadRepositorySync {
def getArbeitskategorien(implicit session: DBSession): List[Arbeitskategorie]

def getArbeitsangebote(implicit session: DBSession, gjFilter: Option[GeschaeftsjahrFilter], queryString: Option[QueryFilter]): List[Arbeitsangebot]
Expand All @@ -43,10 +44,9 @@ trait ArbeitseinsatzReadRepositorySync extends BaseReadRepositorySync {
def getArbeitseinsatzabrechnung(implicit session: DBSession, queryString: Option[QueryFilter]): List[ArbeitseinsatzAbrechnung]
def getArbeitseinsatzDetailByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[ArbeitseinsatzDetail]
def getPersonenByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[Person]
def getProjekt(implicit session: DBSession): Option[Projekt]
}

trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositorySync with LazyLogging with ArbeitseinsatzRepositoryQueries {
trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositorySync with LazyLogging with ArbeitseinsatzRepositoryQueries with ProjektReadRepositorySyncImpl {
def getArbeitskategorien(implicit session: DBSession): List[Arbeitskategorie] = {
getArbeitskategorienQuery.apply()
}
Expand Down Expand Up @@ -94,8 +94,4 @@ trait ArbeitseinsatzReadRepositorySyncImpl extends ArbeitseinsatzReadRepositoryS
def getPersonenByArbeitsangebot(arbeitsangebotId: ArbeitsangebotId)(implicit session: DBSession): List[Person] = {
getPersonenByArbeitsangebotQuery(arbeitsangebotId).apply()
}

def getProjekt(implicit session: DBSession): Option[Projekt] = {
getProjektQuery.apply()
}
}
Loading

0 comments on commit 479177b

Please sign in to comment.