2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-28 21:07:46 +00:00

Merge pull request #1425 from Jay07GIT/email_ownership_format

updated ownership transfer email content
This commit is contained in:
Nicholas Spadaccino 2024-12-11 15:50:34 -05:00 committed by GitHub
commit f03a2889f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 93 additions and 66 deletions

View File

@ -21,7 +21,7 @@ import cats.effect.IO
import cats.implicits._ import cats.implicits._
import cats.effect.IO import cats.effect.IO
import vinyldns.core.domain.batch.{BatchChange, BatchChangeApprovalStatus, SingleAddChange, SingleChange, SingleDeleteRRSetChange} import vinyldns.core.domain.batch.{BatchChange, BatchChangeApprovalStatus, SingleAddChange, SingleChange, SingleDeleteRRSetChange}
import vinyldns.core.domain.membership.{GroupRepository, User, UserRepository} import vinyldns.core.domain.membership.{Group, GroupRepository, User, UserRepository}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.mail.internet.{InternetAddress, MimeMessage} import javax.mail.internet.{InternetAddress, MimeMessage}
@ -33,6 +33,7 @@ import vinyldns.core.domain.record.OwnerShipTransferStatus.OwnerShipTransferStat
import java.time.format.{DateTimeFormatter, FormatStyle} import java.time.format.{DateTimeFormatter, FormatStyle}
import vinyldns.core.domain.batch.BatchChangeStatus._ import vinyldns.core.domain.batch.BatchChangeStatus._
import vinyldns.core.domain.batch.BatchChangeApprovalStatus._ import vinyldns.core.domain.batch.BatchChangeApprovalStatus._
import vinyldns.core.domain.zone.Zone
import java.time.ZoneId import java.time.ZoneId
@ -83,51 +84,41 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
} }
} }
def sendRecordSetOwnerTransferNotification(rsc: RecordSetChange): IO[Unit] = { def sendRecordSetOwnerTransferNotification(rsc: RecordSetChange): IO[Unit] =
for { for {
toGroup <- groupRepository.getGroup(rsc.recordSet.ownerGroupId.getOrElse("<none>")) currentUser <- userRepository.getUser(rsc.userId)
ccGroup <- groupRepository.getGroup(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("<none>")).getOrElse("<none>")) currentGroup <- groupRepository.getGroup(rsc.recordSet.ownerGroupId.getOrElse("<none>"))
_ <- toGroup match { ownerGroup <- groupRepository.getGroup(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("<none>")).getOrElse("<none>"))
currentUsers <- currentGroup match {
case Some(group) => case Some(group) =>
group.memberIds.toList.traverse { id => val users = group.memberIds.toList.map { id =>
userRepository.getUser(id).flatMap { userRepository.getUser(id).map {
case Some(UserWithEmail(toEmail)) => case Some(user) => user
ccGroup match { case None => null
case Some(ccg) => }
ccg.memberIds.toList.traverse { id => }
userRepository.getUser(id).flatMap { users.traverse(identity)
case Some(ccUser) => case None => IO.pure(List.empty)
val ccEmail = ccUser.email.getOrElse("<none>") }
send(toEmail)(new InternetAddress(ccEmail)) { message => ownerUsers <- ownerGroup match {
message.setSubject(s"VinylDNS RecordSet change ${rsc.id} results") case Some(group) =>
message.setContent(formatRecordSetChange(rsc), "text/html") val users = group.memberIds.toList.map { id =>
userRepository.getUser(id).map {
case Some(user) => user
case None => null
}
}
users.traverse(identity)
case None => IO.pure(List.empty)
}
toEmails = currentUsers.collect { case UserWithEmail(address) => address }
ccEmails = ownerUsers.collect { case UserWithEmail(address) => address }
_ <- send(toEmails: _*)(ccEmails: _*) { message =>
message.setSubject(s"VinylDNS RecordSet Ownership transfer")
message.setContent(formatRecordSetOwnerShipTransfer(rsc, currentUser, currentGroup, ownerGroup), "text/html")
message message
} }
case None =>
IO.unit
}
}
case None => IO.unit
}
case Some(user: User) if user.email.isDefined =>
IO {
logger.warn(
s"Unable to properly parse email for ${user.id}: ${user.email.getOrElse("<none>")}"
)
}
case None =>
IO {
logger.warn(s"Unable to find user: ${rsc.userId}")
}
case _ =>
IO.unit
}
}
case None => IO.unit // Handle case where toGroup is None
}
} yield () } yield ()
}
def formatBatchChange(bc: BatchChange): String = { def formatBatchChange(bc: BatchChange): String = {
val sb = new StringBuilder val sb = new StringBuilder
@ -178,26 +169,51 @@ class EmailNotifier(config: EmailNotifierConfig, session: Session, userRepositor
case (_, status) => status.toString case (_, status) => status.toString
} }
def formatRecordSetChange(rsc: RecordSetChange): String = { def formatRecordSetOwnerShipTransfer(rsc: RecordSetChange,
currentUser: Option[User],
currentGroup: Option[Group],
ownerGroup: Option[Group]
): String = {
val portalHost = config.smtp.getProperty("mail.smtp.portal.url")
val sb = new StringBuilder val sb = new StringBuilder
sb.append(s"""<h1>RecordSet Ownership Transfer</h1> sb.append(s"""<h2><u>RecordSet Ownership Transfer Alert: </u></h2>
| <b>Submitter:</b> ${ userRepository.getUser(rsc.userId).map(_.get.userName)} |<b>Submitted time: </b> ${DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withZone(ZoneId.systemDefault()).format(rsc.created)} <br/><br/>
| <b>Id:</b> ${rsc.id}<br/> | <b>Submitter: </b> ${currentUser.get.userName}<br/><br/>
| <b>Submitted time:</b> ${DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withZone(ZoneId.systemDefault()).format(rsc.created)} <br/> | <b>Zone: </b> ${if(portalHost != null) s"""<a href="$portalHost/zones/${rsc.zone.id}">${rsc.zone.name}</a> <br/>"""
| <b>OwnerShip Current Group:</b> ${rsc.recordSet.ownerGroupId.getOrElse("none")} <br/> else s"${rsc.zone.name} <br/>"}
| <b>OwnerShip Transfer Group:</b> ${rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.getOrElse("none")).getOrElse("none")} <br/> | <br/><table border = "1">
| <b>OwnerShip Transfer Status:</b> ${formatOwnerShipStatus(rsc.recordSet.recordSetGroupChange.map(_.ownerShipTransferStatus).get)}<br/> | <tr>
| <th>RecordSet</th>
| <th>Record Type</th>
| <th>TTL</th>
| <th>Record Data</th>
| </tr>
| <tr>
| <td>${rsc.recordSet.name}</td>
| <td>${rsc.recordSet.typ}</td>
| <td>${rsc.recordSet.ttl}</td>
| <td>${rsc.recordSet.records.map(id =>id)}</td>
| </tr>
| </table><br/>
| <b>Current Owner Group: </b> ${currentGroup.get.name} <br/><br/>
| <b>Transfer Owner Group: </b> ${ownerGroup.get.name} <br/><br/>
| <b>Status: ${formatOwnerShipStatus(rsc.recordSet.recordSetGroupChange.map(_.ownerShipTransferStatus).get,rsc.zone,portalHost)}</b>
| <br/><br/>
""".stripMargin) """.stripMargin)
sb.toString sb.toString
} }
def formatOwnerShipStatus(status: OwnerShipTransferStatus): String = def formatOwnerShipStatus(status: OwnerShipTransferStatus, zone:Zone, portalHost: String): String =
status match { status match {
case OwnerShipTransferStatus.ManuallyRejected => "Rejected" case OwnerShipTransferStatus.ManuallyRejected => "<i style=\"color: red;\">Rejected</i>"
case OwnerShipTransferStatus.PendingReview => "Pending Review" case OwnerShipTransferStatus.PendingReview => s"""<i style=\"color: blue;\">Pending Review </i> <br/><br/>
case OwnerShipTransferStatus.ManuallyApproved => "Approved" Requesting your review for the Ownership transfer. <br/>
case OwnerShipTransferStatus.Cancelled => "Cancelled" ${if(portalHost != null)
s"""<a href="$portalHost/zones/${zone.id}">Go to Zones </a>
>>> Search by RecordSet Name >>> Click Close Request </i><br/>"""
else s""}"""
case OwnerShipTransferStatus.ManuallyApproved => "<i style=\"color: green;\">Approved</i>"
case OwnerShipTransferStatus.Cancelled => "<i style=\"color: dark grey;\">Cancelled</i>"
} }
def formatSingleChange(sc: SingleChange, index: Int): String = sc match { def formatSingleChange(sc: SingleChange, index: Int): String = sc match {

View File

@ -189,13 +189,12 @@ class EmailNotifierSpec
message.getFrom should be(Array(fromAddress)) message.getFrom should be(Array(fromAddress))
message.getContentType should be("text/html; charset=us-ascii") message.getContentType should be("text/html; charset=us-ascii")
message.getAllRecipients should be(expectedAddresses) message.getAllRecipients should be(expectedAddresses)
message.getSubject should be(s"VinylDNS RecordSet change ${rsc.id} results") message.getSubject should be(s"VinylDNS RecordSet Ownership transfer")
val content = message.getContent.asInstanceOf[String] val content = message.getContent.asInstanceOf[String]
content.contains(rsc.id) should be(true) content.contains(rsc.zone.name) should be(true)
content.contains(rsc.recordSet.ownerGroupId.get) should be(true) content.contains(rsc.recordSet.name) should be(true)
content.contains(rsc.recordSet.recordSetGroupChange.map(_.requestedOwnerGroupId.get).get) should be(true)
} }
"do nothing when user not found" in { "do nothing when user not found" in {

View File

@ -534,6 +534,9 @@ email = {
smtp { smtp {
# Host SMTP server # Host SMTP server
portal {
url= "http://127.0.0.1:9001" # portal uri for email link
}
host = "example.host" host = "example.host"
# if smtp host requires authentication we can enable auth # if smtp host requires authentication we can enable auth
auth = true auth = true
@ -867,7 +870,16 @@ dotted-hosts = {
smtp { smtp {
# Host SMTP server # Host SMTP server
portal {
url= "http://127.0.0.1:9001" # portal uri for email link
}
host = "example.host" host = "example.host"
# if smtp host requires authentication we can enable auth
auth = true
username = sampleUser
password = samplePassword
starttls.enable = true
}
} }
} }